feat: filter log messages based on the device key

This commit is contained in:
Andrew Welker
2025-05-07 07:56:53 -05:00
parent 2368f0c8cc
commit ed2b90d8e7

View File

@@ -3,6 +3,7 @@ using Crestron.SimplSharp.CrestronDataStore;
using Crestron.SimplSharp.CrestronIO; using Crestron.SimplSharp.CrestronIO;
using Crestron.SimplSharp.CrestronLogger; using Crestron.SimplSharp.CrestronLogger;
using Newtonsoft.Json; using Newtonsoft.Json;
using Org.BouncyCastle.Crypto.Prng;
using PepperDash.Core.Logging; using PepperDash.Core.Logging;
using Serilog; using Serilog;
using Serilog.Context; using Serilog.Context;
@@ -13,7 +14,9 @@ using Serilog.Formatting.Json;
using Serilog.Templates; using Serilog.Templates;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace PepperDash.Core namespace PepperDash.Core
@@ -25,8 +28,9 @@ namespace PepperDash.Core
{ {
private static readonly string LevelStoreKey = "ConsoleDebugLevel"; private static readonly string LevelStoreKey = "ConsoleDebugLevel";
private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel"; private static readonly string WebSocketLevelStoreKey = "WebsocketDebugLevel";
private static readonly string ErrorLogLevelStoreKey = "ErrorLogDebugLevel"; private static readonly string Error_logLevelstoreKey = "ErrorLogDebugLevel";
private static readonly string FileLevelStoreKey = "FileDebugLevel"; private static readonly string FileLevelStoreKey = "FileDebugLevel";
private static readonly string DeviceKeysStoreKey = "DeviceKeys";
private static readonly Dictionary<uint, LogEventLevel> _logLevels = new Dictionary<uint, LogEventLevel>() private static readonly Dictionary<uint, LogEventLevel> _logLevels = new Dictionary<uint, LogEventLevel>()
{ {
@@ -44,10 +48,12 @@ namespace PepperDash.Core
private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch; private static readonly LoggingLevelSwitch _websocketLoggingLevelSwitch;
private static readonly LoggingLevelSwitch _errorLogLevelSwitch; private static readonly LoggingLevelSwitch _error_logLevelswitch;
private static readonly LoggingLevelSwitch _fileLevelSwitch; private static readonly LoggingLevelSwitch _fileLevelSwitch;
private static List<string> deviceKeysFilterList = new List<string>();
public static LogEventLevel WebsocketMinimumLogLevel public static LogEventLevel WebsocketMinimumLogLevel
{ {
get { return _websocketLoggingLevelSwitch.MinimumLevel; } get { return _websocketLoggingLevelSwitch.MinimumLevel; }
@@ -124,19 +130,21 @@ namespace PepperDash.Core
var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey); var defaultWebsocketLevel = GetStoredLogEventLevel(WebSocketLevelStoreKey);
var defaultErrorLogLevel = GetStoredLogEventLevel(ErrorLogLevelStoreKey); var defaultErrorLogLevel = GetStoredLogEventLevel(Error_logLevelstoreKey);
var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey); var defaultFileLogLevel = GetStoredLogEventLevel(FileLevelStoreKey);
deviceKeysFilterList = GetStoredDeviceKeys();
_consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel); _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultConsoleLevel);
_websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel); _websocketLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultWebsocketLevel);
_errorLogLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel); _error_logLevelswitch = new LoggingLevelSwitch(initialMinimumLevel: defaultErrorLogLevel);
_fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel); _fileLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: defaultFileLogLevel);
_websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true)); _websocketSink = new DebugWebsocketSink(new JsonFormatter(renderMessage: true));
var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ? var logFilePath = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance ?
$@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" : $@"{Directory.GetApplicationRootDirectory()}{Path.DirectorySeparatorChar}user{Path.DirectorySeparatorChar}debug{Path.DirectorySeparatorChar}app{InitialParametersClass.ApplicationNumber}{Path.DirectorySeparatorChar}global-log.log" :
@@ -152,9 +160,9 @@ 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: _consoleLoggingLevelSwitch) .WriteTo.Conditional(ConsoleFilter,c => c.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(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch)
.WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _errorLogLevelSwitch) .WriteTo.Sink(new DebugErrorLogSink(new ExpressionTemplate(errorLogTemplate)), levelSwitch: _error_logLevelswitch))
.WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath, .WriteTo.File(new RenderedCompactJsonFormatter(), logFilePath,
rollingInterval: RollingInterval.Day, rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Debug, restrictedToMinimumLevel: LogEventLevel.Debug,
@@ -206,12 +214,15 @@ namespace PepperDash.Core
CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug",
"appdebug:P [0-5]: Sets the application's console debug message level", "appdebug:P [0-5]: Sets the application's console debug message level",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog",
"appdebuglog:P [all] Use \"all\" for full log.", "appdebuglog:P [all] Use \"all\" for full log.",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear",
"appdebugclear:P Clears the current custom log", "appdebugclear:P Clears the current custom log",
ConsoleAccessLevelEnum.AccessOperator); ConsoleAccessLevelEnum.AccessOperator);
CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter",
"appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator);
} }
@@ -233,6 +244,23 @@ namespace PepperDash.Core
}; };
} }
private static bool ConsoleFilter(LogEvent evt)
{
// print error and fatal messages no matter what
if(evt.Level >= LogEventLevel.Error)
{
return true;
}
// message is a keyed message
if(evt.Properties.TryGetValue("Key", out var value) && value is ScalarValue sv && sv.Value is string rawValue)
{
return deviceKeysFilterList.Count == 0 || deviceKeysFilterList.Contains(rawValue);
}
return true;
}
public static void UpdateLoggerConfiguration(LoggerConfiguration config) public static void UpdateLoggerConfiguration(LoggerConfiguration config)
{ {
_loggerConfiguration = config; _loggerConfiguration = config;
@@ -273,6 +301,39 @@ namespace PepperDash.Core
} }
} }
private static List<string> GetStoredDeviceKeys()
{
try
{
var result = CrestronDataStoreStatic.GetLocalStringValue(DeviceKeysStoreKey, out string devicesString);
if (result != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
{
CrestronConsole.Print($"Unable to retrieve stored log level for DEVICE_KEYS.\r\nError: {result}.\r\nShowing Log statements from all devices\r\n");
return new List<string>();
}
var devices = devicesString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(d => d.Trim())
.ToList();
if (devices.Count == 0)
{
CrestronConsole.PrintLine($"Showing log statements from all devices");
return devices;
}
CrestronConsole.PrintLine($"Showing log statements from devices: {string.Join(", ", devices)}");
return devices;
}
catch (Exception ex)
{
CrestronConsole.PrintLine($"Exception retrieving log level for DEVICE_KEYS: {ex.Message}");
return new List<string>();
}
}
private static void GetVersion() private static void GetVersion()
{ {
var assembly = Assembly.GetExecutingAssembly(); var assembly = Assembly.GetExecutingAssembly();
@@ -342,29 +403,58 @@ namespace PepperDash.Core
return; return;
} }
if(int.TryParse(levelString, out var levelInt)) if (int.TryParse(levelString, out var levelInt))
{ {
if(levelInt < 0 || levelInt > 5) 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"); CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level. If using a number, value must be between 0-5");
return; return;
} }
SetDebugLevel((uint) levelInt); SetDebugLevel((uint)levelInt);
return; return;
} }
if(Enum.TryParse<LogEventLevel>(levelString, out var levelEnum)) if (Enum.TryParse<LogEventLevel>(levelString, out var levelEnum))
{ {
SetDebugLevel(levelEnum); SetDebugLevel(levelEnum);
return; return;
} }
CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level"); CrestronConsole.ConsoleCommandResponse($"Error: Unable to parse {levelString} to valid log level");
} }
catch catch
{ {
CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]"); CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-5]");
}
}
/// <summary>
/// Sets the device filter for the debug messages
/// </summary>
/// <param name="deviceKeys">Array of keys to filter. Empty array is all devices</param>
public static void SetDeviceFilter(string[] deviceKeys)
{
var newKeys = deviceKeys.ToList();
if (newKeys.Count == 0)
{
deviceKeysFilterList.Clear();
} }
else
{
deviceKeysFilterList = deviceKeysFilterList.Union(newKeys).ToList();
}
var joinedList = string.Join(", ", deviceKeysFilterList);
CrestronConsole.ConsoleCommandResponse($"[Application {InitialParametersClass.ApplicationNumber}], Device filter set to {(deviceKeysFilterList.Count > 0 ? joinedList : "all devices")}\r\n");
var err = CrestronDataStoreStatic.SetLocalStringValue(DeviceKeysStoreKey, joinedList);
CrestronConsole.ConsoleCommandResponse($"Store result: {err}");
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
CrestronConsole.PrintLine($"Error saving console debug level setting: {err}");
} }
/// <summary> /// <summary>
@@ -380,6 +470,8 @@ namespace PepperDash.Core
CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}"); CrestronConsole.ConsoleCommandResponse($"{level} not valid. Setting level to {logLevel}");
SetDebugLevel(logLevel); SetDebugLevel(logLevel);
return;
} }
SetDebugLevel(logLevel); SetDebugLevel(logLevel);
@@ -416,9 +508,9 @@ namespace PepperDash.Core
public static void SetErrorLogMinimumDebugLevel(LogEventLevel level) public static void SetErrorLogMinimumDebugLevel(LogEventLevel level)
{ {
_errorLogLevelSwitch.MinimumLevel = level; _error_logLevelswitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(Error_logLevelstoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err); LogMessage(LogEventLevel.Information, "Error saving Error Log debug level setting: {error}", err);
@@ -428,9 +520,9 @@ namespace PepperDash.Core
public static void SetFileMinimumDebugLevel(LogEventLevel level) public static void SetFileMinimumDebugLevel(LogEventLevel level)
{ {
_errorLogLevelSwitch.MinimumLevel = level; _error_logLevelswitch.MinimumLevel = level;
var err = CrestronDataStoreStatic.SetLocalUintValue(ErrorLogLevelStoreKey, (uint)level); var err = CrestronDataStoreStatic.SetLocalUintValue(Error_logLevelstoreKey, (uint)level);
if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS)
LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err); LogMessage(LogEventLevel.Information, "Error saving File debug level setting: {error}", err);
@@ -466,78 +558,34 @@ namespace PepperDash.Core
/// <param name="items"></param> /// <param name="items"></param>
public static void SetDebugFilterFromConsole(string items) public static void SetDebugFilterFromConsole(string items)
{ {
var str = items.Trim(); var arguments = items.Trim();
if (str == "?") if (arguments == "?")
{ {
CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + CrestronConsole.ConsoleCommandResponse("Usage:\r\n APPDEBUGFILTER [all] [key1 key2 key3...]\r\n " +
"+all: at beginning puts filter into 'default include' mode\r" + "all: clears filters and shows all device messages\r\n" +
" All keys that follow will be excluded from output.\r" + "key1 key2 key3...: adds keys to the filter list\r\n" +
"-all: at beginning puts filter into 'default exclude all' mode.\r" + "non-keyed messages will ALWAYS be shown based on their level\r\n"
" 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; return;
} }
var keys = Regex.Split(str, @"\s*"); if (string.IsNullOrEmpty(arguments))
foreach (var keyToken in keys) {
{ CrestronConsole.ConsoleCommandResponse("APPDEBUGFILTER: No arguments provided");
var lkey = keyToken.ToLower(); return;
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(); var keys = arguments.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
// otherwise include all mode, remove this from exclusions if (keys[0].ToLower() == "all")
else {
{ CrestronConsole.ConsoleCommandResponse("Clearing all filters");
if (IncludedExcludedKeys.ContainsKey(key))
IncludedExcludedKeys.Remove(key); SetDeviceFilter(new string[] { });
} return;
} }
}
} SetDeviceFilter(keys);
} }