diff options
Diffstat (limited to 'src/StardewModdingAPI/Framework/Logging')
3 files changed, 206 insertions, 0 deletions
diff --git a/src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs b/src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs new file mode 100644 index 00000000..b8f2c34e --- /dev/null +++ b/src/StardewModdingAPI/Framework/Logging/ConsoleInterceptionManager.cs @@ -0,0 +1,86 @@ +using System; + +namespace StardewModdingAPI.Framework.Logging +{ + /// <summary>Manages console output interception.</summary> + internal class ConsoleInterceptionManager : IDisposable + { + /********* + ** Properties + *********/ + /// <summary>The intercepting console writer.</summary> + private readonly InterceptingTextWriter Output; + + + /********* + ** Accessors + *********/ + /// <summary>Whether the current console supports color formatting.</summary> + public bool SupportsColor { get; } + + /// <summary>The event raised when a message is written to the console directly.</summary> + public event Action<string> OnMessageIntercepted; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + public ConsoleInterceptionManager() + { + // redirect output through interceptor + this.Output = new InterceptingTextWriter(Console.Out); + this.Output.OnMessageIntercepted += line => this.OnMessageIntercepted?.Invoke(line); + Console.SetOut(this.Output); + + // test color support + this.SupportsColor = this.TestColorSupport(); + } + + /// <summary>Get an exclusive lock and write to the console output without interception.</summary> + /// <param name="action">The action to perform within the exclusive write block.</param> + public void ExclusiveWriteWithoutInterception(Action action) + { + lock (Console.Out) + { + try + { + this.Output.ShouldIntercept = false; + action(); + } + finally + { + this.Output.ShouldIntercept = true; + } + } + } + + /// <summary>Release all resources.</summary> + public void Dispose() + { + Console.SetOut(this.Output.Out); + this.Output.Dispose(); + } + + + /********* + ** private methods + *********/ + /// <summary>Test whether the current console supports color formatting.</summary> + private bool TestColorSupport() + { + try + { + this.ExclusiveWriteWithoutInterception(() => + { + Console.ForegroundColor = Console.ForegroundColor; + }); + return true; + } + catch (Exception) + { + return false; // Mono bug + } + } + } +} diff --git a/src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs b/src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs new file mode 100644 index 00000000..9ca61b59 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Logging/InterceptingTextWriter.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Text; + +namespace StardewModdingAPI.Framework.Logging +{ + /// <summary>A text writer which allows intercepting output.</summary> + internal class InterceptingTextWriter : TextWriter + { + /********* + ** Accessors + *********/ + /// <summary>The underlying console output.</summary> + public TextWriter Out { get; } + + /// <summary>The character encoding in which the output is written.</summary> + public override Encoding Encoding => this.Out.Encoding; + + /// <summary>Whether to intercept console output.</summary> + public bool ShouldIntercept { get; set; } + + /// <summary>The event raised when a message is written to the console directly.</summary> + public event Action<string> OnMessageIntercepted; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="output">The underlying output writer.</param> + public InterceptingTextWriter(TextWriter output) + { + this.Out = output; + } + + /// <summary>Writes a subarray of characters to the text string or stream.</summary> + /// <param name="buffer">The character array to write data from.</param> + /// <param name="index">The character position in the buffer at which to start retrieving data.</param> + /// <param name="count">The number of characters to write.</param> + public override void Write(char[] buffer, int index, int count) + { + if (this.ShouldIntercept) + this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n')); + else + this.Out.Write(buffer, index, count); + } + + /// <summary>Writes a character to the text string or stream.</summary> + /// <param name="ch">The character to write to the text stream.</param> + /// <remarks>Console log messages from the game should be caught by <see cref="Write(char[],int,int)"/>. This method passes through anything that bypasses that method for some reason, since it's better to show it to users than hide it from everyone.</remarks> + public override void Write(char ch) + { + this.Out.Write(ch); + } + + /// <summary>Releases the unmanaged resources used by the <see cref="T:System.IO.TextWriter" /> and optionally releases the managed resources.</summary> + /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> + protected override void Dispose(bool disposing) + { + this.OnMessageIntercepted = null; + } + } +} diff --git a/src/StardewModdingAPI/Framework/Logging/LogFileManager.cs b/src/StardewModdingAPI/Framework/Logging/LogFileManager.cs new file mode 100644 index 00000000..8cfe0527 --- /dev/null +++ b/src/StardewModdingAPI/Framework/Logging/LogFileManager.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace StardewModdingAPI.Framework.Logging +{ + /// <summary>Manages reading and writing to log file.</summary> + internal class LogFileManager : IDisposable + { + /********* + ** Properties + *********/ + /// <summary>The underlying stream writer.</summary> + private readonly StreamWriter Stream; + + + /********* + ** Accessors + *********/ + /// <summary>The full path to the log file being written.</summary> + public string Path { get; } + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="path">The log file to write.</param> + public LogFileManager(string path) + { + this.Path = path; + + // create log directory if needed + string logDir = System.IO.Path.GetDirectoryName(path); + if (logDir == null) + throw new ArgumentException($"The log path '{path}' is not valid."); + Directory.CreateDirectory(logDir); + + // open log file stream + this.Stream = new StreamWriter(path, append: false) { AutoFlush = true }; + } + + /// <summary>Write a message to the log.</summary> + /// <param name="message">The message to log.</param> + public void WriteLine(string message) + { + // always use Windows-style line endings for convenience + // (Linux/Mac editors are fine with them, Windows editors often require them) + this.Stream.Write(message + "\r\n"); + } + + /// <summary>Release all resources.</summary> + public void Dispose() + { + this.Stream.Dispose(); + } + } +} |