feat: remove context file for storing values

This commit is contained in:
Andrew Welker 2025-07-04 16:03:00 -05:00 committed by Neil Dorin
parent 3ece4f0b7b
commit 76759d35cc
2 changed files with 1030 additions and 1245 deletions

View file

@ -23,6 +23,7 @@ using Serilog.Templates;
namespace PepperDash.Core; namespace PepperDash.Core;
/// <summary> /// <summary>
/// Contains debug commands for use in various situations
/// </summary> /// </summary>
public static class Debug public static class Debug
{ {
@ -30,6 +31,7 @@ namespace PepperDash.Core;
private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel"; private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel";
private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel"; private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel";
private static readonly string FileLevelStoreKey = "FileDebugLevel"; private static readonly string FileLevelStoreKey = "FileDebugLevel";
private static readonly string DoNotLoadOnNextBootKey = "DoNotLoadOnNextBoot";
private static readonly Dictionary<uint, LogEventLevel> _logLevels = new Dictionary<uint, LogEventLevel>() private static readonly Dictionary<uint, LogEventLevel> _logLevels = new Dictionary<uint, LogEventLevel>()
{ {
@ -43,27 +45,21 @@ namespace PepperDash.Core;
private static ILogger _logger; private static ILogger _logger;
private static readonly LoggingLevelSwitch _consoleLogLevelSwitch; private static readonly LoggingLevelSwitch _consoleLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _websocketLogLevelSwitch; private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _errorLogLevelSwitch; private static readonly LoggingLevelSwitch _errorLogLevelSwitch;
private static readonly LoggingLevelSwitch _fileLogLevelSwitch; private static readonly LoggingLevelSwitch _fileLevelSwitch;
/// <summary>
/// Gets the minimum log level for the websocket sink.
/// </summary>
public static LogEventLevel WebsocketMinimumLogLevel public static LogEventLevel WebsocketMinimumLogLevel
{ {
get { return _websocketLogLevelSwitch.MinimumLevel; } get { return _websocketLoggingLevelSwitch.MinimumLevel; }
} }
private static readonly DebugWebsocketSink _websocketSink; private static readonly DebugWebsocketSink _websocketSink;
/// <summary>
/// Gets the websocket sink for debug logging.
/// </summary>
public static DebugWebsocketSink WebsocketSink public static DebugWebsocketSink WebsocketSink
{ {
get { return _websocketSink; } get { return _websocketSink; }
@ -87,12 +83,12 @@ namespace PepperDash.Core;
public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber); public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber);
/// <summary> /// <summary>
/// Gets or sets the Level /// Debug level to set for a given program.
/// </summary> /// </summary>
public static int Level { get; private set; } public static int Level { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the DoNotLoadConfigOnNextBoot /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal
/// </summary> /// </summary>
public static bool DoNotLoadConfigOnNextBoot { get; private set; } public static bool DoNotLoadConfigOnNextBoot { get; private set; }
@ -100,13 +96,10 @@ namespace PepperDash.Core;
private const int SaveTimeoutMs = 30000; private const int SaveTimeoutMs = 30000;
/// <summary>
/// Indicates whether the system is running on an appliance.
/// </summary>
public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; public static bool IsRunningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance;
/// <summary> /// <summary>
/// Gets or sets the PepperDashCoreVersion /// Version for the currently loaded PepperDashCore dll
/// </summary> /// </summary>
public static string PepperDashCoreVersion { get; private set; } public static string PepperDashCoreVersion { get; private set; }
@ -124,12 +117,11 @@ namespace PepperDash.Core;
private static LoggerConfiguration _loggerConfiguration; private static LoggerConfiguration _loggerConfiguration;
/// <summary>
/// Gets the logger configuration for the debug logging.
/// </summary>
public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration; public static LoggerConfiguration LoggerConfiguration => _loggerConfiguration;
static Debug() static Debug()
{
try
{ {
CrestronDataStoreStatic.InitCrestronDataStore(); CrestronDataStoreStatic.InitCrestronDataStore();
@ -141,13 +133,13 @@ namespace PepperDash.Core;
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); 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); _errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
@ -165,31 +157,16 @@ namespace PepperDash.Core;
.MinimumLevel.Verbose() .MinimumLevel.Verbose()
.Enrich.FromLogContext() .Enrich.FromLogContext()
.Enrich.With(new CrestronEnricher()) .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(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: _websocketLogLevelSwitch) .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch)
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug, restrictedToMinimumLevel: LogEventLevel.Debug,
retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 7 : 14, retainedFileCountLimit: CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? 30 : 60,
levelSwitch: _fileLogLevelSwitch 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 // Instantiate the root logger
_loggerConfiguration = _defaultLoggerConfiguration; _loggerConfiguration = _defaultLoggerConfiguration;
@ -231,27 +208,35 @@ namespace PepperDash.Core;
CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler;
LoadMemory(); DoNotLoadConfigOnNextBoot = GetDoNotLoadOnNextBoot();
var context = _contexts.GetOrCreateItem("DEFAULT");
Level = context.Level;
DoNotLoadConfigOnNextBoot = context.DoNotLoadOnNextBoot;
if (DoNotLoadConfigOnNextBoot) 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)); 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);
}; };
}
// Set initial error log level based on platform && stored level. If appliance, use stored level, otherwise default to verbose catch (Exception ex)
SetErrorLogMinimumDebugLevel(CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? _errorLogLevelSwitch.MinimumLevel : LogEventLevel.Verbose); {
LogError(ex, "Exception in Debug static constructor: {message}", ex.Message);
}
}
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;
}
return doNotLoad;
} }
/// <summary>
/// UpdateLoggerConfiguration method
/// </summary>
public static void UpdateLoggerConfiguration(LoggerConfiguration config) public static void UpdateLoggerConfiguration(LoggerConfiguration config)
{ {
_loggerConfiguration = config; _loggerConfiguration = config;
@ -259,9 +244,6 @@ namespace PepperDash.Core;
_logger = config.CreateLogger(); _logger = config.CreateLogger();
} }
/// <summary>
/// ResetLoggerConfiguration method
/// </summary>
public static void ResetLoggerConfiguration() public static void ResetLoggerConfiguration()
{ {
_loggerConfiguration = _defaultLoggerConfiguration; _loggerConfiguration = _defaultLoggerConfiguration;
@ -278,10 +260,7 @@ namespace PepperDash.Core;
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) 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"); CrestronConsole.Print($"Unable to retrieve stored log level for {levelStoreKey}.\r\nError: {result}.\r\nSetting level to {LogEventLevel.Information}\r\n");
return LogEventLevel.Information;
CrestronDataStoreStatic.SetLocalIntValue(levelStoreKey, levelStoreKey == ErrorLogLevelStoreKey ? (int)LogEventLevel.Warning : (int)LogEventLevel.Information);
return levelStoreKey == ErrorLogLevelStoreKey ? LogEventLevel.Warning : LogEventLevel.Information;
} }
if(logLevel < 0 || logLevel > 5) if(logLevel < 0 || logLevel > 5)
@ -290,11 +269,8 @@ namespace PepperDash.Core;
return LogEventLevel.Information; return LogEventLevel.Information;
} }
CrestronConsole.PrintLine($"Stored log level for {levelStoreKey} is {logLevel}");
return (LogEventLevel)logLevel; return (LogEventLevel)logLevel;
} } catch (Exception ex)
catch (Exception ex)
{ {
CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}"); CrestronConsole.PrintLine($"Exception retrieving log level for {levelStoreKey}: {ex.Message}");
return LogEventLevel.Information; return LogEventLevel.Information;
@ -347,9 +323,6 @@ namespace PepperDash.Core;
/// Callback for console command /// Callback for console command
/// </summary> /// </summary>
/// <param name="levelString"></param> /// <param name="levelString"></param>
/// <summary>
/// SetDebugFromConsole method
/// </summary>
public static void SetDebugFromConsole(string levelString) public static void SetDebugFromConsole(string levelString)
{ {
try try
@ -357,104 +330,44 @@ namespace PepperDash.Core;
if (levelString.Trim() == "?") if (levelString.Trim() == "?")
{ {
CrestronConsole.ConsoleCommandResponse( CrestronConsole.ConsoleCommandResponse(
"Used to set the minimum level of debug messages:\r\n" + $@"Used to set the minimum level of debug messages to be printed to the console:
"Usage: appdebug:P [sink] [level]\r\n" + {_logLevels[0]} = 0
" sink: console (default), errorlog, file, all\r\n" + {_logLevels[1]} = 1
" all: sets all sinks to the specified level\r\n" + {_logLevels[2]} = 2
" level: 0-5 or LogEventLevel name\r\n" + {_logLevels[3]} = 3
$"{_logLevels[0]} = 0\r\n" + {_logLevels[4]} = 4
$"{_logLevels[1]} = 1\r\n" + {_logLevels[5]} = 5");
$"{_logLevels[2]} = 2\r\n" +
$"{_logLevels[3]} = 3\r\n" +
$"{_logLevels[4]} = 4\r\n" +
$"{_logLevels[5]} = 5");
return; return;
} }
if (string.IsNullOrEmpty(levelString.Trim())) if (string.IsNullOrEmpty(levelString.Trim()))
{ {
CrestronConsole.ConsoleCommandResponse("Console log level = {0}\r\n", _consoleLogLevelSwitch.MinimumLevel); CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", _consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("File log level = {0}\r\n", _fileLogLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse("Error log level = {0}\r\n", _errorLogLevelSwitch.MinimumLevel);
return; return;
} }
// Parse tokens: first token is sink (defaults to console), second token is level if(int.TryParse(levelString, out var levelInt))
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) 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"); 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; return;
} }
if (!_logLevels.TryGetValue((uint)levelInt, out level)) if(Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
{ {
level = LogEventLevel.Information; SetDebugLevel(levelEnum);
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; return;
} }
// Set the level for the specified sink CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
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 catch
{ {
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [sink] [level]"); CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
} }
} }
@ -462,9 +375,6 @@ namespace PepperDash.Core;
/// Sets the debug level /// Sets the debug level
/// </summary> /// </summary>
/// <param name="level"> Valid values 0-5</param> /// <param name="level"> Valid values 0-5</param>
/// <summary>
/// SetDebugLevel method
/// </summary>
public static void SetDebugLevel(uint level) public static void SetDebugLevel(uint level)
{ {
if(!_logLevels.TryGetValue(level, out var logLevel)) if(!_logLevels.TryGetValue(level, out var logLevel))
@ -479,15 +389,12 @@ namespace PepperDash.Core;
SetDebugLevel(logLevel); SetDebugLevel(logLevel);
} }
/// <summary>
/// SetDebugLevel method
/// </summary>
public static void SetDebugLevel(LogEventLevel level) public static void SetDebugLevel(LogEventLevel level)
{ {
_consoleLogLevelSwitch.MinimumLevel = level; _consoleLoggingLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}] Debug level set to {1}\r\n", CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}\r\n",
InitialParametersClass.ApplicationNumber, _consoleLogLevelSwitch.MinimumLevel); InitialParametersClass.ApplicationNumber, _consoleLoggingLevelSwitch.MinimumLevel);
CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}"); CrestronConsole.ConsoleCommandResponse($"Storing level {level}:{(int) level}");
@ -499,68 +406,46 @@ namespace PepperDash.Core;
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}"); CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
} }
/// <summary>
/// SetWebSocketMinimumDebugLevel method
/// </summary>
public static void SetWebSocketMinimumDebugLevel(LogEventLevel level) public static void SetWebSocketMinimumDebugLevel(LogEventLevel level)
{ {
_websocketLogLevelSwitch.MinimumLevel = level; _websocketLoggingLevelSwitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level); var err = CrestronDataStoreStatic.SetLocalUintValue(WebSocketLevelStoreKey, (uint) level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err); LogMessage(LogEventLevel.Information, "Error saving websocket debug level setting: {erro}", err);
LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLogLevelSwitch.MinimumLevel); LogMessage(LogEventLevel.Information, "Websocket debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary>
/// SetErrorLogMinimumDebugLevel method
/// </summary>
public static void SetErrorLogMinimumDebugLevel(LogEventLevel level) public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
{ {
_errorLogLevelSwitch.MinimumLevel = level; _errorLogLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}] Error log level set to {1}\r\n", var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
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) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
CrestronConsole.PrintLine($"Error saving error log debug level setting: {err}"); LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
LogMessage(LogEventLevel.Information, "Error log debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary>
/// SetFileMinimumDebugLevel method
/// </summary>
public static void SetFileMinimumDebugLevel(LogEventLevel level) public static void SetFileMinimumDebugLevel(LogEventLevel level)
{ {
_fileLogLevelSwitch.MinimumLevel = level; _errorLogLevelSwitch.MinimumLevel = level;
CrestronConsole.ConsoleCommandResponse("[Application {0}] File log level set to {1}\r\n", var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level);
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) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
CrestronConsole.PrintLine($"Error saving file debug level setting: {err}"); LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
LogMessage(LogEventLevel.Information, "File debug level set to {0}", _websocketLoggingLevelSwitch.MinimumLevel);
} }
/// <summary> /// <summary>
/// Callback for console command /// Callback for console command
/// </summary> /// </summary>
/// <param name="stateString"></param> /// <param name="stateString"></param>
/// <summary>
/// SetDoNotLoadOnNextBootFromConsole method
/// </summary>
public static void SetDoNotLoadOnNextBootFromConsole(string stateString) public static void SetDoNotLoadOnNextBootFromConsole(string stateString)
{ {
try try
@ -583,9 +468,6 @@ namespace PepperDash.Core;
/// Callback for console command /// Callback for console command
/// </summary> /// </summary>
/// <param name="items"></param> /// <param name="items"></param>
/// <summary>
/// SetDebugFilterFromConsole method
/// </summary>
public static void SetDebugFilterFromConsole(string items) public static void SetDebugFilterFromConsole(string items)
{ {
var str = items.Trim(); var str = items.Trim();
@ -681,9 +563,6 @@ namespace PepperDash.Core;
/// </summary> /// </summary>
/// <param name="deviceKey"></param> /// <param name="deviceKey"></param>
/// <returns></returns> /// <returns></returns>
/// <summary>
/// GetDeviceDebugSettingsForKey method
/// </summary>
public static object GetDeviceDebugSettingsForKey(string deviceKey) public static object GetDeviceDebugSettingsForKey(string deviceKey)
{ {
return _contexts.GetDebugSettingsForKey(deviceKey); return _contexts.GetDebugSettingsForKey(deviceKey);
@ -696,15 +575,17 @@ namespace PepperDash.Core;
public static void SetDoNotLoadConfigOnNextBoot(bool state) public static void SetDoNotLoadConfigOnNextBoot(bool state)
{ {
DoNotLoadConfigOnNextBoot = state; DoNotLoadConfigOnNextBoot = state;
_contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state;
SaveMemoryOnTimeout();
CrestronConsole.ConsoleCommandResponse("[Application {0}], Do Not Load Config on Next Boot set to {1}", var err = CrestronDataStoreStatic.SetLocalBoolValue(DoNotLoadOnNextBootKey, state);
InitialParametersClass.ApplicationNumber, DoNotLoadConfigOnNextBoot);
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);
} }
/// <summary> /// <summary>
/// ShowDebugLog method ///
/// </summary> /// </summary>
public static void ShowDebugLog(string s) public static void ShowDebugLog(string s)
{ {
@ -720,9 +601,6 @@ namespace PepperDash.Core;
/// <param name="message">Message template</param> /// <param name="message">Message template</param>
/// <param name="device">Optional IKeyed device. If provided, the Key of the device will be added to the log message</param> /// <param name="device">Optional IKeyed device. If provided, the Key of the device will be added to the log message</param>
/// <param name="args">Args to put into message template</param> /// <param name="args">Args to put into message template</param>
/// <summary>
/// LogMessage method
/// </summary>
public static void LogMessage(Exception ex, string message, IKeyed device = null, params object[] args) public static void LogMessage(Exception ex, string message, IKeyed device = null, params object[] args)
{ {
using (LogContext.PushProperty("Key", device?.Key)) using (LogContext.PushProperty("Key", device?.Key))
@ -746,36 +624,21 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// Logs a message at the specified log level.
/// </summary>
/// <param name="level">Level to log at</param>
/// <param name="message">Message template</param>
/// <param name="args">Args to put into message template</param>
public static void LogMessage(LogEventLevel level, string message, params object[] args) public static void LogMessage(LogEventLevel level, string message, params object[] args)
{ {
_logger.Write(level, message, args); _logger.Write(level, message, args);
} }
/// <summary>
/// LogMessage method
/// </summary>
public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args) public static void LogMessage(LogEventLevel level, Exception ex, string message, params object[] args)
{ {
_logger.Write(level, ex, message, args); _logger.Write(level, ex, message, args);
} }
/// <summary>
/// LogMessage method
/// </summary>
public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args) public static void LogMessage(LogEventLevel level, IKeyed keyed, string message, params object[] args)
{ {
LogMessage(level, message, keyed, args); LogMessage(level, message, keyed, args);
} }
/// <summary>
/// LogMessage method
/// </summary>
public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args) public static void LogMessage(LogEventLevel level, Exception ex, IKeyed device, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", device?.Key)) using (LogContext.PushProperty("Key", device?.Key))
@ -785,9 +648,6 @@ namespace PepperDash.Core;
} }
#region Explicit methods for logging levels #region Explicit methods for logging levels
/// <summary>
/// LogVerbose method
/// </summary>
public static void LogVerbose(IKeyed keyed, string message, params object[] args) public static void LogVerbose(IKeyed keyed, string message, params object[] args)
{ {
using(LogContext.PushProperty("Key", keyed?.Key)) using(LogContext.PushProperty("Key", keyed?.Key))
@ -796,9 +656,6 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogVerbose method
/// </summary>
public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogVerbose(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using(LogContext.PushProperty("Key", keyed?.Key)) using(LogContext.PushProperty("Key", keyed?.Key))
@ -807,25 +664,16 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogVerbose method
/// </summary>
public static void LogVerbose(string message, params object[] args) public static void LogVerbose(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Verbose, message, args); _logger.Write(LogEventLevel.Verbose, message, args);
} }
/// <summary>
/// LogVerbose method
/// </summary>
public static void LogVerbose(Exception ex, string message, params object[] args) public static void LogVerbose(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Verbose, ex, message, args); _logger.Write(LogEventLevel.Verbose, ex, null, message, args);
} }
/// <summary>
/// LogDebug method
/// </summary>
public static void LogDebug(IKeyed keyed, string message, params object[] args) public static void LogDebug(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -834,9 +682,6 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogDebug method
/// </summary>
public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogDebug(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -845,25 +690,16 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogDebug method
/// </summary>
public static void LogDebug(string message, params object[] args) public static void LogDebug(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Debug, message, args); _logger.Write(LogEventLevel.Debug, message, args);
} }
/// <summary>
/// LogDebug method
/// </summary>
public static void LogDebug(Exception ex, string message, params object[] args) public static void LogDebug(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Debug, ex, null, message, args); _logger.Write(LogEventLevel.Debug, ex, null, message, args);
} }
/// <summary>
/// LogInformation method
/// </summary>
public static void LogInformation(IKeyed keyed, string message, params object[] args) public static void LogInformation(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -872,9 +708,6 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogInformation method
/// </summary>
public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogInformation(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -883,25 +716,16 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogInformation method
/// </summary>
public static void LogInformation(string message, params object[] args) public static void LogInformation(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Information, message, args); _logger.Write(LogEventLevel.Information, message, args);
} }
/// <summary>
/// LogInformation method
/// </summary>
public static void LogInformation(Exception ex, string message, params object[] args) public static void LogInformation(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Information, ex, message, args); _logger.Write(LogEventLevel.Information, ex, null, message, args);
} }
/// <summary>
/// LogWarning method
/// </summary>
public static void LogWarning(IKeyed keyed, string message, params object[] args) public static void LogWarning(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -910,9 +734,6 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogWarning method
/// </summary>
public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogWarning(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -921,25 +742,16 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogWarning method
/// </summary>
public static void LogWarning(string message, params object[] args) public static void LogWarning(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Warning, message, args); _logger.Write(LogEventLevel.Warning, message, args);
} }
/// <summary>
/// LogWarning method
/// </summary>
public static void LogWarning(Exception ex, string message, params object[] args) public static void LogWarning(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Warning, ex, message, args); _logger.Write(LogEventLevel.Warning, ex, null, message, args);
} }
/// <summary>
/// LogError method
/// </summary>
public static void LogError(IKeyed keyed, string message, params object[] args) public static void LogError(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -948,9 +760,6 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogError method
/// </summary>
public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogError(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -959,25 +768,16 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogError method
/// </summary>
public static void LogError(string message, params object[] args) public static void LogError(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Error, message, args); _logger.Write(LogEventLevel.Error, message, args);
} }
/// <summary>
/// LogError method
/// </summary>
public static void LogError(Exception ex, string message, params object[] args) public static void LogError(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Error, ex, message, args); _logger.Write(LogEventLevel.Error, ex, null, message, args);
} }
/// <summary>
/// LogFatal method
/// </summary>
public static void LogFatal(IKeyed keyed, string message, params object[] args) public static void LogFatal(IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -986,9 +786,6 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogFatal method
/// </summary>
public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args) public static void LogFatal(Exception ex, IKeyed keyed, string message, params object[] args)
{ {
using (LogContext.PushProperty("Key", keyed?.Key)) using (LogContext.PushProperty("Key", keyed?.Key))
@ -997,20 +794,14 @@ namespace PepperDash.Core;
} }
} }
/// <summary>
/// LogFatal method
/// </summary>
public static void LogFatal(string message, params object[] args) public static void LogFatal(string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Fatal, message, args); _logger.Write(LogEventLevel.Fatal, message, args);
} }
/// <summary>
/// LogFatal method
/// </summary>
public static void LogFatal(Exception ex, string message, params object[] args) public static void LogFatal(Exception ex, string message, params object[] args)
{ {
_logger.Write(LogEventLevel.Fatal, ex, message, args); _logger.Write(LogEventLevel.Fatal, ex, null, message, args);
} }
#endregion #endregion
@ -1218,7 +1009,7 @@ namespace PepperDash.Core;
} }
/// <summary> /// <summary>
/// Enumeration of ErrorLogLevel values /// Error level to for message to be logged at
/// </summary> /// </summary>
public enum ErrorLogLevel public enum ErrorLogLevel
{ {

View file

@ -17,8 +17,8 @@ using WebSocketSharp.Server;
using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2; using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
using WebSocketSharp.Net; using WebSocketSharp.Net;
namespace PepperDash.Core namespace PepperDash.Core;
{
/// <summary> /// <summary>
/// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected /// Provides a WebSocket-based logging sink for debugging purposes, allowing log events to be broadcast to connected
/// WebSocket clients. /// WebSocket clients.
@ -28,7 +28,7 @@ namespace PepperDash.Core
/// and uses a self-signed certificate for SSL/TLS encryption.</remarks> /// and uses a self-signed certificate for SSL/TLS encryption.</remarks>
public class DebugWebsocketSink : ILogEventSink, IKeyed public class DebugWebsocketSink : ILogEventSink, IKeyed
{ {
private WebSocketServer _wsServer; private HttpServer _httpsServer;
private readonly string _path = "/debug/join/"; private readonly string _path = "/debug/join/";
private const string _certificateName = "selfCres"; private const string _certificateName = "selfCres";
@ -41,8 +41,8 @@ namespace PepperDash.Core
{ get { get
{ {
if(_wsServer == null) return 0; if(_httpsServer == null) return 0;
return _wsServer.Port; return _httpsServer.Port;
} }
} }
@ -55,15 +55,15 @@ namespace PepperDash.Core
{ {
get get
{ {
if (_wsServer == null) return ""; if (_httpsServer == null) return "";
return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_wsServer.Port}{_wsServer.WebSocketServices[_path].Path}"; return $"wss://{CrestronEthernetHelper.GetEthernetParameter(CrestronEthernetHelper.ETHERNET_PARAMETER_TO_GET.GET_CURRENT_IP_ADDRESS, 0)}:{_httpsServer.Port}{_httpsServer.WebSocketServices[_path].Path}";
} }
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the WebSocket server is currently listening for incoming connections. /// Gets a value indicating whether the HTTPS server is currently listening for incoming connections.
/// </summary> /// </summary>
public bool IsRunning { get => _wsServer?.IsListening ?? false; } public bool IsRunning { get => _httpsServer?.IsListening ?? false; }
/// <inheritdoc/> /// <inheritdoc/>
public string Key => "DebugWebsocketSink"; public string Key => "DebugWebsocketSink";
@ -111,13 +111,15 @@ namespace PepperDash.Core
//Crestron fails to let us do this...perhaps it should be done through their Dll's but haven't tested //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.CertificatePassword = _certificatePassword;
utility.WriteCertificate(certificate, @"\user\", _certificateName); utility.WriteCertificate(certificate, @$"{separator}user{separator}", _certificateName);
} }
catch (Exception ex) catch (Exception ex)
{ {
//Debug.Console(0, "WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace); //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); CrestronConsole.PrintLine("WSS CreateCert Failed\r\n{0}\r\n{1}", ex.Message, ex.StackTrace);
} }
} }
@ -130,12 +132,12 @@ namespace PepperDash.Core
/// <param name="logEvent">The log event to be formatted and broadcasted. Cannot be null.</param> /// <param name="logEvent">The log event to be formatted and broadcasted. Cannot be null.</param>
public void Emit(LogEvent logEvent) public void Emit(LogEvent logEvent)
{ {
if (_wsServer == null || !_wsServer.IsListening) return; if (_httpsServer == null || !_httpsServer.IsListening) return;
var sw = new StringWriter(); var sw = new StringWriter();
_textFormatter.Format(logEvent, sw); _textFormatter.Format(logEvent, sw);
_wsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString()); _httpsServer.WebSocketServices[_path].Sessions.Broadcast(sw.ToString());
} }
/// <summary> /// <summary>
@ -147,7 +149,7 @@ namespace PepperDash.Core
/// <param name="port">The port number on which the WebSocket server will listen. Must be a valid, non-negative port number.</param> /// <param name="port">The port number on which the WebSocket server will listen. Must be a valid, non-negative port number.</param>
public void StartServerAndSetPort(int port) public void StartServerAndSetPort(int port)
{ {
Debug.LogInformation("Starting Websocket Server on port: {0}", port); Debug.Console(0, "Starting Websocket Server on port: {0}", port);
Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword); Start(port, $"\\user\\{_certificateName}.pfx", _certificatePassword);
@ -157,36 +159,28 @@ namespace PepperDash.Core
{ {
try try
{ {
ServerSslConfiguration sslConfig = null; _httpsServer = new HttpServer(port, true);
if (!string.IsNullOrWhiteSpace(certPath)) if (!string.IsNullOrWhiteSpace(certPath))
{ {
Debug.LogInformation("Assigning SSL Configuration"); Debug.Console(0, "Assigning SSL Configuration");
sslConfig = new ServerSslConfiguration(new X509Certificate2(certPath, certPassword))
{ _httpsServer.SslConfiguration.ServerCertificate = new X509Certificate2(certPath, certPassword);
ClientCertificateRequired = false, _httpsServer.SslConfiguration.ClientCertificateRequired = false;
CheckCertificateRevocation = false, _httpsServer.SslConfiguration.CheckCertificateRevocation = false;
EnabledSslProtocols = SslProtocols.Tls12, _httpsServer.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
//this is just to test, you might want to actually validate //this is just to test, you might want to actually validate
ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => _httpsServer.SslConfiguration.ClientCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
{ {
Debug.LogInformation("HTTPS ClientCerticateValidation Callback triggered"); Debug.Console(0, "HTTPS ClientCerticateValidation Callback triggered");
return true; return true;
}
}; };
} }
Debug.Console(0, "Adding Debug Client Service");
_wsServer = new WebSocketServer(port, true); _httpsServer.AddWebSocketService<DebugClient>(_path);
if (sslConfig != null) Debug.Console(0, "Assigning Log Info");
{ _httpsServer.Log.Level = LogLevel.Trace;
_wsServer.SslConfiguration.ServerCertificate = sslConfig.ServerCertificate; _httpsServer.Log.Output = (d, s) =>
}
Debug.LogInformation("Adding Debug Client Service");
_wsServer.AddWebSocketService<DebugClient>(_path);
Debug.LogInformation("Assigning Log Info");
_wsServer.Log.Level = LogLevel.Trace;
_wsServer.Log.Output = (d, s) =>
{ {
uint level; uint level;
@ -215,16 +209,16 @@ namespace PepperDash.Core
break; break;
} }
Debug.LogInformation("{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s); Debug.Console(level, "{1} {0}\rCaller:{2}\rMessage:{3}\rs:{4}", d.Level.ToString(), d.Date.ToString(), d.Caller.ToString(), d.Message, s);
}; };
Debug.LogInformation("Starting"); Debug.Console(0, "Starting");
_wsServer.Start(); _httpsServer.Start();
Debug.LogInformation("Ready"); Debug.Console(0, "Ready");
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.LogError("WebSocket Failed to start {0}", ex.Message); Debug.Console(0, "WebSocket Failed to start {0}", ex.Message);
} }
} }
@ -235,10 +229,10 @@ namespace PepperDash.Core
/// calling this method, the server will no longer accept or process incoming connections.</remarks> /// calling this method, the server will no longer accept or process incoming connections.</remarks>
public void StopServer() public void StopServer()
{ {
Debug.LogInformation("Stopping Websocket Server"); Debug.Console(0, "Stopping Websocket Server");
_wsServer?.Stop(); _httpsServer?.Stop();
_wsServer = null; _httpsServer = null;
} }
} }
@ -297,10 +291,11 @@ namespace PepperDash.Core
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DebugClient"/> class. /// Initializes a new instance of the <see cref="DebugClient"/> class.
/// </summary> /// </summary>
/// <remarks>This constructor creates a new <see cref="DebugClient"/> instance and logs its creation.</remarks> /// <remarks>This constructor creates a new <see cref="DebugClient"/> instance and logs its
/// creation using the <see cref="Debug.Console(int, string)"/> method with a debug level of 0.</remarks>
public DebugClient() public DebugClient()
{ {
Debug.LogInformation("DebugClient Created"); Debug.Console(0, "DebugClient Created");
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -309,7 +304,7 @@ namespace PepperDash.Core
base.OnOpen(); base.OnOpen();
var url = Context.WebSocket.Url; var url = Context.WebSocket.Url;
Debug.LogInformation("New WebSocket Connection from: {0}", url); Debug.Console(0, Debug.ErrorLogLevel.Notice, "New WebSocket Connection from: {0}", url);
_connectionTime = DateTime.Now; _connectionTime = DateTime.Now;
} }
@ -319,7 +314,7 @@ namespace PepperDash.Core
{ {
base.OnMessage(e); base.OnMessage(e);
Debug.LogInformation("WebSocket UiClient Message: {0}", e.Data); Debug.Console(0, "WebSocket UiClient Message: {0}", e.Data);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -327,7 +322,7 @@ namespace PepperDash.Core
{ {
base.OnClose(e); base.OnClose(e);
Debug.LogInformation("WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason); Debug.Console(0, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Closing: {0} reason: {1}", e.Code, e.Reason);
} }
@ -336,7 +331,6 @@ namespace PepperDash.Core
{ {
base.OnError(e); base.OnError(e);
Debug.LogError("WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message); Debug.Console(2, Debug.ErrorLogLevel.Notice, "WebSocket UiClient Error: {0} message: {1}", e.Exception, e.Message);
}
} }
} }