using System; using System.Collections.Generic; using System.Linq; 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 Enumerable.Cast(Enum.GetValues(typeof(LogLevel))) select level.ToString().Length).Max(); /// The console text color for each log level. private static readonly Dictionary Colors = 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 }; /// A delegate which requests that SMAPI immediately exit the game. This should only be invoked when an irrecoverable fatal error happens that risks save corruption or game-breaking bugs. private RequestExitDelegate RequestExit; /********* ** Accessors *********/ /// 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. /// A delegate which requests that SMAPI immediately exit the game. public Monitor(string source, ConsoleInterceptionManager consoleManager, LogFileManager logFile, RequestExitDelegate requestExitDelegate) { // validate if (string.IsNullOrWhiteSpace(source)) throw new ArgumentException("The log source cannot be empty."); if (logFile == null) throw new ArgumentNullException(nameof(logFile), "The log file manager cannot be null."); // initialise this.Source = source; this.LogFile = logFile; this.ConsoleManager = consoleManager; } /// 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.RequestExit(this.Source, reason); } /// Log a fatal error message. /// The message to log. internal void LogFatal(string message) { this.LogImpl(this.Source, message, LogLevel.Error, ConsoleColor.White, background: ConsoleColor.Red); } /// Log a message for the player or developer, using the specified console color. /// The name of the mod logging the message. /// The message to log. /// The console color. /// The log level. [Obsolete("This method is provided for backwards compatibility and otherwise should not be used. Use " + nameof(Monitor) + "." + nameof(Monitor.Log) + " instead.")] internal void LegacyLog(string source, string message, ConsoleColor color, LogLevel level = LogLevel.Debug) { this.LogImpl(source, message, level, color); } /********* ** Private methods *********/ /// 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); message = $"[{DateTime.Now:HH:mm:ss} {levelStr} {source}] {message}"; // write to console if (this.WriteToConsole && (this.ShowTraceInConsole || level != LogLevel.Trace)) { this.ConsoleManager.ExclusiveWriteWithoutInterception(() => { if (this.ConsoleManager.SupportsColor) { Console.ForegroundColor = color; Console.WriteLine(message); Console.ResetColor(); } else Console.WriteLine(message); }); } // write to log file if (this.WriteToFile) this.LogFile.WriteLine(message); } } }