diff --git a/src/Pepperdash.Core/Debug.cs b/src/Pepperdash.Core/Debug.cs
new file mode 100644
index 0000000..e580cb4
--- /dev/null
+++ b/src/Pepperdash.Core/Debug.cs
@@ -0,0 +1,687 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Crestron.SimplSharp;
+using Crestron.SimplSharp.CrestronDataStore;
+using Crestron.SimplSharp.CrestronIO;
+using Crestron.SimplSharp.CrestronLogger;
+using PepperDash.Core.Logging;
+using Serilog;
+using Serilog.Core;
+using Serilog.Events;
+using Serilog.Formatting.Compact;
+using Serilog.Formatting.Json;
+using Serilog.Templates;
+
+namespace PepperDash.Core
+{
+ ///
+ /// Contains debug commands for use in various situations
+ ///
+ public static class Debug
+ {
+ public const string DoNotLoadConfigOnNextBootKey = "DoNotLoadConfigOnNextBoot";
+ public const string ConsoleLevelStoreKey = "ConsoleDebugLevel";
+ public const string WebSocketLevelStoreKey = "WebsocketDebugLevel";
+ public const string ErrorLogLevelStoreKey = "ErrorLogDebugLevel";
+ public const string FileLevelStoreKey = "FileDebugLevel";
+
+ private static readonly Dictionary LogLevels = new()
+ {
+ {0, LogEventLevel.Information },
+ {3, LogEventLevel.Warning },
+ {4, LogEventLevel.Error },
+ {5, LogEventLevel.Fatal },
+ {1, LogEventLevel.Debug },
+ {2, LogEventLevel.Verbose },
+ };
+
+ private static readonly LoggingLevelSwitch ConsoleLoggingLevelSwitch = new();
+
+ private static readonly LoggingLevelSwitch WebsocketLoggingLevelSwitch = new(LogEventLevel.Debug);
+
+ private static readonly LoggingLevelSwitch ErrorLogLevelSwitch = new(LogEventLevel.Error);
+
+ private static readonly LoggingLevelSwitch FileLevelSwitch = new(LogEventLevel.Debug);
+
+ ///
+ /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal
+ ///
+ public static bool DoNotLoadConfigOnNextBoot =>
+ CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadConfigOnNextBootKey, out var value) switch
+ {
+ CrestronDataStore.CDS_ERROR.CDS_SUCCESS => value,
+ _ => false
+ };
+
+ ///
+ /// Version for the currently loaded PepperDashCore dll
+ ///
+ public static string PepperDashCoreVersion { get; }
+
+ public static int WebsocketPort { get; }
+
+ public static readonly LoggerConfiguration DefaultLoggerConfiguration;
+
+ public static readonly bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
+
+ static Debug()
+ {
+ CrestronDataStoreStatic.InitCrestronDataStore();
+
+ var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
+ $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
+ $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log";
+
+ CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
+
+ const string consoleTemplate =
+ "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
+
+ var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
+ ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
+ : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
+
+ DefaultLoggerConfiguration = new LoggerConfiguration()
+ .MinimumLevel.Verbose()
+ .Enrich.FromLogContext()
+ .Enrich.With(new CrestronEnricher())
+ .WriteTo.DebugConsoleSink(new ExpressionTemplate(template: consoleTemplate), levelSwitch: ConsoleLoggingLevelSwitch)
+ .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(template: errorLogTemplate)), levelSwitch: ErrorLogLevelSwitch)
+ .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
+ rollingInterval: RollingInterval.Day,
+ restrictedToMinimumLevel: LogEventLevel.Debug,
+ retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
+ levelSwitch: FileLevelSwitch
+ );
+
+#if NET472
+
+ const string certFilename = "cert.pfx";
+ var certPath = IsRunningOnAppliance ? Path.Combine("user", certFilename) : Path.Combine("User", certFilename);
+ var websocket = new DebugNet472WebSocket(certPath);
+ WebsocketPort = websocket.Port;
+ DefaultLoggerConfiguration.WriteTo.Sink(new DebugWebsocketSink(websocket, new JsonFormatter(renderMessage: true)), levelSwitch: WebsocketLoggingLevelSwitch);
+#endif
+
+ try
+ {
+ if (InitialParametersClass.NumberOfRemovableDrives > 0)
+ {
+ CrestronConsole.PrintLine("{0} RM Drive(s) Present. Initializing Crestron Logger", InitialParametersClass.NumberOfRemovableDrives);
+ DefaultLoggerConfiguration.WriteTo.Sink(new DebugCrestronLoggerSink());
+ }
+ else
+ CrestronConsole.PrintLine("No RM Drive(s) Present. Not using Crestron Logger");
+ }
+ catch (Exception e)
+ {
+ CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e);
+ }
+
+ var storedConsoleLevel = DebugContext.GetDataForKey(ConsoleLevelStoreKey, LogEventLevel.Information);
+ ConsoleLoggingLevelSwitch.MinimumLevel = storedConsoleLevel.Level;
+
+ Log.Logger = DefaultLoggerConfiguration.CreateLogger();
+ PepperDashCoreVersion = GetVersion();
+
+ var msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
+
+ if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
+ {
+ msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
+ }
+
+ LogMessage(LogEventLevel.Information,msg);
+
+ if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
+ {
+ CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
+ "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
+
+ CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
+ "appdebug:P [0-5] --devices [devices]: Sets the application's console debug message level. Devices is an array of comma separated device keys for devices you would like to debug. An empty array will allow all devices",
+ ConsoleAccessLevelEnum.AccessOperator);
+
+ CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
+ "appdebuglog:P [all] Use \"all\" for full log.",
+ ConsoleAccessLevelEnum.AccessOperator);
+ }
+
+ if(DoNotLoadConfigOnNextBoot)
+ CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
+ }
+
+ private static string GetVersion()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var ver =
+ assembly
+ .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false);
+
+ if (ver.Length > 0 && ver[0] is AssemblyInformationalVersionAttribute verAttribute)
+ {
+ return verAttribute.InformationalVersion;
+ }
+
+ var name = assembly.GetName();
+ return name.Version?.ToString() ?? "0.0.0";
+ }
+
+ private static void SetDebugFromConsole(string levelString)
+ {
+ try
+ {
+ if (levelString.Trim() == "?")
+ {
+ CrestronConsole.ConsoleCommandResponse(
+ $"""
+ Used to set the minimum level of debug messages to be printed to the console:
+ {LogLevels[0]} = 0
+ {LogLevels[1]} = 1
+ {LogLevels[2]} = 2
+ {LogLevels[3]} = 3
+ {LogLevels[4]} = 4
+ {LogLevels[5]} = 5
+ """);
+ return;
+ }
+
+ if (string.IsNullOrEmpty(levelString.Trim()))
+ {
+ CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", ConsoleLoggingLevelSwitch.MinimumLevel);
+ return;
+ }
+
+ const string pattern = @"^(\d+)(?:\s+--devices\s+([\w,]+))?$";
+ var match = Regex.Match(levelString, pattern);
+ if (match.Success)
+ {
+ var level = int.Parse(match.Groups[1].Value);
+ var devices = match.Groups[2].Success
+ ? match.Groups[2].Value.Split(',')
+ : [];
+
+
+ if (LogLevels.TryGetValue(level, out var logEventLevel))
+ {
+ SetConsoleDebugLevel(logEventLevel, devices);
+ }
+ else
+ {
+ CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
+ }
+ }
+
+ CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
+ }
+ catch
+ {
+ CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5] --devices [deviceKeys]");
+ }
+ }
+
+ public static void SetConsoleDebugLevel(LogEventLevel level, string[] includedDevices = null)
+ {
+ ConsoleLoggingLevelSwitch.MinimumLevel = level;
+ DebugContext.SetDataForKey(ConsoleLevelStoreKey, level, includedDevices);
+
+ var includedDevicesMessage = includedDevices == null || includedDevices.Length == 0 ? "all" : string.Join(",", includedDevices);
+ CrestronConsole.ConsoleCommandResponse($"Success: set console debug level to {level} with devices:{includedDevicesMessage}");
+
+ }
+
+ public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
+ {
+ WebsocketLoggingLevelSwitch.MinimumLevel = level;
+ LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", WebsocketLoggingLevelSwitch.MinimumLevel);
+ }
+
+ public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
+ {
+ ErrorLogLevelSwitch.MinimumLevel = level;
+ LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", WebsocketLoggingLevelSwitch.MinimumLevel);
+ }
+
+ public static void SetFileMinimumDebugLevel(LogEventLevel level)
+ {
+ ErrorLogLevelSwitch.MinimumLevel = level;
+ LogMessage(LogEventLevel.Information, "File debug level set to {0}", WebsocketLoggingLevelSwitch.MinimumLevel);
+ }
+
+ public static void SetDebugLevel(uint level)
+ {
+ if (LogLevels.TryGetValue((int)level, out var logEventLevel))
+ {
+ SetConsoleDebugLevel(logEventLevel);
+ }
+ else
+ {
+ CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {level} to valid log level");
+ }
+ }
+
+ ///
+ /// Callback for console command
+ ///
+ ///
+ private static void SetDoNotLoadOnNextBootFromConsole(string stateString)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(stateString.Trim()))
+ {
+ CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot);
+ return;
+ }
+
+ SetDoNotLoadConfigOnNextBoot(bool.Parse(stateString));
+ }
+ catch
+ {
+ CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
+ }
+ }
+
+ ///
+ /// Sets the flag to prevent application starting on next boot
+ ///
+ ///
+ public static void SetDoNotLoadConfigOnNextBoot(bool state)
+ {
+ CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadConfigOnNextBootKey, state);
+ CrestronConsole.ConsoleCommandResponse("[Application {0}], Do Not Load Config on Next Boot set to {1}", InitialParametersClass.ApplicationNumber, DoNotLoadConfigOnNextBoot);
+ }
+
+ ///
+ /// Prints message to console if current debug level is equal to or higher than the level of this message.
+ /// Uses CrestronConsole.PrintLine.
+ ///
+ ///
+ /// Console format string
+ /// Object parameters
+ [Obsolete("Use LogMessage methods")]
+ public static void Console(uint level, string format, params object[] items)
+ {
+ var logLevel = LogLevels.TryGetValue((int)level, out var value) ? value : LogEventLevel.Information;
+ LogMessage(logLevel, format, items);
+
+ //if (IsRunningOnAppliance)
+ //{
+ // CrestronConsole.PrintLine("[{0}]App {1} Lvl {2}:{3}", DateTime.Now.ToString("HH:mm:ss.fff"),
+ // InitialParametersClass.ApplicationNumber,
+ // level,
+ // string.Format(format, items));
+ //}
+ }
+
+ ///
+ /// Logs to Console when at-level, and all messages to error log, including device key
+ ///
+ [Obsolete("Use LogMessage methods")]
+ public static void Console(uint level, IKeyed dev, string format, params object[] items)
+ {
+ var logLevel = LogLevels.TryGetValue((int)level, out var value) ? value : LogEventLevel.Information;
+ LogMessage(logLevel, dev, format, items);
+
+ //if (Level >= level)
+ // Console(level, "[{0}] {1}", dev.Key, message);
+ }
+
+ ///
+ /// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log.
+ /// Uses CrestronConsole.PrintLine.
+ ///
+ [Obsolete("Use LogMessage methods")]
+ public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel, string format, params object[] items)
+ {
+ var logLevel = LogLevels.TryGetValue((int)level, out var value) ? value : LogEventLevel.Information;
+ LogMessage(logLevel, dev, format, items);
+ }
+
+ ///
+ /// Logs to Console when at-level, and all messages to error log
+ ///
+ [Obsolete("Use LogMessage methods")]
+ public static void Console(uint level, ErrorLogLevel errorLogLevel, string format, params object[] items)
+ {
+ var logLevel = LogLevels.TryGetValue((int)level, out var value) ? value : LogEventLevel.Information;
+ LogMessage(logLevel, format, items);
+ }
+
+ ///
+ /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
+ /// or above the level provided, then the output will be written to both console and the log. Otherwise
+ /// it will only be written to the log.
+ ///
+ [Obsolete("Use LogMessage methods")]
+ public static void ConsoleWithLog(uint level, string format, params object[] items)
+ {
+ var logLevel = LogLevels.TryGetValue((int)level, out var value) ? value : LogEventLevel.Information;
+ LogMessage(logLevel, format, items);
+
+ // var str = string.Format(format, items);
+ //if (Level >= level)
+ // CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
+ // CrestronLogger.WriteToLog(str, level);
+ }
+
+ ///
+ /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
+ /// or above the level provided, then the output will be written to both console and the log. Otherwise
+ /// it will only be written to the log.
+ ///
+ [Obsolete("Use LogMessage methods")]
+ public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
+ {
+ var logLevel = LogLevels.TryGetValue((int)level, out var value) ? value : LogEventLevel.Information;
+ LogMessage(logLevel, dev, format, items);
+
+ // var str = string.Format(format, items);
+ // CrestronLogger.WriteToLog(string.Format("[{0}] {1}", dev.Key, str), level);
+ }
+
+ ///
+ ///
+ ///
+ public static void ShowDebugLog(string s)
+ {
+ var logList = CrestronLogger.PrintTheLog(s.ToLower() == "all");
+ foreach (var l in logList)
+ CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
+ }
+
+ public static ILogger CreateLoggerForDevice(T device) where T : IKeyed =>
+ Log.ForContext().ForContext("Key", device?.Key);
+
+ //Message
+ public static void LogMessage(LogEventLevel level, string message, params object[] args)
+ {
+ Log.Write(level, message, args);
+ }
+
+ public static void LogMessage(LogEventLevel level, IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Write(level, message, args);
+ }
+
+ public static void LogMessage(LogEventLevel level, IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Write(level, ex, message, args);
+ }
+
+ public static void LogMessage(LogEventLevel level, IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).ForContext().Write(level, message, args);
+ }
+
+ public static void LogMessage(LogEventLevel level, string message, params object[] args)
+ {
+ Log.ForContext().Write(level, message, args);
+ }
+
+ public static void LogMessage(LogEventLevel level, IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext("Key", device?.Key).Write(level, ex, message, args);
+ }
+
+ //Error
+ public static void LogError(string message, params object[] args)
+ {
+ Log.Error(message, args);
+ }
+
+ public static void LogError(Exception ex, string message, params object[] args)
+ {
+ Log.Error(ex, message, args);
+ }
+
+ public static void LogError(string message, params object[] args)
+ {
+ Log.ForContext().Error(message, args);
+ }
+
+ public static void LogError(Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().Error(ex, message, args);
+ }
+
+ public static void LogError(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Error(message, args);
+ }
+
+ public static void LogError(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Error(ex, message, args);
+ }
+
+ public static void LogError(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext().ForContext("Key", device?.Key).Error(message, args);
+ }
+
+ public static void LogError(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext("Key", device?.Key).Error(ex, message, args);
+ }
+
+ //Verbose
+ public static void LogVerbose(string message, params object[] args)
+ {
+ Log.Verbose(message, args);
+ }
+ public static void LogVerbose(Exception ex, string message, params object[] args)
+ {
+ Log.Verbose(ex, message, args);
+ }
+
+ public static void LogVerbose(string message, params object[] args)
+ {
+ Log.ForContext().Verbose(message, args);
+ }
+
+ public static void LogVerbose(Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext().Verbose(ex, message, args);
+ }
+
+ public static void LogVerbose(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Verbose(message, args);
+ }
+ public static void LogVerbose(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Verbose(ex, message, args);
+ }
+
+ public static void LogVerbose(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).ForContext().Verbose(message, args);
+ }
+
+ public static void LogVerbose(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext().Verbose(ex, message, args);
+ }
+
+ //Debug
+ public static void LogDebug(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Debug(message, args);
+ }
+ public static void LogDebug(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Debug(ex, message, args);
+ }
+
+ public static void LogDebug(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).ForContext().Debug(message, args);
+ }
+
+ public static void LogDebug(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext().Debug(ex, message, args);
+ }
+
+ public static void LogDebug(string message, params object[] args)
+ {
+ Log.Debug(message, args);
+ }
+
+ public static void LogDebug(string message, params object[] args)
+ {
+ Log.ForContext().Debug(message, args);
+ }
+
+ public static void LogDebug(Exception ex, string message, params object[] args)
+ {
+ Log.Debug(ex, message, args);
+ }
+
+ public static void LogDebug(Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().Debug(ex, message, args);
+ }
+
+ //Information
+ public static void LogInformation(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Information(message, args);
+ }
+ public static void LogInformation(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Information(ex, message, args);
+ }
+
+ public static void LogInformation(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).ForContext().Information(message, args);
+ }
+
+ public static void LogInformation(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext().Information(ex, message, args);
+ }
+
+ public static void LogInformation(string message, params object[] args)
+ {
+ Log.Information(message, args);
+ }
+
+ public static void LogInformation(string message, params object[] args)
+ {
+ Log.ForContext().Information(message, args);
+ }
+
+ public static void LogInformation(Exception ex, string message, params object[] args)
+ {
+ Log.Information(ex, message, args);
+ }
+
+ public static void LogInformation(Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().Information(ex, message, args);
+ }
+
+ //Fatal
+ public static void LogWarning(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Warning(message, args);
+ }
+ public static void LogWarning(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Warning(ex, message, args);
+ }
+
+ public static void LogWarning(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).ForContext().Warning(message, args);
+ }
+
+ public static void LogWarning(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext().Warning(ex, message, args);
+ }
+
+ public static void LogWarning(string message, params object[] args)
+ {
+ Log.Warning(message, args);
+ }
+
+ public static void LogWarning(string message, params object[] args)
+ {
+ Log.ForContext().Warning(message, args);
+ }
+
+ public static void LogWarning(Exception ex, string message, params object[] args)
+ {
+ Log.Warning(ex, message, args);
+ }
+ public static void LogWarning(Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().Warning(ex, message, args);
+ }
+
+ //Fatal
+ public static void LogFatal(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Fatal(message, args);
+ }
+ public static void LogFatal(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).Fatal(ex, message, args);
+ }
+
+ public static void LogFatal(IKeyed device, string message, params object[] args)
+ {
+ Log.ForContext("Key", device?.Key).ForContext().Fatal(message, args);
+ }
+
+ public static void LogFatal(IKeyed device, Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().ForContext().Fatal(ex, message, args);
+ }
+
+ public static void LogFatal(string message, params object[] args)
+ {
+ Log.Fatal(message, args);
+ }
+
+ public static void LogFatal(string message, params object[] args)
+ {
+ Log.ForContext().Fatal(message, args);
+ }
+
+ public static void LogFatal(Exception ex, string message, params object[] args)
+ {
+ Log.Fatal(ex, message, args);
+ }
+ public static void LogFatal(Exception ex, string message, params object[] args)
+ {
+ Log.ForContext().Fatal(ex, message, args);
+ }
+
+ public enum ErrorLogLevel
+ {
+ ///
+ /// Error
+ ///
+ Error,
+ ///
+ /// Warning
+ ///
+ Warning,
+ ///
+ /// Notice
+ ///
+ Notice,
+ ///
+ /// None
+ ///
+ None,
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Pepperdash.Core/Logging/CrestronEnricher.cs b/src/Pepperdash.Core/Logging/CrestronEnricher.cs
index 902ce8d..61a267a 100644
--- a/src/Pepperdash.Core/Logging/CrestronEnricher.cs
+++ b/src/Pepperdash.Core/Logging/CrestronEnricher.cs
@@ -1,35 +1,27 @@
using Crestron.SimplSharp;
using Serilog.Core;
using Serilog.Events;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace PepperDash.Core.Logging
{
public class CrestronEnricher : ILogEventEnricher
{
- static readonly string _appName;
+ private static readonly string AppName;
static CrestronEnricher()
{
- switch (CrestronEnvironment.DevicePlatform)
+ AppName = CrestronEnvironment.DevicePlatform switch
{
- case eDevicePlatform.Appliance:
- _appName = $"App {InitialParametersClass.ApplicationNumber}";
- break;
- case eDevicePlatform.Server:
- _appName = $"{InitialParametersClass.RoomId}";
- break;
- }
+ eDevicePlatform.Appliance => $"App {InitialParametersClass.ApplicationNumber}",
+ eDevicePlatform.Server => $"{InitialParametersClass.RoomId}",
+ _ => string.Empty
+ };
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
- var property = propertyFactory.CreateProperty("App", _appName);
+ var property = propertyFactory.CreateProperty("App", AppName);
logEvent.AddOrUpdateProperty(property);
}
diff --git a/src/Pepperdash.Core/Logging/Debug.cs b/src/Pepperdash.Core/Logging/Debug.cs
deleted file mode 100644
index 4e79a60..0000000
--- a/src/Pepperdash.Core/Logging/Debug.cs
+++ /dev/null
@@ -1,895 +0,0 @@
-using Crestron.SimplSharp;
-using Crestron.SimplSharp.CrestronDataStore;
-using Crestron.SimplSharp.CrestronIO;
-using Crestron.SimplSharp.CrestronLogger;
-using Newtonsoft.Json;
-using PepperDash.Core.Logging;
-using Serilog;
-using Serilog.Context;
-using Serilog.Core;
-using Serilog.Events;
-using Serilog.Formatting.Compact;
-using Serilog.Formatting.Json;
-using Serilog.Templates;
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Text.RegularExpressions;
-
-namespace PepperDash.Core
-{
- ///
- /// Contains debug commands for use in various situations
- ///
- public static class Debug
- {
- private static readonly string LevelStoreKey = "ConsoleDebugLevel";
- private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel";
- private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel";
- private static readonly string FileLevelStoreKey = "FileDebugLevel";
-
- private static readonly Dictionary _logLevels = new Dictionary()
- {
- {0, LogEventLevel.Information },
- {3, LogEventLevel.Warning },
- {4, LogEventLevel.Error },
- {5, LogEventLevel.Fatal },
- {1, LogEventLevel.Debug },
- {2, LogEventLevel.Verbose },
- };
-
- private static ILogger _logger;
-
- private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch;
-
- private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
-
- private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
-
- private static readonly LoggingLevelSwitch _fileLevelSwitch;
-
- public static LogEventLevel WebsocketMinimumLogLevel
- {
- get { return _websocketLoggingLevelSwitch.MinimumLevel; }
- }
-
- private static readonly DebugWebsocketSink _websocketSink;
-
- public static DebugWebsocketSink WebsocketSink
- {
- get { return _websocketSink; }
- }
-
- ///
- /// Describes the folder location where a given program stores it's debug level memory. By default, the
- /// file written will be named appNdebug where N is 1-10.
- ///
- public static string OldFilePathPrefix = @"\nvram\debug\";
-
- ///
- /// Describes the new folder location where a given program stores it's debug level memory. By default, the
- /// file written will be named appNdebug where N is 1-10.
- ///
- public static string NewFilePathPrefix = @"\user\debug\";
-
- ///
- /// The name of the file containing the current debug settings.
- ///
- public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
-
- ///
- /// Debug level to set for a given program.
- ///
- public static int Level { get; private set; }
-
- ///
- /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal
- ///
- public static bool DoNotLoadConfigOnNextBoot { get; private set; }
-
- private static DebugContextCollection _contexts;
-
- private const int SaveTimeoutMs = 30000;
-
- public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
-
- ///
- /// Version for the currently loaded PepperDashCore dll
- ///
- public static string PepperDashCoreVersion { get; private set; }
-
- private static CTimer _saveTimer;
-
- ///
- /// When true, the IncludedExcludedKeys dict will contain keys to include.
- /// When false (default), IncludedExcludedKeys will contain keys to exclude.
- ///
- private static bool _excludeAllMode;
-
- //static bool ExcludeNoKeyMessages;
-
- private static readonly Dictionary IncludedExcludedKeys;
-
- private static readonly LoggerConfiguration _defaultLoggerConfiguration;
-
- private static LoggerConfiguration _loggerConfiguration;
-
- public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
-
- static Debug()
- {
- CrestronDataStoreStatic.InitCrestronDataStore();
-
- var defaultConsoleLevel = GetStoredLogEventLevel(LevelStoreKey);
-
- var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
-
- var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey);
-
- var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
-
- _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
-
- _websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
-
- _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
-
- _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
-
- _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
-
- var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
- $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
- $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}room{InitialParametersClass.RoomId}{Path.DirectorySeparatorChar}global-log.log";
-
- CrestronConsole.PrintLine($"Saving log files to {logFilePath}");
-
- var errorLogTemplate = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance
- ? "{@t:fff}ms [{@l:u4}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}"
- : "[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}";
-
- _defaultLoggerConfiguration = new LoggerConfiguration()
- .MinimumLevel.Verbose()
- .Enrich.FromLogContext()
- .Enrich.With(new CrestronEnricher())
- .WriteTo.Sink(new DebugConsoleSink(new ExpressionTemplate("[{@t:yyyy-MM-dd HH:mm:ss.fff}][{@l:u4}][{App}]{#if Key is not null}[{Key}]{#end} {@m}{#if @x is not null}\r\n{@x}{#end}")), levelSwitch: _consoleLoggingLevelSwitch)
- .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
- .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
- .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
- rollingInterval: RollingInterval.Day,
- restrictedToMinimumLevel: LogEventLevel.Debug,
- retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
- levelSwitch: _fileLevelSwitch
- );
-
- try
- {
- if (InitialParametersClass.NumberOfRemovableDrives > 0)
- {
- CrestronConsole.PrintLine("{0} RM Drive(s) Present. Initializing CrestronLogger", InitialParametersClass.NumberOfRemovableDrives);
- _defaultLoggerConfiguration.WriteTo.Sink(new DebugCrestronLoggerSink());
- }
- else
- CrestronConsole.PrintLine("No RM Drive(s) Present. Not using Crestron Logger");
- }
- catch (Exception e)
- {
- CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e);
- }
-
- // Instantiate the root logger
- _loggerConfiguration = _defaultLoggerConfiguration;
-
- _logger = _loggerConfiguration.CreateLogger();
- // Get the assembly version and print it to console and the log
- GetVersion();
-
- string msg = $"[App {InitialParametersClass.ApplicationNumber}] Using PepperDash_Core v{PepperDashCoreVersion}";
-
- if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server)
- {
- msg = $"[Room {InitialParametersClass.RoomId}] Using PepperDash_Core v{PepperDashCoreVersion}";
- }
-
- CrestronConsole.PrintLine(msg);
-
- LogMessage(LogEventLevel.Information,msg);
-
- IncludedExcludedKeys = new Dictionary();
-
- if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
- {
- // Add command to console
- CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot",
- "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator);
-
- CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
- "appdebug:P [0-5]: Sets the application's console debug message level",
- ConsoleAccessLevelEnum.AccessOperator);
- CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
- "appdebuglog:P [all] Use \"all\" for full log.",
- ConsoleAccessLevelEnum.AccessOperator);
- CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
- "appdebugclear:P Clears the current custom log",
- ConsoleAccessLevelEnum.AccessOperator);
- CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
- "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
- }
-
- CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
-
- LoadMemory();
-
- var context = _contexts.GetOrCreateItem("DEFAULT");
- Level = context.Level;
- DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot;
-
- if(DoNotLoadConfigOnNextBoot)
- CrestronConsole.PrintLine(string.Format("Program {0} will not load config after next boot. Use console command go:{0} to load the config manually", InitialParametersClass.ApplicationNumber));
-
- _consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) =>
- {
- Console(0, "Console debug level set to {0}", _consoleLoggingLevelSwitch.MinimumLevel);
- };
- }
-
- public static void UpdateLoggerConfiguration(LoggerConfiguration config)
- {
- _loggerConfiguration = config;
-
- _logger = config.CreateLogger();
- }
-
- public static void ResetLoggerConfiguration()
- {
- _loggerConfiguration = _defaultLoggerConfiguration;
-
- _logger = _loggerConfiguration.CreateLogger();
- }
-
- private static LogEventLevel GetStoredLogEventLevel(string levelStoreKey)
- {
- try
- {
- var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel);
-
- if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
- {
- CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
- return LogEventLevel.Information;
- }
-
- if(logLevel < 0 || logLevel > 5)
- {
- CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}");
- return LogEventLevel.Information;
- }
-
- return (LogEventLevel)logLevel;
- } catch (Exception ex)
- {
- CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
- return LogEventLevel.Information;
- }
- }
-
- private static void GetVersion()
- {
- var assembly = Assembly.GetExecutingAssembly();
- var ver =
- assembly
- .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false);
-
- if (ver != null && ver.Length > 0)
- {
- if (ver[0] is AssemblyInformationalVersionAttribute verAttribute)
- {
- PepperDashCoreVersion = verAttribute.InformationalVersion;
- }
- }
- else
- {
- var version = assembly.GetName().Version;
- PepperDashCoreVersion = version.ToString();
- }
- }
-
- ///
- /// Used to save memory when shutting down
- ///
- ///
- static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
- {
-
- if (programEventType == eProgramStatusEventType.Stopping)
- {
- Log.CloseAndFlush();
-
- if (_saveTimer != null)
- {
- _saveTimer.Stop();
- _saveTimer = null;
- }
- Console(0, "Saving debug settings");
- SaveMemory();
- }
- }
-
- ///
- /// Callback for console command
- ///
- ///
- public static void SetDebugFromConsole(string levelString)
- {
- try
- {
- if (levelString.Trim() == "?")
- {
- CrestronConsole.ConsoleCommandResponse(
- $@"Used to set the minimum level of debug messages to be printed to the console:
-{_logLevels[0]} = 0
-{_logLevels[1]} = 1
-{_logLevels[2]} = 2
-{_logLevels[3]} = 3
-{_logLevels[4]} = 4
-{_logLevels[5]} = 5");
- return;
- }
-
- if (string.IsNullOrEmpty(levelString.Trim()))
- {
- CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel);
- return;
- }
-
- if(int.TryParse(levelString, out var levelInt))
- {
- if(levelInt < 0 || levelInt > 5)
- {
- CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
- return;
- }
- SetDebugLevel((uint) levelInt);
- return;
- }
-
- if(Enum.TryParse(levelString, out var levelEnum))
- {
- SetDebugLevel(levelEnum);
- return;
- }
-
- CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
- }
- catch
- {
- CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
- }
- }
-
- ///
- /// Sets the debug level
- ///
- /// Valid values 0-5
- public static void SetDebugLevel(uint level)
- {
- if(!_logLevels.TryGetValue(level, out var logLevel))
- {
- logLevel = LogEventLevel.Information;
-
- CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
-
- SetDebugLevel(logLevel);
- }
-
- SetDebugLevel(logLevel);
- }
-
- public static void SetDebugLevel(LogEventLevel level)
- {
- _consoleLoggingLevelSwitch.MinimumLevel = level;
-
- CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
- InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
-
- CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}");
-
- var err = CrestronDataStoreStatic.SetLocalIntValue(LevelStoreKey, (int) level);
-
- CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}");
-
- if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
- CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
- }
-
- public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
- {
- _websocketLoggingLevelSwitch.MinimumLevel = level;
-
- var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level);
-
- if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
- LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
-
- LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
- }
-
- public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
- {
- _errorLogLevelSwitch.MinimumLevel = level;
-
- var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
-
- if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
- LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
-
- LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
- }
-
- public static void SetFileMinimumDebugLevel(LogEventLevel level)
- {
- _errorLogLevelSwitch.MinimumLevel = level;
-
- var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
-
- if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
- LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
-
- LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
- }
-
- ///
- /// Callback for console command
- ///
- ///
- public static void SetDoNotLoadOnNextBootFromConsole(string stateString)
- {
- try
- {
- if (string.IsNullOrEmpty(stateString.Trim()))
- {
- CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot);
- return;
- }
-
- SetDoNotLoadConfigOnNextBoot(bool.Parse(stateString));
- }
- catch
- {
- CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]");
- }
- }
-
- ///
- /// Callback for console command
- ///
- ///
- public static void SetDebugFilterFromConsole(string items)
- {
- var str = items.Trim();
- if (str == "?")
- {
- CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " +
- "+all: at beginning puts filter into 'default include' mode\r" +
- " All keys that follow will be excluded from output.\r" +
- "-all: at beginning puts filter into 'default exclude all' mode.\r" +
- " All keys that follow will be the only keys that are shown\r" +
- "+nokey: Enables messages with no key (default)\r" +
- "-nokey: Disables messages with no key.\r" +
- "(nokey settings are independent of all other settings)");
- return;
- }
- var keys = Regex.Split(str, @"\s*");
- foreach (var keyToken in keys)
- {
- var lkey = keyToken.ToLower();
- if (lkey == "+all")
- {
- IncludedExcludedKeys.Clear();
- _excludeAllMode = false;
- }
- else if (lkey == "-all")
- {
- IncludedExcludedKeys.Clear();
- _excludeAllMode = true;
- }
- //else if (lkey == "+nokey")
- //{
- // ExcludeNoKeyMessages = false;
- //}
- //else if (lkey == "-nokey")
- //{
- // ExcludeNoKeyMessages = true;
- //}
- else
- {
- string key;
- if (lkey.StartsWith("-"))
- {
- key = lkey.Substring(1);
- // if in exclude all mode, we need to remove this from the inclusions
- if (_excludeAllMode)
- {
- if (IncludedExcludedKeys.ContainsKey(key))
- IncludedExcludedKeys.Remove(key);
- }
- // otherwise include all mode, add to the exclusions
- else
- {
- IncludedExcludedKeys[key] = new object();
- }
- }
- else if (lkey.StartsWith("+"))
- {
- key = lkey.Substring(1);
- // if in exclude all mode, we need to add this as inclusion
- if (_excludeAllMode)
- {
-
- IncludedExcludedKeys[key] = new object();
- }
- // otherwise include all mode, remove this from exclusions
- else
- {
- if (IncludedExcludedKeys.ContainsKey(key))
- IncludedExcludedKeys.Remove(key);
- }
- }
- }
- }
- }
-
-
-
-
- ///
- /// sets the settings for a device or creates a new entry
- ///
- ///
- ///
- ///
- public static void SetDeviceDebugSettings(string deviceKey, object settings)
- {
- _contexts.SetDebugSettingsForKey(deviceKey, settings);
- SaveMemoryOnTimeout();
- }
-
- ///
- /// Gets the device settings for a device by key or returns null
- ///
- ///
- ///
- public static object GetDeviceDebugSettingsForKey(string deviceKey)
- {
- return _contexts.GetDebugSettingsForKey(deviceKey);
- }
-
- ///
- /// Sets the flag to prevent application starting on next boot
- ///
- ///
- public static void SetDoNotLoadConfigOnNextBoot(bool state)
- {
- DoNotLoadConfigOnNextBoot = state;
- _contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state;
- SaveMemoryOnTimeout();
-
- CrestronConsole.ConsoleCommandResponse("[Application {0}], Do Not Load Config on Next Boot set to {1}",
- InitialParametersClass.ApplicationNumber, DoNotLoadConfigOnNextBoot);
- }
-
- ///
- ///
- ///
- public static void ShowDebugLog(string s)
- {
- var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all");
- foreach (var l in loglist)
- CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine);
- }
-
- ///
- /// Log an Exception using Serilog's default Exception logging mechanism
- ///
- /// Exception to log
- /// Message template
- /// Optional IKeyed device. If provided, the Key of the device will be added to the log message
- /// Args to put into message template
- public static void LogMessage(Exception ex, string message, IKeyed device = null, params object[] args)
- {
- using (LogContext.PushProperty("Key", device?.Key))
- {
- _logger.Error(ex, message, args);
- }
- }
-
- ///
- /// Log a message
- ///
- /// Level to log at
- /// Message template
- /// Optional IKeyed device. If provided, the Key of the device will be added to the log message
- /// Args to put into message template
- public static void LogMessage(LogEventLevel level, string message, IKeyed device=null, params object[] args)
- {
- using (LogContext.PushProperty("Key", device?.Key))
- {
- _logger.Write(level, message, args);
- }
- }
-
- public static void LogMessage(LogEventLevel level, string message, params object[] args)
- {
- LogMessage(level, message, null, args);
- }
-
- public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args)
- {
- LogMessage(level, message, keyed, args);
- }
-
-
- private static void LogMessage(uint level, string format, params object[] items)
- {
- if (!_logLevels.ContainsKey(level)) return;
-
- var logLevel = _logLevels[level];
-
- LogMessage(logLevel, format, items);
- }
-
- private static void LogMessage(uint level, IKeyed keyed, string format, params object[] items)
- {
- if (!_logLevels.ContainsKey(level)) return;
-
- var logLevel = _logLevels[level];
-
- LogMessage(logLevel, keyed, format, items);
- }
-
-
- ///
- /// Prints message to console if current debug level is equal to or higher than the level of this message.
- /// Uses CrestronConsole.PrintLine.
- ///
- ///
- /// Console format string
- /// Object parameters
- [Obsolete("Use LogMessage methods")]
- public static void Console(uint level, string format, params object[] items)
- {
-
- LogMessage(level, format, items);
-
- //if (IsRunningOnAppliance)
- //{
- // CrestronConsole.PrintLine("[{0}]App {1} Lvl {2}:{3}", DateTime.Now.ToString("HH:mm:ss.fff"),
- // InitialParametersClass.ApplicationNumber,
- // level,
- // string.Format(format, items));
- //}
- }
-
- ///
- /// Logs to Console when at-level, and all messages to error log, including device key
- ///
- [Obsolete("Use LogMessage methods")]
- public static void Console(uint level, IKeyed dev, string format, params object[] items)
- {
- LogMessage(level, dev, format, items);
-
- //if (Level >= level)
- // Console(level, "[{0}] {1}", dev.Key, message);
- }
-
- ///
- /// Prints message to console if current debug level is equal to or higher than the level of this message. Always sends message to Error Log.
- /// Uses CrestronConsole.PrintLine.
- ///
- [Obsolete("Use LogMessage methods")]
- public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel,
- string format, params object[] items)
- {
- LogMessage(level, dev, format, items);
- }
-
- ///
- /// Logs to Console when at-level, and all messages to error log
- ///
- [Obsolete("Use LogMessage methods")]
- public static void Console(uint level, ErrorLogLevel errorLogLevel,
- string format, params object[] items)
- {
- LogMessage(level, format, items);
- }
-
- ///
- /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
- /// or above the level provided, then the output will be written to both console and the log. Otherwise
- /// it will only be written to the log.
- ///
- [Obsolete("Use LogMessage methods")]
- public static void ConsoleWithLog(uint level, string format, params object[] items)
- {
- LogMessage(level, format, items);
-
- // var str = string.Format(format, items);
- //if (Level >= level)
- // CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
- // CrestronLogger.WriteToLog(str, level);
- }
-
- ///
- /// Logs to both console and the custom user log (not the built-in error log). If appdebug level is set at
- /// or above the level provided, then the output will be written to both console and the log. Otherwise
- /// it will only be written to the log.
- ///
- [Obsolete("Use LogMessage methods")]
- public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items)
- {
- LogMessage(level, dev, format, items);
-
- // var str = string.Format(format, items);
- // CrestronLogger.WriteToLog(string.Format("[{0}] {1}", dev.Key, str), level);
- }
-
- ///
- /// Prints to log and error log
- ///
- ///
- ///
- [Obsolete("Use LogMessage methods")]
- public static void LogError(ErrorLogLevel errorLogLevel, string str)
- {
- switch (errorLogLevel)
- {
- case ErrorLogLevel.Error:
- LogMessage(LogEventLevel.Error, str);
- break;
- case ErrorLogLevel.Warning:
- LogMessage(LogEventLevel.Warning, str);
- break;
- case ErrorLogLevel.Notice:
- LogMessage(LogEventLevel.Information, str);
- break;
- }
- }
-
- ///
- /// Writes the memory object after timeout
- ///
- static void SaveMemoryOnTimeout()
- {
- Console(0, "Saving debug settings");
- if (_saveTimer == null)
- _saveTimer = new CTimer(o =>
- {
- _saveTimer = null;
- SaveMemory();
- }, SaveTimeoutMs);
- else
- _saveTimer.Reset(SaveTimeoutMs);
- }
-
- ///
- /// Writes the memory - use SaveMemoryOnTimeout
- ///
- static void SaveMemory()
- {
- //var dir = @"\NVRAM\debug";
- //if (!Directory.Exists(dir))
- // Directory.Create(dir);
-
- var fileName = GetMemoryFileName();
-
- Console(0, ErrorLogLevel.Notice, "Loading debug settings file from {0}", fileName);
-
- using (var sw = new StreamWriter(fileName))
- {
- var json = JsonConvert.SerializeObject(_contexts);
- sw.Write(json);
- sw.Flush();
- }
- }
-
- ///
- ///
- ///
- static void LoadMemory()
- {
- var file = GetMemoryFileName();
- if (File.Exists(file))
- {
- using (var sr = new StreamReader(file))
- {
- var json = sr.ReadToEnd();
- _contexts = JsonConvert.DeserializeObject(json);
-
- if (_contexts != null)
- {
- Console(1, "Debug memory restored from file");
- return;
- }
- }
- }
-
- _contexts = new DebugContextCollection();
- }
-
- ///
- /// Helper to get the file path for this app's debug memory
- ///
- static string GetMemoryFileName()
- {
- if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance)
- {
- // CheckForMigration();
- return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
- }
-
- return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId);
- }
-
- private static void CheckForMigration()
- {
- var oldFilePath = String.Format(@"\nvram\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
- var newFilePath = String.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber);
-
- //check for file at old path
- if (!File.Exists(oldFilePath))
- {
- Console(0, ErrorLogLevel.Notice,
- String.Format(
- @"Debug settings file migration not necessary. Using file at \user\debugSettings\program{0}",
- InitialParametersClass.ApplicationNumber));
-
- return;
- }
-
- //create the new directory if it doesn't already exist
- if (!Directory.Exists(@"\user\debugSettings"))
- {
- Directory.CreateDirectory(@"\user\debugSettings");
- }
-
- Console(0, ErrorLogLevel.Notice,
- String.Format(
- @"File found at \nvram\debugSettings\program{0}. Migrating to \user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber));
-
- //Copy file from old path to new path, then delete it. This will overwrite the existing file
- File.Copy(oldFilePath, newFilePath, true);
- File.Delete(oldFilePath);
-
- //Check if the old directory is empty, then delete it if it is
- if (Directory.GetFiles(@"\nvram\debugSettings").Length > 0)
- {
- return;
- }
-
- Directory.Delete(@"\nvram\debugSettings");
- }
-
- ///
- /// Error level to for message to be logged at
- ///
- public enum ErrorLogLevel
- {
- ///
- /// Error
- ///
- Error,
- ///
- /// Warning
- ///
- Warning,
- ///
- /// Notice
- ///
- Notice,
- ///
- /// None
- ///
- None,
- }
- }
-}
\ No newline at end of file
diff --git a/src/Pepperdash.Core/Logging/DebugClient.cs b/src/Pepperdash.Core/Logging/DebugClient.cs
new file mode 100644
index 0000000..5dcddef
--- /dev/null
+++ b/src/Pepperdash.Core/Logging/DebugClient.cs
@@ -0,0 +1,62 @@
+#if NET472
+using System;
+using WebSocketSharp;
+using WebSocketSharp.Server;
+
+namespace PepperDash.Core.Logging
+{
+ public class DebugClient : WebSocketBehavior
+ {
+ private DateTime connectionTime;
+
+ public TimeSpan ConnectedDuration
+ {
+ get
+ {
+ if (Context.WebSocket.IsAlive)
+ {
+ return DateTime.Now - connectionTime;
+ }
+
+ return new TimeSpan(0);
+ }
+ }
+
+ public DebugClient()
+ {
+ Debug.Console(0, "DebugClient Created");
+ }
+
+ protected override void OnOpen()
+ {
+ base.OnOpen();
+
+ var url = Context.WebSocket.Url;
+ Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
+
+ connectionTime = DateTime.Now;
+ }
+
+ protected override void OnMessage(MessageEventArgs e)
+ {
+ base.OnMessage(e);
+
+ Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
+ }
+
+ protected override void OnClose(CloseEventArgs e)
+ {
+ base.OnClose(e);
+
+ Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
+ }
+
+ protected override void OnError(WebSocketSharp.ErrorEventArgs e)
+ {
+ base.OnError(e);
+
+ Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Pepperdash.Core/Logging/DebugConsoleSink.cs b/src/Pepperdash.Core/Logging/DebugConsoleSink.cs
index a6c7f89..ff8d61f 100644
--- a/src/Pepperdash.Core/Logging/DebugConsoleSink.cs
+++ b/src/Pepperdash.Core/Logging/DebugConsoleSink.cs
@@ -1,23 +1,21 @@
-using Crestron.SimplSharp;
-using Serilog.Configuration;
+using System.IO;
+using System.Text;
+using Crestron.SimplSharp;
using Serilog;
+using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Formatting.Json;
-using System.IO;
-using System.Text;
-
-namespace PepperDash.Core
+namespace PepperDash.Core.Logging
{
public class DebugConsoleSink : ILogEventSink
{
- private readonly ITextFormatter _textFormatter;
+ private readonly ITextFormatter textFormatter;
public void Emit(LogEvent logEvent)
{
- if (!Debug.IsRunningOnAppliance) return;
/*string message = $"[{logEvent.Timestamp}][{logEvent.Level}][App {InitialParametersClass.ApplicationNumber}]{logEvent.RenderMessage()}";
@@ -28,28 +26,44 @@ namespace PepperDash.Core
var buffer = new StringWriter(new StringBuilder(256));
- _textFormatter.Format(logEvent, buffer);
+ textFormatter.Format(logEvent, buffer);
var message = buffer.ToString();
CrestronConsole.PrintLine(message);
}
- public DebugConsoleSink(ITextFormatter formatProvider )
+ public DebugConsoleSink(ITextFormatter formatProvider)
{
- _textFormatter = formatProvider ?? new JsonFormatter();
+ textFormatter = formatProvider ?? new JsonFormatter();
}
-
}
public static class DebugConsoleSinkExtensions
{
public static LoggerConfiguration DebugConsoleSink(
- this LoggerSinkConfiguration loggerConfiguration,
- ITextFormatter formatProvider = null)
+ this LoggerSinkConfiguration loggerConfiguration,
+ ITextFormatter formatProvider = null,
+ LoggingLevelSwitch levelSwitch = null)
{
- return loggerConfiguration.Sink(new DebugConsoleSink(formatProvider));
+ var sink = new DebugConsoleSink(formatProvider);
+ return loggerConfiguration.Conditional(Predicate, c => c.Sink(sink, levelSwitch: levelSwitch));
+
+ static bool Predicate(LogEvent @event)
+ {
+ if (!Debug.IsRunningOnAppliance)
+ {
+ return false;
+ }
+
+ if (@event.Properties.TryGetValue("Key", out var value) &&
+ value is ScalarValue { Value: string rawValue })
+ {
+ return DebugContext.DeviceExistsInContext(Debug.ConsoleLevelStoreKey, rawValue);
+ }
+
+ return true;
+ }
}
}
-
}
diff --git a/src/Pepperdash.Core/Logging/DebugContext.cs b/src/Pepperdash.Core/Logging/DebugContext.cs
index 54c8741..8f9d98f 100644
--- a/src/Pepperdash.Core/Logging/DebugContext.cs
+++ b/src/Pepperdash.Core/Logging/DebugContext.cs
@@ -1,281 +1,107 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using Crestron.SimplSharp;
-using Crestron.SimplSharp.CrestronIO;
using Newtonsoft.Json;
+using Serilog;
+using Serilog.Events;
+using WebSocketSharp;
-
-namespace PepperDash.Core
+namespace PepperDash.Core.Logging
{
///
/// Represents a debugging context
///
- public class DebugContext
+ public static class DebugContext
{
- ///
- /// Describes the folder location where a given program stores it's debug level memory. By default, the
- /// file written will be named appNdebug where N is 1-10.
- ///
- public string Key { get; private set; }
+ private static readonly CTimer SaveTimer;
+ private static readonly Dictionary CurrentData;
- /////
- ///// The name of the file containing the current debug settings.
- /////
- //string FileName = string.Format(@"\nvram\debug\app{0}Debug.json", InitialParametersClass.ApplicationNumber);
-
- DebugContextSaveData SaveData;
-
- int SaveTimeoutMs = 30000;
-
- CTimer SaveTimer;
-
-
- static List Contexts = new List();
+ public static readonly string ApplianceFilePath = Path.Combine("user", "debug", $"app-{InitialParametersClass.ApplicationNumber}-Debug.json");
+ public static readonly string ServerFilePath = Path.Combine("User", "debug", $"app-{InitialParametersClass.RoomId}-Debug.json");
///
- /// Creates or gets a debug context
+ /// The name of the file containing the current debug settings.
///
- ///
- ///
- public static DebugContext GetDebugContext(string key)
+ public static readonly string FileName = CrestronEnvironment.DevicePlatform switch
{
- var context = Contexts.FirstOrDefault(c => c.Key.Equals(key, StringComparison.OrdinalIgnoreCase));
- if (context == null)
- {
- context = new DebugContext(key);
- Contexts.Add(context);
- }
- return context;
- }
+ eDevicePlatform.Appliance => ApplianceFilePath,
+ eDevicePlatform.Server => ServerFilePath,
+ _ => string.Empty
+ };
- ///
- /// Do not use. For S+ access.
- ///
- public DebugContext() { }
-
- DebugContext(string key)
+ static DebugContext()
{
- Key = key;
- if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro)
+ CurrentData = LoadData();
+ SaveTimer = new CTimer(_ => SaveData(), Timeout.Infinite);
+
+ CrestronEnvironment.ProgramStatusEventHandler += args =>
{
- // Add command to console
- CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
- "appdebug:P [0-2]: Sets the application's console debug message level",
- ConsoleAccessLevelEnum.AccessOperator);
- }
-
- CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
-
- LoadMemory();
- }
-
- ///
- /// Used to save memory when shutting down
- ///
- ///
- void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType)
- {
- if (programEventType == eProgramStatusEventType.Stopping)
- {
- if (SaveTimer != null)
+ if (args == eProgramStatusEventType.Stopping)
{
- SaveTimer.Stop();
- SaveTimer = null;
+ using (SaveTimer)
+ {
+ SaveData();
+ SaveTimer.Stop();
+ }
}
- Console(0, "Saving debug settings");
- SaveMemory();
- }
+ };
}
- ///
- /// Callback for console command
- ///
- ///
- public void SetDebugFromConsole(string levelString)
+ public static DebugContextData GetDataForKey(string key, LogEventLevel defaultLevel) =>
+ CurrentData.TryGetValue(key, out var data) ? data : new DebugContextData(defaultLevel);
+
+ public static bool DeviceExistsInContext(string contextKey, string deviceKey) =>
+ CurrentData.TryGetValue(contextKey, out var data) switch
+ {
+ true when data.Devices != null => data.Devices.Any(key => string.Equals(key, deviceKey, StringComparison.OrdinalIgnoreCase)),
+ _ => false
+ };
+
+ public static void SetDataForKey(string key, LogEventLevel defaultLevel, string[] devices = null)
{
+ if (CurrentData.ContainsKey(key))
+ {
+ CurrentData[key] = new DebugContextData(defaultLevel, devices);
+ }
+ else
+ {
+ CurrentData.Add(key, new DebugContextData(defaultLevel, devices));
+ }
+
+ SaveTimer.Reset(5000);
+ }
+
+ private static void SaveData()
+ {
+ Log.Information("Saving debug data to file:{File}", FileName);
+
try
{
- if (string.IsNullOrEmpty(levelString.Trim()))
- {
- CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", SaveData.Level);
- return;
- }
-
- SetDebugLevel(Convert.ToInt32(levelString));
+ var json = JsonConvert.SerializeObject(CurrentData);
+ File.WriteAllText(FileName, json);
}
- catch
+ catch (Exception e)
{
- CrestronConsole.PrintLine("Usage: appdebug:P [0-2]");
+ Log.Error(e, "Failed to save debug data");
}
}
- ///
- /// Sets the debug level
- ///
- /// Valid values 0 (no debug), 1 (critical), 2 (all messages)
- public void SetDebugLevel(int level)
+ private static Dictionary LoadData()
{
- if (level <= 2)
+ if (!File.Exists(FileName))
{
- SaveData.Level = level;
- SaveMemoryOnTimeout();
-
- CrestronConsole.PrintLine("[Application {0}], Debug level set to {1}",
- InitialParametersClass.ApplicationNumber, SaveData.Level);
+ return new Dictionary();
}
- }
- ///
- /// Prints message to console if current debug level is equal to or higher than the level of this message.
- /// Uses CrestronConsole.PrintLine.
- ///
- ///
- /// Console format string
- /// Object parameters
- public void Console(uint level, string format, params object[] items)
- {
- if (SaveData.Level >= level)
- CrestronConsole.PrintLine("App {0}:{1}", InitialParametersClass.ApplicationNumber,
- string.Format(format, items));
- }
-
- ///
- /// Appends a device Key to the beginning of a message
- ///
- public void Console(uint level, IKeyed dev, string format, params object[] items)
- {
- if (SaveData.Level >= level)
- Console(level, "[{0}] {1}", dev.Key, string.Format(format, items));
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public void Console(uint level, IKeyed dev, Debug.ErrorLogLevel errorLogLevel,
- string format, params object[] items)
- {
- if (SaveData.Level >= level)
- {
- var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items));
- Console(level, str);
- LogError(errorLogLevel, str);
- }
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public void Console(uint level, Debug.ErrorLogLevel errorLogLevel,
- string format, params object[] items)
- {
- if (SaveData.Level >= level)
- {
- var str = string.Format(format, items);
- Console(level, str);
- LogError(errorLogLevel, str);
- }
- }
-
- ///
- ///
- ///
- ///
- ///
- public void LogError(Debug.ErrorLogLevel errorLogLevel, string str)
- {
- string msg = string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str);
- switch (errorLogLevel)
- {
- case Debug.ErrorLogLevel.Error:
- ErrorLog.Error(msg);
- break;
- case Debug.ErrorLogLevel.Warning:
- ErrorLog.Warn(msg);
- break;
- case Debug.ErrorLogLevel.Notice:
- ErrorLog.Notice(msg);
- break;
- }
- }
-
- ///
- /// Writes the memory object after timeout
- ///
- void SaveMemoryOnTimeout()
- {
- if (SaveTimer == null)
- SaveTimer = new CTimer(o =>
- {
- SaveTimer = null;
- SaveMemory();
- }, SaveTimeoutMs);
- else
- SaveTimer.Reset(SaveTimeoutMs);
- }
-
- ///
- /// Writes the memory - use SaveMemoryOnTimeout
- ///
- void SaveMemory()
- {
- using (StreamWriter sw = new StreamWriter(GetMemoryFileName()))
- {
- var json = JsonConvert.SerializeObject(SaveData);
- sw.Write(json);
- sw.Flush();
- }
- }
-
- ///
- ///
- ///
- void LoadMemory()
- {
- var file = GetMemoryFileName();
- if (File.Exists(file))
- {
- using (StreamReader sr = new StreamReader(file))
- {
- var data = JsonConvert.DeserializeObject(sr.ReadToEnd());
- if (data != null)
- {
- SaveData = data;
- Debug.Console(1, "Debug memory restored from file");
- return;
- }
- else
- SaveData = new DebugContextSaveData();
- }
- }
- }
-
- ///
- /// Helper to get the file path for this app's debug memory
- ///
- string GetMemoryFileName()
- {
- return string.Format(@"\NVRAM\debugSettings\program{0}-{1}", InitialParametersClass.ApplicationNumber, Key);
+ var json = File.ReadAllText(FileName);
+ return JsonConvert.DeserializeObject>(json);
}
}
///
///
///
- public class DebugContextSaveData
- {
- ///
- ///
- ///
- public int Level { get; set; }
- }
+ public record DebugContextData(LogEventLevel Level, string[] Devices = null);
}
\ No newline at end of file
diff --git a/src/Pepperdash.Core/Logging/DebugErrorLogSink.cs b/src/Pepperdash.Core/Logging/DebugErrorLogSink.cs
index 3885982..d3a0c99 100644
--- a/src/Pepperdash.Core/Logging/DebugErrorLogSink.cs
+++ b/src/Pepperdash.Core/Logging/DebugErrorLogSink.cs
@@ -5,9 +5,7 @@ using Serilog.Formatting;
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text;
-using System.Threading.Tasks;
namespace PepperDash.Core.Logging
{
@@ -24,6 +22,7 @@ namespace PepperDash.Core.Logging
{LogEventLevel.Error, (msg) => ErrorLog.Error(msg) },
{LogEventLevel.Fatal, (msg) => ErrorLog.Error(msg) }
};
+
public void Emit(LogEvent logEvent)
{
string message;
diff --git a/src/Pepperdash.Core/Logging/DebugExtensions.cs b/src/Pepperdash.Core/Logging/DebugExtensions.cs
index e37e6d9..46c8d8e 100644
--- a/src/Pepperdash.Core/Logging/DebugExtensions.cs
+++ b/src/Pepperdash.Core/Logging/DebugExtensions.cs
@@ -1,4 +1,5 @@
-using Serilog;
+using System;
+using Serilog;
using Serilog.Events;
using Log = PepperDash.Core.Debug;
@@ -30,10 +31,5 @@ namespace PepperDash.Core.Logging
{
Log.LogMessage(LogEventLevel.Error, device, message, args);
}
-
- public static void LogFatal(this IKeyed device, string message, params object[] args)
- {
- Log.LogMessage(LogEventLevel.Fatal, device, message, args);
- }
}
}
diff --git a/src/Pepperdash.Core/Logging/DebugMemory.cs b/src/Pepperdash.Core/Logging/DebugMemory.cs
deleted file mode 100644
index a5737af..0000000
--- a/src/Pepperdash.Core/Logging/DebugMemory.cs
+++ /dev/null
@@ -1,115 +0,0 @@
-using System.Collections.Generic;
-using Crestron.SimplSharp;
-using Newtonsoft.Json;
-
-namespace PepperDash.Core.Logging
-{
- ///
- /// Class to persist current Debug settings across program restarts
- ///
- public class DebugContextCollection
- {
- ///
- /// To prevent threading issues with the DeviceDebugSettings collection
- ///
- private readonly CCriticalSection _deviceDebugSettingsLock;
-
- [JsonProperty("items")] private readonly Dictionary _items;
-
- ///
- /// Collection of the debug settings for each device where the dictionary key is the device key
- ///
- [JsonProperty("deviceDebugSettings")]
- private Dictionary DeviceDebugSettings { get; set; }
-
-
- ///
- /// Default constructor
- ///
- public DebugContextCollection()
- {
- _deviceDebugSettingsLock = new CCriticalSection();
- DeviceDebugSettings = new Dictionary();
- _items = new Dictionary();
- }
-
- ///
- /// Sets the level of a given context item, and adds that item if it does not
- /// exist
- ///
- ///
- ///
- public void SetLevel(string contextKey, int level)
- {
- if (level < 0 || level > 2)
- return;
- GetOrCreateItem(contextKey).Level = level;
- }
-
- ///
- /// Gets a level or creates it if not existing
- ///
- ///
- ///
- public DebugContextItem GetOrCreateItem(string contextKey)
- {
- if (!_items.ContainsKey(contextKey))
- _items[contextKey] = new DebugContextItem { Level = 0 };
- return _items[contextKey];
- }
-
-
- ///
- /// sets the settings for a device or creates a new entry
- ///
- ///
- ///
- ///
- public void SetDebugSettingsForKey(string deviceKey, object settings)
- {
- try
- {
- _deviceDebugSettingsLock.Enter();
-
- if (DeviceDebugSettings.ContainsKey(deviceKey))
- {
- DeviceDebugSettings[deviceKey] = settings;
- }
- else
- DeviceDebugSettings.Add(deviceKey, settings);
- }
- finally
- {
- _deviceDebugSettingsLock.Leave();
- }
- }
-
- ///
- /// Gets the device settings for a device by key or returns null
- ///
- ///
- ///
- public object GetDebugSettingsForKey(string deviceKey)
- {
- return DeviceDebugSettings[deviceKey];
- }
- }
-
- ///
- /// Contains information about
- ///
- public class DebugContextItem
- {
- ///
- /// The level of debug messages to print
- ///
- [JsonProperty("level")]
- public int Level { get; set; }
-
- ///
- /// Property to tell the program not to intitialize when it boots, if desired
- ///
- [JsonProperty("doNotLoadOnNextBoot")]
- public bool DoNotLoadOnNextBoot { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Pepperdash.Core/Logging/DebugNet472WebSocket.cs b/src/Pepperdash.Core/Logging/DebugNet472WebSocket.cs
new file mode 100644
index 0000000..e211ffa
--- /dev/null
+++ b/src/Pepperdash.Core/Logging/DebugNet472WebSocket.cs
@@ -0,0 +1,72 @@
+#if NET472
+
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using WebSocketSharp.Net;
+using WebSocketSharp.Server;
+
+namespace PepperDash.Core.Logging
+{
+ internal class DebugNet472WebSocket : DebugWebSocket
+ {
+ private const string Path = "/debug/join/";
+
+ private readonly WebSocketServer server;
+
+ public DebugNet472WebSocket(string certPath = "") : base(certPath)
+ {
+ server = new WebSocketServer(Port, IsSecure);
+
+ if (IsSecure)
+ {
+ server.SslConfiguration = new ServerSslConfiguration(new X509Certificate2(certPath, CertificatePassword))
+ {
+ ClientCertificateRequired = false,
+ CheckCertificateRevocation = false,
+ EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
+
+ ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
+ {
+ Debug.LogInformation("HTTPS ClientCertificateValidation Callback triggered");
+ return true;
+ }
+ };
+ }
+
+ server.AddWebSocketService(Path);
+ server.Start();
+ }
+
+ public override bool IsListening => server.IsListening;
+
+ public override void Broadcast(string message) => server.WebSocketServices.Broadcast(message);
+ }
+}
+#else
+
+using System;
+using System.Net;
+
+namespace PepperDash.Core.Logging
+{
+ internal class DebugNetWebSocket : DebugWebSocket
+ {
+ private const string Path = "/debug/join/";
+
+ private readonly HttpListener server = new();
+
+ public DebugNetWebSocket(int port, string certPath = "") : base(certPath)
+ {
+ server.AuthenticationSchemeSelectorDelegate = (request) => AuthenticationSchemes.Anonymous;
+ server.Prefixes.Add("wss://*:" + port + Path);
+ }
+
+ public override bool IsListening => server.IsListening;
+
+ public override void Broadcast(string message) => throw new NotImplementedException();
+ }
+}
+
+#endif
+
+
diff --git a/src/Pepperdash.Core/Logging/DebugWebSocket.cs b/src/Pepperdash.Core/Logging/DebugWebSocket.cs
new file mode 100644
index 0000000..311e0c9
--- /dev/null
+++ b/src/Pepperdash.Core/Logging/DebugWebSocket.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using Crestron.SimplSharp;
+using Org.BouncyCastle.Asn1.X509;
+
+namespace PepperDash.Core.Logging
+{
+ public abstract class DebugWebSocket
+ {
+ public const string CertificateName = "selfCres";
+ public const string CertificatePassword = "cres12345";
+
+ protected DebugWebSocket(string certPath = "")
+ {
+ IsSecure = !string.IsNullOrEmpty(certPath);
+
+ if (!IsSecure)
+ {
+ return;
+ }
+
+ if (!File.Exists(certPath))
+ {
+ CreateCert(certPath);
+ }
+ }
+
+ protected bool IsSecure { get; }
+
+ private static void CreateCert(string filePath)
+ {
+ try
+ {
+ var utility = new BouncyCertificate();
+ var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
+ var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
+ var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
+ var certificate = utility.CreateSelfSignedCertificate($"CN={hostName}.{domainName}", [$"{hostName}.{domainName}", ipAddress], new[] { KeyPurposeID.IdKPServerAuth, KeyPurposeID.IdKPClientAuth });
+
+ utility.CertificatePassword = CertificatePassword;
+ utility.WriteCertificate(certificate, filePath, CertificateName);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError(ex, "WSS failed to create cert");
+ }
+ }
+
+ public abstract bool IsListening { get; }
+
+ public abstract void Broadcast(string message);
+
+ public int Port { get; } = new Random().Next(65435, 65535);
+ }
+}
diff --git a/src/Pepperdash.Core/Logging/DebugWebsocketSink.cs b/src/Pepperdash.Core/Logging/DebugWebsocketSink.cs
index f24a585..f138790 100644
--- a/src/Pepperdash.Core/Logging/DebugWebsocketSink.cs
+++ b/src/Pepperdash.Core/Logging/DebugWebsocketSink.cs
@@ -1,271 +1,40 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.IO;
using Serilog;
+using Serilog.Configuration;
using Serilog.Core;
using Serilog.Events;
-using Serilog.Configuration;
-using WebSocketSharp.Server;
-using Crestron.SimplSharp;
-using WebSocketSharp;
-using System.Security.Authentication;
-using WebSocketSharp.Net;
-using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
-using System.IO;
-using Org.BouncyCastle.Asn1.X509;
using Serilog.Formatting;
-using Newtonsoft.Json.Linq;
using Serilog.Formatting.Json;
-namespace PepperDash.Core
+namespace PepperDash.Core.Logging
{
public class DebugWebsocketSink : ILogEventSink
{
- private HttpServer _httpsServer;
-
- private string _path = "/debug/join/";
- private const string _certificateName = "selfCres";
- private const string _certificatePassword = "cres12345";
+ private readonly DebugWebSocket webSocket;
+ private readonly ITextFormatter textFormatter;
- public int Port
- { get
- {
-
- if(_httpsServer == null) return 0;
- return _httpsServer.Port;
- }
- }
-
- public string Url
+ public DebugWebsocketSink(DebugWebSocket webSocket, ITextFormatter formatProvider = null)
{
- get
- {
- if (_httpsServer == null) return "";
- return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{_httpsServer.WebSocketServices[_path].Path}";
- }
+ this.webSocket = webSocket;
+ textFormatter = formatProvider ?? new JsonFormatter();
}
- public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
-
-
- private readonly ITextFormatter _textFormatter;
-
- public DebugWebsocketSink(ITextFormatter formatProvider)
- {
-
- _textFormatter = formatProvider ?? new JsonFormatter();
-
- if (!File.Exists($"\\user\\{_certificateName}.pfx"))
- CreateCert(null);
-
- CrestronEnvironment.ProgramStatusEventHandler += type =>
- {
- if (type == eProgramStatusEventType.Stopping)
- {
- StopServer();
- }
- };
- }
-
- private void CreateCert(string[] args)
- {
- try
- {
- //Debug.Console(0,"CreateCert Creating Utility");
- CrestronConsole.PrintLine("CreateCert Creating Utility");
- //var utility = new CertificateUtility();
- var utility = new BouncyCertificate();
- //Debug.Console(0, "CreateCert Calling CreateCert");
- CrestronConsole.PrintLine("CreateCert Calling CreateCert");
- //utility.CreateCert();
- var ipAddress = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0);
- var hostName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_HOSTNAME, 0);
- var domainName = CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_DOMAIN_NAME, 0);
-
- //Debug.Console(0, "DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress);
- CrestronConsole.PrintLine(string.Format("DomainName: {0} | HostName: {1} | {1}.{0}@{2}", domainName, hostName, ipAddress));
-
- var certificate = utility.CreateSelfSignedCertificate(string.Format("CN={0}.{1}", hostName, domainName), new[] { string.Format("{0}.{1}", hostName, domainName), ipAddress }, new[] { KeyPurposeID.IdKPServerAuth, KeyPurposeID.IdKPClientAuth });
- //Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested
- //Debug.Print($"CreateCert Storing Certificate To My.LocalMachine");
- //utility.AddCertToStore(certificate, StoreName.My, StoreLocation.LocalMachine);
- //Debug.Console(0, "CreateCert Saving Cert to \\user\\");
- CrestronConsole.PrintLine("CreateCert Saving Cert to \\user\\");
- utility.CertificatePassword = _certificatePassword;
- utility.WriteCertificate(certificate, @"\user\", _certificateName);
- //Debug.Console(0, "CreateCert Ending CreateCert");
- CrestronConsole.PrintLine("CreateCert Ending CreateCert");
- }
- catch (Exception ex)
- {
- //Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
- CrestronConsole.PrintLine(string.Format("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace));
- }
- }
public void Emit(LogEvent logEvent)
{
- if (_httpsServer == null || !_httpsServer.IsListening) return;
+ if (!webSocket.IsListening) return;
- var sw = new StringWriter();
- _textFormatter.Format(logEvent, sw);
-
- _httpsServer.WebSocketServices.Broadcast(sw.ToString());
-
- }
-
- public void StartServerAndSetPort(int port)
- {
- Debug.Console(0, "Starting Websocket Server on port: {0}", port);
-
-
- Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
- }
-
- private void Start(int port, string certPath = "", string certPassword = "")
- {
- try
- {
- _httpsServer = new HttpServer(port, true);
-
-
- if (!string.IsNullOrWhiteSpace(certPath))
- {
- Debug.Console(0, "Assigning SSL Configuration");
- _httpsServer.SslConfiguration = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword))
- {
- ClientCertificateRequired = false,
- CheckCertificateRevocation = false,
- EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
- //this is just to test, you might want to actually validate
- ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
- {
- Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
- return true;
- }
- };
- }
- Debug.Console(0, "Adding Debug Client Service");
- _httpsServer.AddWebSocketService(_path);
- Debug.Console(0, "Assigning Log Info");
- _httpsServer.Log.Level = LogLevel.Trace;
- _httpsServer.Log.Output = (d, s) =>
- {
- uint level;
-
- switch(d.Level)
- {
- case WebSocketSharp.LogLevel.Fatal:
- level = 3;
- break;
- case WebSocketSharp.LogLevel.Error:
- level = 2;
- break;
- case WebSocketSharp.LogLevel.Warn:
- level = 1;
- break;
- case WebSocketSharp.LogLevel.Info:
- level = 0;
- break;
- case WebSocketSharp.LogLevel.Debug:
- level = 4;
- break;
- case WebSocketSharp.LogLevel.Trace:
- level = 5;
- break;
- default:
- level = 4;
- break;
- }
-
- Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
- };
- Debug.Console(0, "Starting");
-
- _httpsServer.Start();
- Debug.Console(0, "Ready");
- }
- catch (Exception ex)
- {
- Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
- }
- }
-
- public void StopServer()
- {
- Debug.Console(0, "Stopping Websocket Server");
- _httpsServer?.Stop();
-
- _httpsServer = null;
+ using var sw = new StringWriter();
+ textFormatter.Format(logEvent, sw);
+ webSocket.Broadcast(sw.ToString());
}
}
- public static class DebugWebsocketSinkExtensions
+ public class NoopSink : ILogEventSink
{
- public static LoggerConfiguration DebugWebsocketSink(
- this LoggerSinkConfiguration loggerConfiguration,
- ITextFormatter formatProvider = null)
+ public static readonly NoopSink Instance = new NoopSink();
+ public void Emit(LogEvent logEvent)
{
- return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider));
- }
- }
-
- public class DebugClient : WebSocketBehavior
- {
- private DateTime _connectionTime;
-
- public TimeSpan ConnectedDuration
- {
- get
- {
- if (Context.WebSocket.IsAlive)
- {
- return DateTime.Now - _connectionTime;
- }
- else
- {
- return new TimeSpan(0);
- }
- }
- }
-
- public DebugClient()
- {
- Debug.Console(0, "DebugClient Created");
- }
-
- protected override void OnOpen()
- {
- base.OnOpen();
-
- var url = Context.WebSocket.Url;
- Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
-
- _connectionTime = DateTime.Now;
- }
-
- protected override void OnMessage(MessageEventArgs e)
- {
- base.OnMessage(e);
-
- Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
- }
-
- protected override void OnClose(CloseEventArgs e)
- {
- base.OnClose(e);
-
- Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
-
- }
-
- protected override void OnError(WebSocketSharp.ErrorEventArgs e)
- {
- base.OnError(e);
-
- Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
}
}
}
diff --git a/src/Pepperdash.Core/PepperDash.Core.csproj b/src/Pepperdash.Core/PepperDash.Core.csproj
index c257ad1..fc5d431 100644
--- a/src/Pepperdash.Core/PepperDash.Core.csproj
+++ b/src/Pepperdash.Core/PepperDash.Core.csproj
@@ -3,6 +3,7 @@
PepperDash.Core
PepperDashCore
net472;net6.0;net8.0
+ latest
true
en
bin\$(Configuration)\
@@ -40,6 +41,10 @@
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+