From 76759d35cc679c44de0a6a4ad2b4bb349b76d050 Mon Sep 17 00:00:00 2001 From: Andrew Welker Date: Fri, 4 Jul 2025 16:03:00 -0500 Subject: [PATCH] feat: remove context file for storing values --- src/PepperDash.Core/Logging/Debug.cs | 1699 ++++++++--------- .../Logging/DebugWebsocketSink.cs | 576 +++--- 2 files changed, 1030 insertions(+), 1245 deletions(-) diff --git a/src/PepperDash.Core/Logging/Debug.cs b/src/PepperDash.Core/Logging/Debug.cs index 6e4fb37d..b3e7452a 100644 --- a/src/PepperDash.Core/Logging/Debug.cs +++ b/src/PepperDash.Core/Logging/Debug.cs @@ -22,95 +22,88 @@ using Serilog.Templates; namespace PepperDash.Core; - /// - /// - public static class Debug +/// +/// 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 string DoNotLoadOnNextBootKey = "DoNotLoadOnNextBoot"; + + private static readonly Dictionary _logLevels = new Dictionary() { - private static readonly string LevelStoreKey = "ConsoleDebugLevel"; - private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel"; - private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel"; - private static readonly string FileLevelStoreKey = "FileDebugLevel"; + {0, LogEventLevel.Information }, + {3, LogEventLevel.Warning }, + {4, LogEventLevel.Error }, + {5, LogEventLevel.Fatal }, + {1, LogEventLevel.Debug }, + {2, LogEventLevel.Verbose }, + }; - 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 ILogger _logger; + private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch; - private static readonly LoggingLevelSwitch _consoleLogLevelSwitch; + private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch; - private static readonly LoggingLevelSwitch _websocketLogLevelSwitch; + private static readonly LoggingLevelSwitch _errorLogLevelSwitch; - private static readonly LoggingLevelSwitch _errorLogLevelSwitch; + private static readonly LoggingLevelSwitch _fileLevelSwitch; - private static readonly LoggingLevelSwitch _fileLogLevelSwitch; + public static LogEventLevel WebsocketMinimumLogLevel + { + get { return _websocketLoggingLevelSwitch.MinimumLevel; } + } - /// - /// Gets the minimum log level for the websocket sink. - /// - public static LogEventLevel WebsocketMinimumLogLevel - { - get { return _websocketLogLevelSwitch.MinimumLevel; } - } + private static readonly DebugWebsocketSink _websocketSink; - private static readonly DebugWebsocketSink _websocketSink; + public static DebugWebsocketSink WebsocketSink + { + get { return _websocketSink; } + } - /// - /// Gets the websocket sink for debug logging. - /// - 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 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\"; - /// - /// 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); - /// - /// 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; } - /// - /// Gets or sets the Level - /// - 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; } - /// - /// Gets or sets the DoNotLoadConfigOnNextBoot - /// - public static bool DoNotLoadConfigOnNextBoot { get; private set; } + private static DebugContextCollection _contexts; - private static DebugContextCollection _contexts; + private const int SaveTimeoutMs = 30000; - private const int SaveTimeoutMs = 30000; + public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; - /// - /// Indicates whether the system is running on an appliance. - /// - public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; + /// + /// Version for the currently loaded PepperDashCore dll + /// + public static string PepperDashCoreVersion { get; private set; } - /// - /// Gets or sets the PepperDashCoreVersion - /// - public static string PepperDashCoreVersion { get; private set; } - - private static CTimer _saveTimer; + private static CTimer _saveTimer; /// /// When true, the IncludedExcludedKeys dict will contain keys to include. @@ -120,16 +113,15 @@ namespace PepperDash.Core; private static readonly Dictionary IncludedExcludedKeys; - private static readonly LoggerConfiguration _defaultLoggerConfiguration; + private static readonly LoggerConfiguration _defaultLoggerConfiguration; - private static LoggerConfiguration _loggerConfiguration; + private static LoggerConfiguration _loggerConfiguration; - /// - /// Gets the logger configuration for the debug logging. - /// - public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration; + public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration; - static Debug() + static Debug() + { + try { CrestronDataStoreStatic.InitCrestronDataStore(); @@ -141,13 +133,13 @@ namespace PepperDash.Core; var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); - _consoleLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); + _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); - _websocketLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); + _websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); - _fileLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); + _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); @@ -165,31 +157,16 @@ namespace PepperDash.Core; .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: _consoleLogLevelSwitch) - .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLogLevelSwitch) + .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 ? 7 : 14, - levelSwitch: _fileLogLevelSwitch + 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; @@ -231,422 +208,327 @@ namespace PepperDash.Core; CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; - LoadMemory(); - - var context = _contexts.GetOrCreateItem("DEFAULT"); - Level = context.Level; - DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot; + DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot(); 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)); - _errorLogLevelSwitch.MinimumLevelChanged += (sender, args) => + _consoleLoggingLevelSwitch.MinimumLevelChanged += (sender, args) => { - LogMessage(LogEventLevel.Information, "Error log debug level set to {minimumLevel}", _errorLogLevelSwitch.MinimumLevel); + LogMessage(LogEventLevel.Information, "Console debug level set to {minimumLevel}", _consoleLoggingLevelSwitch.MinimumLevel); }; + } + catch (Exception ex) + { + LogError(ex, "Exception in Debug static constructor: {message}", ex.Message); + } + } - // Set initial error log level based on platform && stored level. If appliance, use stored level, otherwise default to verbose - SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? _errorLogLevelSwitch.MinimumLevel : LogEventLevel.Verbose); + private static bool GetDoNotLoadOnNextBoot() + { + var err = CrestronDataStoreStatic.GetLocalBoolValue(DoNotLoadOnNextBootKey, out var doNotLoad); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + { + LogError("Error retrieving DoNotLoadOnNextBoot value: {err}", err); + doNotLoad = false; } - /// - /// UpdateLoggerConfiguration method - /// - public static void UpdateLoggerConfiguration(LoggerConfiguration config) + return doNotLoad; + } + + 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 { - _loggerConfiguration = config; + var result = CrestronDataStoreStatic.GetLocalIntValue(levelStoreKey, out int logLevel); - _logger = config.CreateLogger(); - } - - /// - /// ResetLoggerConfiguration method - /// - public static void ResetLoggerConfiguration() - { - _loggerConfiguration = _defaultLoggerConfiguration; - - _logger = _loggerConfiguration.CreateLogger(); - } - - private static LogEventLevel GetStoredLogEventLevel(string levelStoreKey) - { - try + if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) { - 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"); - - CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, levelStoreKey == ErrorLogLevelStoreKey ? (int)LogEventLevel.Warning : (int)LogEventLevel.Information); - - return levelStoreKey == ErrorLogLevelStoreKey ? LogEventLevel.Warning : 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; - } - - CrestronConsole.PrintLine($"Stored log level for {levelStoreKey} is {logLevel}"); - - return (LogEventLevel)logLevel; - } - catch (Exception ex) - { - CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}"); + CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n"); 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(logLevel < 0 || logLevel > 5) { - if (ver[0] is AssemblyInformationalVersionAttribute verAttribute) - { - PepperDashCoreVersion = verAttribute.InformationalVersion; - } + CrestronConsole.PrintLine($"Stored Log level not valid for {levelStoreKey}: {logLevel}. Setting level to {LogEventLevel.Information}"); + return LogEventLevel.Information; } - else + + 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) { - var version = assembly.GetName().Version; - PepperDashCoreVersion = version.ToString(); + PepperDashCoreVersion = verAttribute.InformationalVersion; } } - - /// - /// Used to save memory when shutting down - /// - /// - static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + else { - - if (programEventType == eProgramStatusEventType.Stopping) - { - Log.CloseAndFlush(); - - if (_saveTimer != null) - { - _saveTimer.Stop(); - _saveTimer = null; - } - LogMessage(LogEventLevel.Information, "Saving debug settings"); - SaveMemory(); - } + var version = assembly.GetName().Version; + PepperDashCoreVersion = version.ToString(); } + } - /// - /// Callback for console command - /// - /// - /// - /// SetDebugFromConsole method - /// - public static void SetDebugFromConsole(string levelString) + /// + /// Used to save memory when shutting down + /// + /// + static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) + { + + if (programEventType == eProgramStatusEventType.Stopping) { - try + Log.CloseAndFlush(); + + if (_saveTimer != null) { - if (levelString.Trim() == "?") - { - CrestronConsole.ConsoleCommandResponse( - "Used to set the minimum level of debug messages:\r\n" + - "Usage: appdebug:P [sink] [level]\r\n" + - " sink: console (default), errorlog, file, all\r\n" + - " all: sets all sinks to the specified level\r\n" + - " level: 0-5 or LogEventLevel name\r\n" + - $"{_logLevels[0]} = 0\r\n" + - $"{_logLevels[1]} = 1\r\n" + - $"{_logLevels[2]} = 2\r\n" + - $"{_logLevels[3]} = 3\r\n" + - $"{_logLevels[4]} = 4\r\n" + - $"{_logLevels[5]} = 5"); - return; - } - - if (string.IsNullOrEmpty(levelString.Trim())) - { - CrestronConsole.ConsoleCommandResponse("Console log level = {0}\r\n", _consoleLogLevelSwitch.MinimumLevel); - CrestronConsole.ConsoleCommandResponse("File log level = {0}\r\n", _fileLogLevelSwitch.MinimumLevel); - CrestronConsole.ConsoleCommandResponse("Error log level = {0}\r\n", _errorLogLevelSwitch.MinimumLevel); - return; - } - - // Parse tokens: first token is sink (defaults to console), second token is level - var tokens = levelString.Trim().Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - - string sinkName; - string levelToken; - - if (tokens.Length == 1) - { - // Single token - assume it's a level for console sink - sinkName = "console"; - levelToken = tokens[0]; - } - else if (tokens.Length == 2) - { - // Two tokens - first is sink, second is level - sinkName = tokens[0].ToLowerInvariant(); - levelToken = tokens[1]; - } - else - { - CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]"); - return; - } - - // Parse the level using the same logic as before - LogEventLevel level; - - if (int.TryParse(levelToken, out var levelInt)) - { - if (levelInt < 0 || levelInt > 5) - { - CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level. If using a number, value must be between 0-5"); - return; - } - - if (!_logLevels.TryGetValue((uint)levelInt, out level)) - { - level = LogEventLevel.Information; - CrestronConsole.ConsoleCommandResponse($"{levelInt} not valid. Setting level to {level}"); - } - } - else if (Enum.TryParse(levelToken, true, out level)) - { - // Successfully parsed as LogEventLevel enum - } - else - { - CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelToken} to valid log level"); - return; - } - - // Set the level for the specified sink - switch (sinkName) - { - case "console": - SetDebugLevel(level); - break; - case "errorlog": - SetErrorLogMinimumDebugLevel(level); - break; - case "file": - SetFileMinimumDebugLevel(level); - break; - case "all": - SetDebugLevel(level); - SetErrorLogMinimumDebugLevel(level); - SetFileMinimumDebugLevel(level); - break; - default: - CrestronConsole.ConsoleCommandResponse($"Error: Unknown sink '{sinkName}'. Valid sinks: console, errorlog, file"); - break; - } - } - catch - { - CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]"); + _saveTimer.Stop(); + _saveTimer = null; } + LogMessage(LogEventLevel.Information, "Saving debug settings"); + SaveMemory(); } + } - /// - /// Sets the debug level - /// - /// Valid values 0-5 - /// - /// SetDebugLevel method - /// - public static void SetDebugLevel(uint level) + /// + /// Callback for console command + /// + /// + public static void SetDebugFromConsole(string levelString) + { + try { - if (!_logLevels.TryGetValue(level, out var logLevel)) + if (levelString.Trim() == "?") { - logLevel = LogEventLevel.Information; - - CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}"); - - SetDebugLevel(logLevel); + 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 method - /// - public static void SetDebugLevel(LogEventLevel level) + 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 { - _consoleLogLevelSwitch.MinimumLevel = level; - - CrestronConsole.ConsoleCommandResponse("[Application {0}] Debug level set to {1}\r\n", - InitialParametersClass.ApplicationNumber, _consoleLogLevelSwitch.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}"); - } - - /// - /// SetWebSocketMinimumDebugLevel method - /// - public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) - { - _websocketLogLevelSwitch.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}", _websocketLogLevelSwitch.MinimumLevel); - } - - /// - /// SetErrorLogMinimumDebugLevel method - /// - public static void SetErrorLogMinimumDebugLevel(LogEventLevel level) - { - _errorLogLevelSwitch.MinimumLevel = level; - - CrestronConsole.ConsoleCommandResponse("[Application {0}] Error log level set to {1}\r\n", - InitialParametersClass.ApplicationNumber, _errorLogLevelSwitch.MinimumLevel); - - CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); - - var err = CrestronDataStoreStatic.SetLocalIntValue(ErrorLogLevelStoreKey, (int)level); - - CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); - - if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) - CrestronConsole.PrintLine($"Error saving error log debug level setting: {err}"); - } - - /// - /// SetFileMinimumDebugLevel method - /// - public static void SetFileMinimumDebugLevel(LogEventLevel level) - { - _fileLogLevelSwitch.MinimumLevel = level; - - CrestronConsole.ConsoleCommandResponse("[Application {0}] File log level set to {1}\r\n", - InitialParametersClass.ApplicationNumber, _fileLogLevelSwitch.MinimumLevel); - - CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int)level}"); - - var err = CrestronDataStoreStatic.SetLocalIntValue(FileLevelStoreKey, (int)level); - - CrestronConsole.ConsoleCommandResponse($"Store result: {err}:{(int)level}"); - - if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) - CrestronConsole.PrintLine($"Error saving file debug level setting: {err}"); - } - - /// - /// Callback for console command - /// - /// - /// - /// SetDoNotLoadOnNextBootFromConsole method - /// - public static void SetDoNotLoadOnNextBootFromConsole(string stateString) - { - try + if (string.IsNullOrEmpty(stateString.Trim())) { - 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 - /// - /// - /// - /// SetDebugFilterFromConsole method - /// - 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)"); + CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadConfigOnNextBoot); 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) - { + + 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(); } @@ -664,579 +546,488 @@ namespace PepperDash.Core; - /// - /// sets the settings for a device or creates a new entry - /// - /// - /// - /// - public static void SetDeviceDebugSettings(string deviceKey, object settings) + /// + /// 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; + + var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state); + + if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) + LogError("Error saving console debug level setting: {err}", err); + + LogInformation("Do Not Load Config on Next Boot set to {state}", 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 as an Error + /// + /// 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)) { - _contexts.SetDebugSettingsForKey(deviceKey, settings); - SaveMemoryOnTimeout(); + _logger.Error(ex, message, args); } + } - /// - /// Gets the device settings for a device by key or returns null - /// - /// - /// - /// - /// GetDeviceDebugSettingsForKey method - /// - 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); - } - - /// - /// ShowDebugLog method - /// - 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 as an Error - /// - /// 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 - /// - /// LogMessage method - /// - 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); - } - } - - /// - /// Logs a message at the specified log level. - /// - /// Level to log at - /// Message template - /// Args to put into message template - public static void LogMessage(LogEventLevel level, string message, params object[] 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); } + } - /// - /// LogMessage method - /// - public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args) + public static void LogMessage(LogEventLevel level, string message, params object[] args) + { + _logger.Write(level, message, args); + } + + public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args) + { + _logger.Write(level, ex, message, args); + } + + public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args) + { + LogMessage(level, message, keyed, args); + } + + public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args) + { + using (LogContext.PushProperty("Key", device?.Key)) { _logger.Write(level, ex, message, args); } + } - /// - /// LogMessage method - /// - public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args) - { - LogMessage(level, message, keyed, args); - } - - /// - /// LogMessage method - /// - public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args) - { - using (LogContext.PushProperty("Key", device?.Key)) - { - _logger.Write(level, ex, message, args); - } - } - - #region Explicit methods for logging levels - /// - /// LogVerbose method - /// - public static void LogVerbose(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Verbose, message, args); - } - } - - /// - /// LogVerbose method - /// - public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Verbose, ex, message, args); - } - } - - /// - /// LogVerbose method - /// - public static void LogVerbose(string message, params object[] args) + #region Explicit methods for logging levels + public static void LogVerbose(IKeyed keyed, string message, params object[] args) + { + using(LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Verbose, message, args); } + } - /// - /// LogVerbose method - /// - public static void LogVerbose(Exception ex, string message, params object[] args) + public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) + { + using(LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Verbose, ex, message, args); } + } - /// - /// LogDebug method - /// - public static void LogDebug(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Debug, message, args); - } - } + public static void LogVerbose(string message, params object[] args) + { + _logger.Write(LogEventLevel.Verbose, message, args); + } - /// - /// LogDebug method - /// - public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Debug, ex, message, args); - } - } + public static void LogVerbose(Exception ex, string message, params object[] args) + { + _logger.Write(LogEventLevel.Verbose, ex, null, message, args); + } - /// - /// LogDebug method - /// - public static void LogDebug(string message, params object[] args) + public static void LogDebug(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Debug, message, args); } + } - /// - /// LogDebug method - /// - public static void LogDebug(Exception ex, string message, params object[] args) + public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { - _logger.Write(LogEventLevel.Debug, ex, null, message, args); + _logger.Write(LogEventLevel.Debug, ex, message, args); } + } - /// - /// LogInformation method - /// - public static void LogInformation(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Information, message, args); - } - } + public static void LogDebug(string message, params object[] args) + { + _logger.Write(LogEventLevel.Debug, message, args); + } - /// - /// LogInformation method - /// - public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Information, ex, message, args); - } - } + public static void LogDebug(Exception ex, string message, params object[] args) + { + _logger.Write(LogEventLevel.Debug, ex, null, message, args); + } - /// - /// LogInformation method - /// - public static void LogInformation(string message, params object[] args) + public static void LogInformation(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Information, message, args); } + } - /// - /// LogInformation method - /// - public static void LogInformation(Exception ex, string message, params object[] args) + public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Information, ex, message, args); } + } - /// - /// LogWarning method - /// - public static void LogWarning(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Warning, message, args); - } - } + public static void LogInformation(string message, params object[] args) + { + _logger.Write(LogEventLevel.Information, message, args); + } - /// - /// LogWarning method - /// - public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Warning, ex, message, args); - } - } + public static void LogInformation(Exception ex, string message, params object[] args) + { + _logger.Write(LogEventLevel.Information, ex, null, message, args); + } - /// - /// LogWarning method - /// - public static void LogWarning(string message, params object[] args) + public static void LogWarning(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Warning, message, args); } + } - /// - /// LogWarning method - /// - public static void LogWarning(Exception ex, string message, params object[] args) + public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Warning, ex, message, args); } + } - /// - /// LogError method - /// - public static void LogError(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Error, message, args); - } - } + public static void LogWarning(string message, params object[] args) + { + _logger.Write(LogEventLevel.Warning, message, args); + } - /// - /// LogError method - /// - public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Error, ex, message, args); - } - } + public static void LogWarning(Exception ex, string message, params object[] args) + { + _logger.Write(LogEventLevel.Warning, ex, null, message, args); + } - /// - /// LogError method - /// - public static void LogError(string message, params object[] args) + public static void LogError(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Error, message, args); } + } - /// - /// LogError method - /// - public static void LogError(Exception ex, string message, params object[] args) + public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Error, ex, message, args); } + } - /// - /// LogFatal method - /// - public static void LogFatal(IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Fatal, message, args); - } - } + public static void LogError(string message, params object[] args) + { + _logger.Write(LogEventLevel.Error, message, args); + } - /// - /// LogFatal method - /// - public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args) - { - using (LogContext.PushProperty("Key", keyed?.Key)) - { - _logger.Write(LogEventLevel.Fatal, ex, message, args); - } - } + public static void LogError(Exception ex, string message, params object[] args) + { + _logger.Write(LogEventLevel.Error, ex, null, message, args); + } - /// - /// LogFatal method - /// - public static void LogFatal(string message, params object[] args) + public static void LogFatal(IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Fatal, message, args); } + } - /// - /// LogFatal method - /// - public static void LogFatal(Exception ex, string message, params object[] args) + public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args) + { + using (LogContext.PushProperty("Key", keyed?.Key)) { _logger.Write(LogEventLevel.Fatal, ex, message, args); } + } - #endregion + public static void LogFatal(string message, params object[] args) + { + _logger.Write(LogEventLevel.Fatal, message, args); + } + + public static void LogFatal(Exception ex, string message, params object[] args) + { + _logger.Write(LogEventLevel.Fatal, ex, null, message, args); + } + + #endregion - private static void LogMessage(uint level, string format, params object[] items) - { - if (!_logLevels.ContainsKey(level)) return; + private static void LogMessage(uint level, string format, params object[] items) + { + if (!_logLevels.ContainsKey(level)) return; - var logLevel = _logLevels[level]; + var logLevel = _logLevels[level]; + + LogMessage(logLevel, format, items); + } - LogMessage(logLevel, format, items); - } + private static void LogMessage(uint level, IKeyed keyed, string format, params object[] items) + { + if (!_logLevels.ContainsKey(level)) return; - private static void LogMessage(uint level, IKeyed keyed, string format, params object[] items) - { - if (!_logLevels.ContainsKey(level)) return; + var logLevel = _logLevels[level]; - var logLevel = _logLevels[level]; - - LogMessage(logLevel, keyed, format, items); - } + 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. Will be removed in 2.2.0 and later versions")] - public static void Console(uint level, string format, params object[] 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. Will be removed in 2.2.0 and later versions")] + public static void Console(uint level, string format, params object[] items) + { - LogMessage(level, format, 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)); - //} - } + //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, Will be removed in 2.2.0 and later versions")] - public static void Console(uint level, IKeyed dev, string format, params object[] items) - { - LogMessage(level, dev, format, items); + /// + [Obsolete("Use LogMessage methods, Will be removed in 2.2.0 and later versions")] + 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); + //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, Will be removed in 2.2.0 and later versions")] + 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, Will be removed in 2.2.0 and later versions")] + 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, Will be removed in 2.2.0 and later versions")] + 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, Will be removed in 2.2.0 and later versions")] + 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, Will be removed in 2.2.0 and later versions")] + 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; } + } - /// - /// 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, Will be removed in 2.2.0 and later versions")] - 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, Will be removed in 2.2.0 and later versions")] - 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, Will be removed in 2.2.0 and later versions")] - 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, Will be removed in 2.2.0 and later versions")] - 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, Will be removed in 2.2.0 and later versions")] - public static void LogError(ErrorLogLevel errorLogLevel, string str) - { - switch (errorLogLevel) + /// + /// Writes the memory object after timeout + /// + static void SaveMemoryOnTimeout() + { + Console(0, "Saving debug settings"); + if (_saveTimer == null) + _saveTimer = new CTimer(o => { - case ErrorLogLevel.Error: - LogMessage(LogEventLevel.Error, str); - break; - case ErrorLogLevel.Warning: - LogMessage(LogEventLevel.Warning, str); - break; - case ErrorLogLevel.Notice: - LogMessage(LogEventLevel.Information, str); - break; - } - } + _saveTimer = null; + SaveMemory(); + }, SaveTimeoutMs); + else + _saveTimer.Reset(SaveTimeoutMs); + } - /// - /// Writes the memory object after timeout - /// - static void SaveMemoryOnTimeout() + /// + /// Writes the memory - use SaveMemoryOnTimeout + /// + static void SaveMemory() + { + //var dir = @"\NVRAM\debug"; + //if (!Directory.Exists(dir)) + // Directory.Create(dir); + + var fileName = GetMemoryFileName(); + + LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", fileName); + + using (var sw = new StreamWriter(fileName)) { - Console(0, "Saving debug settings"); - if (_saveTimer == null) - _saveTimer = new CTimer(o => + 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) { - _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(); - - LogMessage(LogEventLevel.Information, "Loading debug settings file from {fileName}", 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) - { - LogMessage(LogEventLevel.Debug, "Debug memory restored from file"); - return; - } + LogMessage(LogEventLevel.Debug, "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); - } + _contexts = new DebugContextCollection(); + } - return string.Format("{0}{1}user{1}debugSettings{1}{2}.json", Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); + /// + /// 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); + } + + /// + /// Error level to for message to be logged at + /// + public enum ErrorLogLevel + { /// - /// Enumeration of ErrorLogLevel values + /// Error /// - public enum ErrorLogLevel - { - /// - /// Error - /// - Error, - /// - /// Warning - /// - Warning, - /// - /// Notice - /// - Notice, - /// - /// None - /// - None, + Error, + /// + /// Warning + /// + Warning, + /// + /// Notice + /// + Notice, + /// + /// None + /// + None, } } \ No newline at end of file diff --git a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs index d62b3c8b..f6e5a2f4 100644 --- a/src/PepperDash.Core/Logging/DebugWebsocketSink.cs +++ b/src/PepperDash.Core/Logging/DebugWebsocketSink.cs @@ -17,326 +17,320 @@ using WebSocketSharp.Server; using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2; using WebSocketSharp.Net; -namespace PepperDash.Core +namespace PepperDash.Core; + +/// +/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected +/// WebSocket clients. +/// +/// This class implements the interface and is designed to send +/// formatted log events to WebSocket clients connected to a secure WebSocket server. The server is hosted locally +/// and uses a self-signed certificate for SSL/TLS encryption. +public class DebugWebsocketSink : ILogEventSink, IKeyed { + private HttpServer _httpsServer; + + private readonly string _path = "/debug/join/"; + private const string _certificateName = "selfCres"; + private const string _certificatePassword = "cres12345"; + /// - /// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected - /// WebSocket clients. + /// Gets the port number on which the HTTPS server is currently running. /// - /// This class implements the interface and is designed to send - /// formatted log events to WebSocket clients connected to a secure WebSocket server. The server is hosted locally - /// and uses a self-signed certificate for SSL/TLS encryption. - public class DebugWebsocketSink : ILogEventSink, IKeyed + public int Port + { get + { + + if(_httpsServer == null) return 0; + return _httpsServer.Port; + } + } + + /// + /// Gets the WebSocket URL for the current server instance. + /// + /// The URL is dynamically constructed based on the server's current IP address, port, + /// and WebSocket path. + public string Url { - private WebSocketServer _wsServer; - - private readonly string _path = "/debug/join/"; - private const string _certificateName = "selfCres"; - private const string _certificatePassword = "cres12345"; - - /// - /// Gets the port number on which the HTTPS server is currently running. - /// - public int Port - { get - { - - if(_wsServer == null) return 0; - return _wsServer.Port; - } - } - - /// - /// Gets the WebSocket URL for the current server instance. - /// - /// The URL is dynamically constructed based on the server's current IP address, port, - /// and WebSocket path. - public string Url + get { - 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}"; + } + } + + /// + /// Gets a value indicating whether the HTTPS server is currently listening for incoming connections. + /// + public bool IsRunning { get => _httpsServer?.IsListening ?? false; } + + /// + public string Key => "DebugWebsocketSink"; + + private readonly ITextFormatter _textFormatter; + + /// + /// Initializes a new instance of the class with the specified text formatter. + /// + /// This constructor initializes the WebSocket sink and ensures that a certificate is + /// available for secure communication. If the required certificate does not exist, it will be created + /// automatically. Additionally, the sink is configured to stop the server when the program is + /// stopping. + /// The text formatter used to format log messages. If null, a default JSON formatter is used. + public DebugWebsocketSink(ITextFormatter formatProvider) + { + + _textFormatter = formatProvider ?? new JsonFormatter(); + + if (!File.Exists($"\\user\\{_certificateName}.pfx")) + CreateCert(); + + CrestronEnvironment.ProgramStatusEventHandler += type => + { + if (type == eProgramStatusEventType.Stopping) { - if (_wsServer == null) return ""; - return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_wsServer.Port}{_wsServer.WebSocketServices[_path].Path}"; + StopServer(); } + }; + } + + private static void CreateCert() + { + 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); + + 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), [string.Format("{0}.{1}", hostName, domainName), ipAddress], [KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth]); + + //Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested + + var separator = Path.DirectorySeparatorChar; + + utility.CertificatePassword = _certificatePassword; + utility.WriteCertificate(certificate, @$"{separator}user{separator}", _certificateName); } - - /// - /// Gets a value indicating whether the WebSocket server is currently listening for incoming connections. - /// - public bool IsRunning { get => _wsServer?.IsListening ?? false; } - - /// - public string Key => "DebugWebsocketSink"; - - private readonly ITextFormatter _textFormatter; - - /// - /// Initializes a new instance of the class with the specified text formatter. - /// - /// This constructor initializes the WebSocket sink and ensures that a certificate is - /// available for secure communication. If the required certificate does not exist, it will be created - /// automatically. Additionally, the sink is configured to stop the server when the program is - /// stopping. - /// The text formatter used to format log messages. If null, a default JSON formatter is used. - public DebugWebsocketSink(ITextFormatter formatProvider) + catch (Exception ex) { + //Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); + CrestronConsole.PrintLine("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); + } + } - _textFormatter = formatProvider ?? new JsonFormatter(); + /// + /// Sends a log event to all connected WebSocket clients. + /// + /// The log event is formatted using the configured text formatter and then broadcasted + /// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not + /// listening, the method exits without performing any action. + /// The log event to be formatted and broadcasted. Cannot be null. + public void Emit(LogEvent logEvent) + { + if (_httpsServer == null || !_httpsServer.IsListening) return; - if (!File.Exists($"\\user\\{_certificateName}.pfx")) - CreateCert(); + var sw = new StringWriter(); + _textFormatter.Format(logEvent, sw); - CrestronEnvironment.ProgramStatusEventHandler += type => + _httpsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString()); + } + + /// + /// Starts the WebSocket server on the specified port and configures it with the appropriate certificate. + /// + /// This method initializes the WebSocket server and binds it to the specified port. It + /// also applies the server's certificate for secure communication. Ensure that the port is not already in use + /// and that the certificate file is accessible. + /// The port number on which the WebSocket server will listen. Must be a valid, non-negative port number. + 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)) { - if (type == eProgramStatusEventType.Stopping) - { - StopServer(); - } - }; - } + Debug.Console(0, "Assigning SSL Configuration"); - private static void CreateCert() - { - 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); - - 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), [string.Format("{0}.{1}", hostName, domainName), ipAddress], [KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth]); - - //Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested - - utility.CertificatePassword = _certificatePassword; - utility.WriteCertificate(certificate, @"\user\", _certificateName); - } - catch (Exception ex) - { - //Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); - Debug.LogError("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); - } - } - - /// - /// Sends a log event to all connected WebSocket clients. - /// - /// The log event is formatted using the configured text formatter and then broadcasted - /// to all clients connected to the WebSocket server. If the WebSocket server is not initialized or not - /// listening, the method exits without performing any action. - /// The log event to be formatted and broadcasted. Cannot be null. - public void Emit(LogEvent logEvent) - { - if (_wsServer == null || !_wsServer.IsListening) return; - - var sw = new StringWriter(); - _textFormatter.Format(logEvent, sw); - - _wsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString()); - } - - /// - /// Starts the WebSocket server on the specified port and configures it with the appropriate certificate. - /// - /// This method initializes the WebSocket server and binds it to the specified port. It - /// also applies the server's certificate for secure communication. Ensure that the port is not already in use - /// and that the certificate file is accessible. - /// The port number on which the WebSocket server will listen. Must be a valid, non-negative port number. - public void StartServerAndSetPort(int port) - { - Debug.LogInformation("Starting Websocket Server on port: {0}", port); - - - Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword); - } - - private void Start(int port, string certPath = "", string certPassword = "") - { - try - { - ServerSslConfiguration sslConfig = null; - - if (!string.IsNullOrWhiteSpace(certPath)) - { - Debug.LogInformation("Assigning SSL Configuration"); - sslConfig = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword)) + _httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword); + _httpsServer.SslConfiguration.ClientCertificateRequired = false; + _httpsServer.SslConfiguration.CheckCertificateRevocation = false; + _httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12; + //this is just to test, you might want to actually validate + _httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { - ClientCertificateRequired = false, - CheckCertificateRevocation = false, - EnabledSslProtocols = SslProtocols.Tls12, - //this is just to test, you might want to actually validate - ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => - { - Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered"); - return true; - } + 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; - _wsServer = new WebSocketServer(port, true); - if (sslConfig != null) + switch(d.Level) { - _wsServer.SslConfiguration.ServerCertificate = sslConfig.ServerCertificate; + 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.LogInformation("Adding Debug Client Service"); - _wsServer.AddWebSocketService(_path); - Debug.LogInformation("Assigning Log Info"); - _wsServer.Log.Level = LogLevel.Trace; - _wsServer.Log.Output = (d, s) => - { - uint level; + 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"); - 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.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s); - }; - Debug.LogInformation("Starting"); - - _wsServer.Start(); - Debug.LogInformation("Ready"); - } - catch (Exception ex) - { - Debug.LogError("WebSocket Failed to start {0}", ex.Message); - } + _httpsServer.Start(); + Debug.Console(0, "Ready"); } - - /// - /// Stops the WebSocket server if it is currently running. - /// - /// This method halts the WebSocket server and releases any associated resources. After - /// calling this method, the server will no longer accept or process incoming connections. - public void StopServer() + catch (Exception ex) { - Debug.LogInformation("Stopping Websocket Server"); - _wsServer?.Stop(); - - _wsServer = null; + Debug.Console(0, "WebSocket Failed to start {0}", ex.Message); } } /// - /// Configures the logger to write log events to a debug WebSocket sink. + /// Stops the WebSocket server if it is currently running. /// - /// This extension method allows you to direct log events to a WebSocket sink for debugging - /// purposes. - public static class DebugWebsocketSinkExtensions + /// This method halts the WebSocket server and releases any associated resources. After + /// calling this method, the server will no longer accept or process incoming connections. + public void StopServer() { - /// - /// Configures a logger to write log events to a debug WebSocket sink. - /// - /// This method adds a sink that writes log events to a WebSocket for debugging purposes. - /// It is typically used during development to stream log events in real-time. - /// The logger sink configuration to apply the WebSocket sink to. - /// An optional text formatter to format the log events. If not provided, a default formatter will be used. - /// A object that can be used to further configure the logger. - public static LoggerConfiguration DebugWebsocketSink( - this LoggerSinkConfiguration loggerConfiguration, - ITextFormatter formatProvider = null) - { - return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider)); - } - } + Debug.Console(0, "Stopping Websocket Server"); + _httpsServer?.Stop(); - /// - /// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message - /// handling functionality. - /// - /// The class extends to handle - /// WebSocket connections, including events for opening, closing, receiving messages, and errors. It tracks the - /// duration of the connection and logs relevant events for debugging. - public class DebugClient : WebSocketBehavior - { - private DateTime _connectionTime; - - /// - /// Gets the duration of time the WebSocket connection has been active. - /// - public TimeSpan ConnectedDuration - { - get - { - if (Context.WebSocket.IsAlive) - { - return DateTime.Now - _connectionTime; - } - else - { - return new TimeSpan(0); - } - } - } - - /// - /// Initializes a new instance of the class. - /// - /// This constructor creates a new instance and logs its creation. - public DebugClient() - { - Debug.LogInformation("DebugClient Created"); - } - - /// - protected override void OnOpen() - { - base.OnOpen(); - - var url = Context.WebSocket.Url; - Debug.LogInformation("New WebSocket Connection from: {0}", url); - - _connectionTime = DateTime.Now; - } - - /// - protected override void OnMessage(MessageEventArgs e) - { - base.OnMessage(e); - - Debug.LogInformation("WebSocket UiClient Message: {0}", e.Data); - } - - /// - protected override void OnClose(CloseEventArgs e) - { - base.OnClose(e); - - Debug.LogInformation("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); - - } - - /// - protected override void OnError(WebSocketSharp.ErrorEventArgs e) - { - base.OnError(e); - - Debug.LogError("WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); - } + _httpsServer = null; + } +} + +/// +/// Configures the logger to write log events to a debug WebSocket sink. +/// +/// This extension method allows you to direct log events to a WebSocket sink for debugging +/// purposes. +public static class DebugWebsocketSinkExtensions +{ + /// + /// Configures a logger to write log events to a debug WebSocket sink. + /// + /// This method adds a sink that writes log events to a WebSocket for debugging purposes. + /// It is typically used during development to stream log events in real-time. + /// The logger sink configuration to apply the WebSocket sink to. + /// An optional text formatter to format the log events. If not provided, a default formatter will be used. + /// A object that can be used to further configure the logger. + public static LoggerConfiguration DebugWebsocketSink( + this LoggerSinkConfiguration loggerConfiguration, + ITextFormatter formatProvider = null) + { + return loggerConfiguration.Sink(new DebugWebsocketSink(formatProvider)); + } +} + +/// +/// Represents a WebSocket client for debugging purposes, providing connection lifecycle management and message +/// handling functionality. +/// +/// The class extends to handle +/// WebSocket connections, including events for opening, closing, receiving messages, and errors. It tracks the +/// duration of the connection and logs relevant events for debugging. +public class DebugClient : WebSocketBehavior +{ + private DateTime _connectionTime; + + /// + /// Gets the duration of time the WebSocket connection has been active. + /// + public TimeSpan ConnectedDuration + { + get + { + if (Context.WebSocket.IsAlive) + { + return DateTime.Now - _connectionTime; + } + else + { + return new TimeSpan(0); + } + } + } + + /// + /// Initializes a new instance of the class. + /// + /// This constructor creates a new instance and logs its + /// creation using the method with a debug level of 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); } }