extern alias Full; using System; using System.Collections.Generic; using System.Text.RegularExpressions; using Crestron.SimplSharp; using Crestron.SimplSharp.Reflection; using Crestron.SimplSharp.CrestronLogger; using Crestron.SimplSharp.CrestronIO; using Full.Newtonsoft.Json; using PepperDash.Core.DebugThings; using Serilog; using Serilog.Core; using Serilog.Events; using Serilog.Formatting.Compact; using Serilog.Formatting.Json; namespace PepperDash.Core { public enum eDebugLevel { Information = 0, Warning = 1, Error = 2, Fatal = 3, Debug = 4, Verbose = 5, } /// /// Contains debug commands for use in various situations /// public static class Debug { //private static Dictionary> _logActions = new Dictionary>() //{ // {0, (s) => _logger.Information(s) }, // {1, (s) => _logger.Warning(s) }, // {2, (s) => _logger.Error(s) }, // {3, (s) => _logger.Fatal(s) }, // {4, (s) => _logger.Debug(s) }, // {5, (s) => _logger.Verbose(s) }, //}; private static Logger _logger; private static LoggingLevelSwitch _consoleLoggingLevelSwitch; private static LoggingLevelSwitch _websocketLoggingLevelSwitch; private static DebugWebsocketSink _websocketSink; public static DebugWebsocketSink WebsocketSink { get { return _websocketSink; } } /// /// Describes the folder location where a given program stores it's debug level memory. By default, the /// file written will be named appNdebug where N is 1-10. /// public static string OldFilePathPrefix = @"\nvram\debug\"; /// /// Describes the new folder location where a given program stores it's debug level memory. By default, the /// file written will be named appNdebug where N is 1-10. /// public static string NewFilePathPrefix = @"\user\debug\"; /// /// The name of the file containing the current debug settings. /// public static string FileName = string.Format(@"app{0}Debug.json", InitialParametersClass.ApplicationNumber); /// /// Debug level to set for a given program. /// public static int Level { get; private set; } /// /// When this is true, the configuration file will NOT be loaded until triggered by either a console command or a signal /// public static bool DoNotLoadOnNextBoot { get; private set; } private static DebugContextCollection _contexts; private const int SaveTimeoutMs = 30000; private static bool _runningOnAppliance = CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance; /// /// Version for the currently loaded PepperDashCore dll /// public static string PepperDashCoreVersion { get; private set; } private static CTimer _saveTimer; /// /// When true, the IncludedExcludedKeys dict will contain keys to include. /// When false (default), IncludedExcludedKeys will contain keys to exclude. /// private static bool _excludeAllMode; //static bool ExcludeNoKeyMessages; private static readonly Dictionary IncludedExcludedKeys; static Debug() { _consoleLoggingLevelSwitch = new LoggingLevelSwitch(initialMinimumLevel: LogEventLevel.Information); _websocketLoggingLevelSwitch = new LoggingLevelSwitch(); _websocketSink = new DebugWebsocketSink(new JsonFormatter()); // Instantiate the root logger _logger = new LoggerConfiguration() //.WriteTo.Logger(lc => lc //.WriteTo.Console(levelSwitch: _consoleLoggingLevelSwitch)) .WriteTo.Sink(_websocketSink, levelSwitch: _websocketLoggingLevelSwitch) .WriteTo.File(@"\user\debug\global-log-{Date}.txt" , rollingInterval: RollingInterval.Day , restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Debug) .CreateLogger(); // Get the assembly version and print it to console and the log GetVersion(); string msg = ""; if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) { msg = string.Format("[App {0}] Using PepperDash_Core v{1}", InitialParametersClass.ApplicationNumber, PepperDashCoreVersion); } else if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) { msg = string.Format("[Room {0}] Using PepperDash_Core v{1}", InitialParametersClass.RoomId, PepperDashCoreVersion); } CrestronConsole.PrintLine(msg); LogError(ErrorLogLevel.Notice, msg); IncludedExcludedKeys = new Dictionary(); //CrestronDataStoreStatic.InitCrestronDataStore(); if (CrestronEnvironment.RuntimeEnvironment == eRuntimeEnvironment.SimplSharpPro) { // Add command to console CrestronConsole.AddNewConsoleCommand(SetDoNotLoadOnNextBootFromConsole, "donotloadonnextboot", "donotloadonnextboot:P [true/false]: Should the application load on next boot", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(SetDebugFromConsole, "appdebug", "appdebug:P [0-2]: Sets the application's console debug message level", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(ShowDebugLog, "appdebuglog", "appdebuglog:P [all] Use \"all\" for full log.", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(s => CrestronLogger.Clear(false), "appdebugclear", "appdebugclear:P Clears the current custom log", ConsoleAccessLevelEnum.AccessOperator); CrestronConsole.AddNewConsoleCommand(SetDebugFilterFromConsole, "appdebugfilter", "appdebugfilter [params]", ConsoleAccessLevelEnum.AccessOperator); } CrestronEnvironment.ProgramStatusEventHandler += CrestronEnvironment_ProgramStatusEventHandler; LoadMemory(); var context = _contexts.GetOrCreateItem("DEFAULT"); Level = context.Level; DoNotLoadOnNextBoot = context.DoNotLoadOnNextBoot; if(DoNotLoadOnNextBoot) 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)); try { if (InitialParametersClass.NumberOfRemovableDrives > 0) { CrestronConsole.PrintLine("{0} RM Drive(s) Present.", InitialParametersClass.NumberOfRemovableDrives); CrestronLogger.Initialize(2, LoggerModeEnum.DEFAULT); // Use RM instead of DEFAULT as not to double-up console messages. } else CrestronConsole.PrintLine("No RM Drive(s) Present."); } catch (Exception e) { CrestronConsole.PrintLine("Initializing of CrestronLogger failed: {0}", e); } } private static void GetVersion() { var assembly = Assembly.GetExecutingAssembly(); var ver = assembly .GetCustomAttributes(typeof (AssemblyInformationalVersionAttribute), false); if (ver != null && ver.Length > 0) { var verAttribute = ver[0] as AssemblyInformationalVersionAttribute; if (verAttribute != null) { PepperDashCoreVersion = verAttribute.InformationalVersion; } } else { var version = assembly.GetName().Version; PepperDashCoreVersion = string.Format("{0}.{1}.{2}.{3}", version.Major, version.Minor, version.Build, version.Revision); } } /// /// Used to save memory when shutting down /// /// static void CrestronEnvironment_ProgramStatusEventHandler(eProgramStatusEventType programEventType) { if (programEventType == eProgramStatusEventType.Stopping) { Log.CloseAndFlush(); if (_saveTimer != null) { _saveTimer.Stop(); _saveTimer = null; } Console(0, "Saving debug settings"); SaveMemory(); } } /// /// Callback for console command /// /// public static void SetDebugFromConsole(string levelString) { try { if (string.IsNullOrEmpty(levelString.Trim())) { CrestronConsole.ConsoleCommandResponse("AppDebug level = {0}", Level); return; } SetDebugLevel(Convert.ToInt32(levelString)); } catch { CrestronConsole.ConsoleCommandResponse("Usage: appdebug:P [0-2]"); } } public static void SetConsoleDebugLevel(LogEventLevel level) { _consoleLoggingLevelSwitch.MinimumLevel = level; } public static void SetWebSocketDebugLevel(LogEventLevel level) { } /// /// Callback for console command /// /// public static void SetDoNotLoadOnNextBootFromConsole(string stateString) { try { if (string.IsNullOrEmpty(stateString.Trim())) { CrestronConsole.ConsoleCommandResponse("DoNotLoadOnNextBoot = {0}", DoNotLoadOnNextBoot); return; } SetDoNotLoadOnNextBoot(Boolean.Parse(stateString)); } catch { CrestronConsole.ConsoleCommandResponse("Usage: donotloadonnextboot:P [true/false]"); } } /// /// Callback for console command /// /// public static void SetDebugFilterFromConsole(string items) { var str = items.Trim(); if (str == "?") { CrestronConsole.ConsoleCommandResponse("Usage:\r APPDEBUGFILTER key1 key2 key3....\r " + "+all: at beginning puts filter into 'default include' mode\r" + " All keys that follow will be excluded from output.\r" + "-all: at beginning puts filter into 'default exclude all' mode.\r" + " All keys that follow will be the only keys that are shown\r" + "+nokey: Enables messages with no key (default)\r" + "-nokey: Disables messages with no key.\r" + "(nokey settings are independent of all other settings)"); return; } var keys = Regex.Split(str, @"\s*"); foreach (var keyToken in keys) { var lkey = keyToken.ToLower(); if (lkey == "+all") { IncludedExcludedKeys.Clear(); _excludeAllMode = false; } else if (lkey == "-all") { IncludedExcludedKeys.Clear(); _excludeAllMode = true; } //else if (lkey == "+nokey") //{ // ExcludeNoKeyMessages = false; //} //else if (lkey == "-nokey") //{ // ExcludeNoKeyMessages = true; //} else { string key; if (lkey.StartsWith("-")) { key = lkey.Substring(1); // if in exclude all mode, we need to remove this from the inclusions if (_excludeAllMode) { if (IncludedExcludedKeys.ContainsKey(key)) IncludedExcludedKeys.Remove(key); } // otherwise include all mode, add to the exclusions else { IncludedExcludedKeys[key] = new object(); } } else if (lkey.StartsWith("+")) { key = lkey.Substring(1); // if in exclude all mode, we need to add this as inclusion if (_excludeAllMode) { IncludedExcludedKeys[key] = new object(); } // otherwise include all mode, remove this from exclusions else { if (IncludedExcludedKeys.ContainsKey(key)) IncludedExcludedKeys.Remove(key); } } } } } /// /// Sets the debug level /// /// Valid values 0 (no debug), 1 (critical), 2 (all messages) public static void SetDebugLevel(int level) { _consoleLoggingLevelSwitch.MinimumLevel = (LogEventLevel)level; if (level <= 5) { Level = level; _contexts.GetOrCreateItem("DEFAULT").Level = level; SaveMemoryOnTimeout(); CrestronConsole.ConsoleCommandResponse("[Application {0}], Debug level set to {1}", InitialParametersClass.ApplicationNumber, Level); //var err = CrestronDataStoreStatic.SetLocalUintValue("DebugLevel", level); //if (err != CrestronDataStore.CDS_ERROR.CDS_SUCCESS) // CrestronConsole.PrintLine("Error saving console debug level setting: {0}", err); } } /// /// 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 SetDoNotLoadOnNextBoot(bool state) { DoNotLoadOnNextBoot = state; _contexts.GetOrCreateItem("DEFAULT").DoNotLoadOnNextBoot = state; SaveMemoryOnTimeout(); CrestronConsole.ConsoleCommandResponse("[Application {0}], Do Not Start on Next Boot set to {1}", InitialParametersClass.ApplicationNumber, DoNotLoadOnNextBoot); } /// /// /// public static void ShowDebugLog(string s) { var loglist = CrestronLogger.PrintTheLog(s.ToLower() == "all"); foreach (var l in loglist) CrestronConsole.ConsoleCommandResponse(l + CrestronEnvironment.NewLine); } /// /// Prints message to console if current debug level is equal to or higher than the level of this message. /// Uses CrestronConsole.PrintLine. /// /// /// Console format string /// Object parameters public static void Console(uint level, string format, params object[] items) { if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Server) { var logString = string.Format("[level {0}] {1}", level, string.Format(format, items)); LogError(ErrorLogLevel.Notice, logString); return; } _logger.Write((LogEventLevel)level, format, items); if (_runningOnAppliance) { 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 /// public static void Console(uint level, IKeyed dev, string format, params object[] items) { var log = _logger.ForContext("key", dev.Key); var message = string.Format(format, items); log.Write((LogEventLevel)level, 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. /// public static void Console(uint level, IKeyed dev, ErrorLogLevel errorLogLevel, string format, params object[] items) { var str = string.Format("[{0}] {1}", dev.Key, string.Format(format, items)); if (errorLogLevel != ErrorLogLevel.None) { LogError(errorLogLevel, str); } var log = _logger.ForContext("Key", dev.Key); log.Write((LogEventLevel)level, format, items); if (Level >= level) { Console(level, str); } } /// /// Logs to Console when at-level, and all messages to error log /// public static void Console(uint level, ErrorLogLevel errorLogLevel, string format, params object[] items) { var str = string.Format(format, items); if (errorLogLevel != ErrorLogLevel.None) { LogError(errorLogLevel, str); } if (Level >= level) { Console(level, str); } } /// /// 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. /// public static void ConsoleWithLog(uint level, string format, params object[] 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. /// public static void ConsoleWithLog(uint level, IKeyed dev, string format, params object[] items) { var str = string.Format(format, items); if (Level >= level) ConsoleWithLog(level, "[{0}] {1}", dev.Key, str); } /// /// Prints to log and error log /// /// /// public static void LogError(ErrorLogLevel errorLogLevel, string str) { var msg = _runningOnAppliance ? string.Format("App {0}:{1}", InitialParametersClass.ApplicationNumber, str) : string.Format("Room {0}:{1}", InitialParametersClass.RoomId, str); switch (errorLogLevel) { case ErrorLogLevel.Error: ErrorLog.Error(msg); break; case ErrorLogLevel.Warning: ErrorLog.Warn(msg); break; case ErrorLogLevel.Notice: ErrorLog.Notice(msg); break; } } /// /// Writes the memory object after timeout /// static void SaveMemoryOnTimeout() { Console(0, "Saving debug settings"); if (_saveTimer == null) _saveTimer = new CTimer(o => { _saveTimer = null; SaveMemory(); }, SaveTimeoutMs); else _saveTimer.Reset(SaveTimeoutMs); } /// /// Writes the memory - use SaveMemoryOnTimeout /// static void SaveMemory() { //var dir = @"\NVRAM\debug"; //if (!Directory.Exists(dir)) // Directory.Create(dir); var fileName = GetMemoryFileName(); Console(0, ErrorLogLevel.Notice, "Loading debug settings file from {0}", fileName); using (var sw = new StreamWriter(fileName)) { var json = JsonConvert.SerializeObject(_contexts); sw.Write(json); sw.Flush(); } } /// /// /// static void LoadMemory() { var file = GetMemoryFileName(); if (File.Exists(file)) { using (var sr = new StreamReader(file)) { var json = sr.ReadToEnd(); _contexts = JsonConvert.DeserializeObject(json); if (_contexts != null) { Console(1, "Debug memory restored from file"); return; } } } _contexts = new DebugContextCollection(); } /// /// Helper to get the file path for this app's debug memory /// static string GetMemoryFileName() { if (CrestronEnvironment.DevicePlatform == eDevicePlatform.Appliance) { CheckForMigration(); return string.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); } return string.Format("{0}{1}user{1}debugSettings{1}{2}.json",Directory.GetApplicationRootDirectory(), Path.DirectorySeparatorChar, InitialParametersClass.RoomId); } private static void CheckForMigration() { var oldFilePath = String.Format(@"\nvram\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); var newFilePath = String.Format(@"\user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber); //check for file at old path if (!File.Exists(oldFilePath)) { Console(0, ErrorLogLevel.Notice, String.Format( @"Debug settings file migration not necessary. Using file at \user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber)); return; } //create the new directory if it doesn't already exist if (!Directory.Exists(@"\user\debugSettings")) { Directory.CreateDirectory(@"\user\debugSettings"); } Console(0, ErrorLogLevel.Notice, String.Format( @"File found at \nvram\debugSettings\program{0}. Migrating to \user\debugSettings\program{0}", InitialParametersClass.ApplicationNumber)); //Copy file from old path to new path, then delete it. This will overwrite the existing file File.Copy(oldFilePath, newFilePath, true); File.Delete(oldFilePath); //Check if the old directory is empty, then delete it if it is if (Directory.GetFiles(@"\nvram\debugSettings").Length > 0) { return; } Directory.Delete(@"\nvram\debugSettings"); } /// /// Error level to for message to be logged at /// public enum ErrorLogLevel { /// /// Error /// Error, /// /// Warning /// Warning, /// /// Notice /// Notice, /// /// None /// None, } } }