summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/SMAPI/Framework/Logging/InterceptingTextWriter.cs28
-rw-r--r--src/SMAPI/Framework/Logging/LogManager.cs31
-rw-r--r--src/SMAPI/Framework/Monitor.cs2
3 files changed, 30 insertions, 31 deletions
diff --git a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs
index 36f81d22..bad69a2a 100644
--- a/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs
+++ b/src/SMAPI/Framework/Logging/InterceptingTextWriter.cs
@@ -8,15 +8,11 @@ namespace StardewModdingAPI.Framework.Logging
internal class InterceptingTextWriter : TextWriter
{
/*********
- ** Fields
+ ** Accessors
*********/
/// <summary>Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary>
- private readonly char IgnoreChar;
-
+ public const char IgnoreChar = '\u200B';
- /*********
- ** Accessors
- *********/
/// <summary>The underlying console output.</summary>
public TextWriter Out { get; }
@@ -36,30 +32,38 @@ namespace StardewModdingAPI.Framework.Logging
*********/
/// <summary>Construct an instance.</summary>
/// <param name="output">The underlying output writer.</param>
- /// <param name="ignoreChar">Prefixing a message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</param>
- public InterceptingTextWriter(TextWriter output, char ignoreChar)
+ public InterceptingTextWriter(TextWriter output)
{
this.Out = output;
- this.IgnoreChar = ignoreChar;
}
/// <inheritdoc />
public override void Write(char[] buffer, int index, int count)
{
+ // track newline skip
bool ignoreIfNewline = this.IgnoreNextIfNewline;
this.IgnoreNextIfNewline = false;
- if (buffer.Length == 0)
+ // get first character if valid
+ if (count == 0 || index < 0 || index >= buffer.Length)
+ {
this.Out.Write(buffer, index, count);
- else if (buffer[0] == this.IgnoreChar || char.IsControl(buffer[0])) // ignore control characters like backspace
+ return;
+ }
+ char firstChar = buffer[index];
+
+ // handle output
+ if (firstChar == InterceptingTextWriter.IgnoreChar)
this.Out.Write(buffer, index + 1, count - 1);
+ else if (char.IsControl(firstChar) && firstChar is not ('\r' or '\n'))
+ this.Out.Write(buffer, index, count);
else if (this.IsEmptyOrNewline(buffer))
{
if (!ignoreIfNewline)
this.Out.Write(buffer, index, count);
}
else
- this.OnMessageIntercepted?.Invoke(new string(buffer, index, count).TrimEnd('\r', '\n'));
+ this.OnMessageIntercepted?.Invoke(new string(buffer, index, count));
}
/// <inheritdoc />
diff --git a/src/SMAPI/Framework/Logging/LogManager.cs b/src/SMAPI/Framework/Logging/LogManager.cs
index acd2c617..a8a8b6ee 100644
--- a/src/SMAPI/Framework/Logging/LogManager.cs
+++ b/src/SMAPI/Framework/Logging/LogManager.cs
@@ -22,9 +22,6 @@ namespace StardewModdingAPI.Framework.Logging
/*********
** Fields
*********/
- /// <summary>Whether to show trace messages in the console.</summary>
- private readonly bool ShowTraceInConsole;
-
/// <summary>The log file to which to write messages.</summary>
private readonly LogFileManager LogFile;
@@ -32,7 +29,7 @@ namespace StardewModdingAPI.Framework.Logging
private readonly InterceptingTextWriter ConsoleInterceptor;
/// <summary>Prefixing a low-level message with this character indicates that the console interceptor should write the string without intercepting it. (The character itself is not written.)</summary>
- private readonly char IgnoreChar = '\u200B';
+ private const char IgnoreChar = InterceptingTextWriter.IgnoreChar;
/// <summary>Get a named monitor instance.</summary>
private readonly Func<string, Monitor> GetMonitorImpl;
@@ -40,18 +37,18 @@ namespace StardewModdingAPI.Framework.Logging
/// <summary>Regex patterns which match console non-error messages to suppress from the console and log.</summary>
private readonly Regex[] SuppressConsolePatterns =
{
- new Regex(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
- new Regex(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant),
- new Regex(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant),
- new Regex(@"^DebugOutput:\s+(?:added cricket|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant),
- new Regex(@"^Ignoring keys: ", RegexOptions.Compiled | RegexOptions.CultureInvariant)
+ new(@"^TextBox\.Selected is now '(?:True|False)'\.$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
+ new(@"^loadPreferences\(\); begin", RegexOptions.Compiled | RegexOptions.CultureInvariant),
+ new(@"^savePreferences\(\); async=", RegexOptions.Compiled | RegexOptions.CultureInvariant),
+ new(@"^DebugOutput:\s+(?:added cricket|dismount tile|Ping|playerPos)", RegexOptions.Compiled | RegexOptions.CultureInvariant),
+ new(@"^Ignoring keys: ", RegexOptions.Compiled | RegexOptions.CultureInvariant)
};
/// <summary>Regex patterns which match console messages to show a more friendly error for.</summary>
private readonly ReplaceLogPattern[] ReplaceConsolePatterns =
{
// Steam not loaded
- new ReplaceLogPattern(
+ new(
search: new Regex(@"^System\.InvalidOperationException: Steamworks is not initialized\.[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
replacement:
#if SMAPI_FOR_WINDOWS
@@ -63,7 +60,7 @@ namespace StardewModdingAPI.Framework.Logging
),
// save file not found error
- new ReplaceLogPattern(
+ new(
search: new Regex(@"^System\.IO\.FileNotFoundException: [^\n]+\n[^:]+: '[^\n]+[/\\]Saves[/\\]([^'\r\n]+)[/\\]([^'\r\n]+)'[\s\S]+LoadGameMenu\.FindSaveGames[\s\S]+$", RegexOptions.Compiled | RegexOptions.CultureInvariant),
replacement: "The game can't find the '$2' file for your '$1' save. See https://stardewvalleywiki.com/Saves#Troubleshooting for help.",
logLevel: LogLevel.Error
@@ -97,11 +94,10 @@ namespace StardewModdingAPI.Framework.Logging
public LogManager(string logPath, ColorSchemeConfig colorConfig, bool writeToConsole, bool isVerbose, bool isDeveloperMode, Func<int?> getScreenIdForLog)
{
// init construction logic
- this.ShowTraceInConsole = isDeveloperMode;
- this.GetMonitorImpl = name => new Monitor(name, this.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog)
+ this.GetMonitorImpl = name => new Monitor(name, LogManager.IgnoreChar, this.LogFile, colorConfig, isVerbose, getScreenIdForLog)
{
WriteToConsole = writeToConsole,
- ShowTraceInConsole = this.ShowTraceInConsole,
+ ShowTraceInConsole = isDeveloperMode,
ShowFullStampInConsole = isDeveloperMode
};
@@ -111,7 +107,7 @@ namespace StardewModdingAPI.Framework.Logging
this.MonitorForGame = this.GetMonitor("game");
// redirect direct console output
- this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out, this.IgnoreChar);
+ this.ConsoleInterceptor = new InterceptingTextWriter(Console.Out);
if (writeToConsole)
this.ConsoleInterceptor.OnMessageIntercepted += message => this.HandleConsoleMessage(this.MonitorForGame, message);
Console.SetOut(this.ConsoleInterceptor);
@@ -153,7 +149,7 @@ namespace StardewModdingAPI.Framework.Logging
.Add(new ReloadI18nCommand(reloadTranslations), this.Monitor);
// start handling command line input
- Thread inputThread = new Thread(() =>
+ Thread inputThread = new(() =>
{
while (true)
{
@@ -393,8 +389,7 @@ namespace StardewModdingAPI.Framework.Logging
// forward to monitor
gameMonitor.Log(message, level);
- if (level == LogLevel.Trace && !this.ShowTraceInConsole)
- this.ConsoleInterceptor.IgnoreNextIfNewline = true;
+ this.ConsoleInterceptor.IgnoreNextIfNewline = true;
}
/// <summary>Write a summary of mod warnings to the console and log.</summary>
diff --git a/src/SMAPI/Framework/Monitor.cs b/src/SMAPI/Framework/Monitor.cs
index 04e67d68..ab76e7c0 100644
--- a/src/SMAPI/Framework/Monitor.cs
+++ b/src/SMAPI/Framework/Monitor.cs
@@ -28,7 +28,7 @@ namespace StardewModdingAPI.Framework
private static readonly int MaxLevelLength = (from level in Enum.GetValues(typeof(LogLevel)).Cast<LogLevel>() select level.ToString().Length).Max();
/// <summary>A cache of messages that should only be logged once.</summary>
- private readonly HashSet<string> LogOnceCache = new HashSet<string>();
+ private readonly HashSet<string> LogOnceCache = new();
/// <summary>Get the screen ID that should be logged to distinguish between players in split-screen mode, if any.</summary>
private readonly Func<int?> GetScreenIdForLog;