From 929dccb75a1405737975d76648e015a3e7c00177 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 7 Oct 2017 23:07:10 -0400 Subject: reorganise repo structure --- src/SMAPI/Framework/Monitor.cs | 194 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/SMAPI/Framework/Monitor.cs (limited to 'src/SMAPI/Framework/Monitor.cs') diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs new file mode 100644 index 00000000..bf338386 --- /dev/null +++ b/src/SMAPI/Framework/Monitor.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using StardewModdingAPI.Framework.Logging; + +namespace StardewModdingAPI.Framework +{ + /// Encapsulates monitoring and logic for a given module. + internal class Monitor : IMonitor + { + /********* + ** Properties + *********/ + /// The name of the module which logs messages using this instance. + private readonly string Source; + + /// Manages access to the console output. + private readonly ConsoleInterceptionManager ConsoleManager; + + /// The log file to which to write messages. + private readonly LogFileManager LogFile; + + /// The maximum length of the values. + private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast() select level.ToString().Length).Max(); + + /// The console text color for each log level. + private static readonly IDictionary Colors = Monitor.GetConsoleColorScheme(); + + /// Propagates notification that SMAPI should exit. + private readonly CancellationTokenSource ExitTokenSource; + + + /********* + ** Accessors + *********/ + /// Whether SMAPI is aborting. Mods don't need to worry about this unless they have background tasks. + public bool IsExiting => this.ExitTokenSource.IsCancellationRequested; + + /// Whether to show the full log stamps (with time/level/logger) in the console. If false, shows a simplified stamp with only the logger. + internal bool ShowFullStampInConsole { get; set; } + + /// Whether to show trace messages in the console. + internal bool ShowTraceInConsole { get; set; } + + /// Whether to write anything to the console. This should be disabled if no console is available. + internal bool WriteToConsole { get; set; } = true; + + /// Whether to write anything to the log file. This should almost always be enabled. + internal bool WriteToFile { get; set; } = true; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The name of the module which logs messages using this instance. + /// Manages access to the console output. + /// The log file to which to write messages. + /// Propagates notification that SMAPI should exit. + public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile, CancellationTokenSource exitTokenSource) + { + // validate + if (string.IsNullOrWhiteSpace(source)) + throw new ArgumentException("The log source cannot be empty."); + + // initialise + this.Source = source; + this.LogFile = logFile ?? throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null."); + this.ConsoleManager = consoleManager; + this.ExitTokenSource = exitTokenSource; + } + + /// Log a message for the player or developer. + /// The message to log. + /// The log severity level. + public void Log(string message, LogLevel level = LogLevel.Debug) + { + this.LogImpl(this.Source, message, level, Monitor.Colors[level]); + } + + /// Immediately exit the game without saving. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs. + /// The reason for the shutdown. + public void ExitGameImmediately(string reason) + { + this.LogFatal($"{this.Source} requested an immediate game shutdown: {reason}"); + this.ExitTokenSource.Cancel(); + } + + /// Write a newline to the console and log file. + internal void Newline() + { + if (this.WriteToConsole) + this.ConsoleManager.ExclusiveWriteWithoutInterception(Console.WriteLine); + if (this.WriteToFile) + this.LogFile.WriteLine(""); + } + + + /********* + ** Private methods + *********/ + /// Log a fatal error message. + /// The message to log. + private void LogFatal(string message) + { + this.LogImpl(this.Source, message, LogLevel.Error, ConsoleColor.White, background: ConsoleColor.Red); + } + + /// Write a message line to the log. + /// The name of the mod logging the message. + /// The message to log. + /// The log level. + /// The console foreground color. + /// The console background color (or null to leave it as-is). + private void LogImpl(string source, string message, LogLevel level, ConsoleColor color, ConsoleColor? background = null) + { + // generate message + string levelStr = level.ToString().ToUpper().PadRight(Monitor.MaxLevelLength); + + string fullMessage = $"[{DateTime.Now:HH:mm:ss} {levelStr} {source}] {message}"; + string consoleMessage = this.ShowFullStampInConsole ? fullMessage : $"[{source}] {message}"; + + // write to console + if (this.WriteToConsole && (this.ShowTraceInConsole || level != LogLevel.Trace)) + { + this.ConsoleManager.ExclusiveWriteWithoutInterception(() => + { + if (this.ConsoleManager.SupportsColor) + { + if (background.HasValue) + Console.BackgroundColor = background.Value; + Console.ForegroundColor = color; + Console.WriteLine(consoleMessage); + Console.ResetColor(); + } + else + Console.WriteLine(consoleMessage); + }); + } + + // write to log file + if (this.WriteToFile) + this.LogFile.WriteLine(fullMessage); + } + + /// Get the color scheme to use for the current console. + private static IDictionary GetConsoleColorScheme() + { + // scheme for dark console background + if (Monitor.IsDark(Console.BackgroundColor)) + { + return new Dictionary + { + [LogLevel.Trace] = ConsoleColor.DarkGray, + [LogLevel.Debug] = ConsoleColor.DarkGray, + [LogLevel.Info] = ConsoleColor.White, + [LogLevel.Warn] = ConsoleColor.Yellow, + [LogLevel.Error] = ConsoleColor.Red, + [LogLevel.Alert] = ConsoleColor.Magenta + }; + } + + // scheme for light console background + return new Dictionary + { + [LogLevel.Trace] = ConsoleColor.DarkGray, + [LogLevel.Debug] = ConsoleColor.DarkGray, + [LogLevel.Info] = ConsoleColor.Black, + [LogLevel.Warn] = ConsoleColor.DarkYellow, + [LogLevel.Error] = ConsoleColor.Red, + [LogLevel.Alert] = ConsoleColor.DarkMagenta + }; + } + + /// Get whether a console color should be considered dark, which is subjectively defined as 'white looks better than black on this text'. + /// The color to check. + private static bool IsDark(ConsoleColor color) + { + switch (color) + { + case ConsoleColor.Black: + case ConsoleColor.Blue: + case ConsoleColor.DarkBlue: + case ConsoleColor.DarkRed: + case ConsoleColor.Red: + return true; + + default: + return false; + } + } + } +} -- cgit