From 9933fd5ddf0bfd1dbf45f0bb7a48861bfc1d4894 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 29 May 2026 04:44:52 +0000
Subject: [PATCH 1/9] Initial plan
From 85172bcc769324edbb2f5e4f96093511b4108b1b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 29 May 2026 05:14:40 +0000
Subject: [PATCH 2/9] Sanitize control characters in console formatter output
Co-authored-by: rosebyte <14963300+rosebyte@users.noreply.github.com>
---
.../Microsoft.Extensions.Logging.Console.cs | 1 +
.../src/ConsoleControlCharacterSanitizer.cs | 63 +++++++++++++++++++
.../src/ConsoleFormatterOptions.cs | 8 +++
.../src/JsonConsoleFormatter.cs | 28 +++++----
.../src/SimpleConsoleFormatter.cs | 12 +++-
.../src/SystemdConsoleFormatter.cs | 12 +++-
.../ConsoleFormatterTests.cs | 61 ++++++++++++++++++
.../ConsoleLoggerConfigureOptions.cs | 7 ++-
.../ConsoleLoggerTest.cs | 18 ++----
.../JsonConsoleFormatterTests.cs | 6 +-
10 files changed, 180 insertions(+), 36 deletions(-)
create mode 100644 src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleControlCharacterSanitizer.cs
diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs
index 76c9169cf61fa6..a26ba823eae0ed 100644
--- a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs
+++ b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs
@@ -77,6 +77,7 @@ public partial class ConsoleFormatterOptions
{
public ConsoleFormatterOptions() { }
public bool IncludeScopes { get { throw null; } set { } }
+ public bool SanitizeControlCharacters { get { throw null; } set { } }
[System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("DateTimeFormat")]
public string? TimestampFormat { get { throw null; } set { } }
public bool UseUtcTimestamp { get { throw null; } set { } }
diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleControlCharacterSanitizer.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleControlCharacterSanitizer.cs
new file mode 100644
index 00000000000000..2d9e84b2ff2641
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleControlCharacterSanitizer.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Globalization;
+using System.Text;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal static class ConsoleControlCharacterSanitizer
+ {
+ public static string? Sanitize(string? value, bool sanitizeControlCharacters)
+ {
+ if (!sanitizeControlCharacters || string.IsNullOrEmpty(value))
+ {
+ return value;
+ }
+
+ int firstEscapedCharacterIndex = GetFirstEscapedCharacterIndex(value);
+ if (firstEscapedCharacterIndex < 0)
+ {
+ return value;
+ }
+
+ var sanitized = new StringBuilder(value.Length + 8);
+ sanitized.Append(value, 0, firstEscapedCharacterIndex);
+
+ for (int i = firstEscapedCharacterIndex; i < value.Length; i++)
+ {
+ char current = value[i];
+ if (ShouldEscape(current))
+ {
+ sanitized.Append(@"\u");
+ sanitized.Append(((int)current).ToString("X4", CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ sanitized.Append(current);
+ }
+ }
+
+ return sanitized.ToString();
+ }
+
+ private static int GetFirstEscapedCharacterIndex(string value)
+ {
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (ShouldEscape(value[i]))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ private static bool ShouldEscape(char c)
+ {
+ UnicodeCategory category = char.GetUnicodeCategory(c);
+ return category == UnicodeCategory.Control || category == UnicodeCategory.Format;
+ }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatterOptions.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatterOptions.cs
index e97c3cbe0c88f5..671968bd74e34a 100644
--- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatterOptions.cs
+++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatterOptions.cs
@@ -41,6 +41,14 @@ public ConsoleFormatterOptions() { }
///
public bool UseUtcTimestamp { get; set; }
+ ///
+ /// Gets or sets a value that indicates whether control and formatting characters are escaped in log output.
+ ///
+ ///
+ /// The default is .
+ ///
+ public bool SanitizeControlCharacters { get; set; } = true;
+
internal virtual void Configure(IConfiguration configuration) => configuration.Bind(this);
}
}
diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
index 040a4d09ae7fb7..f19f49026455ba 100644
--- a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
+++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
@@ -56,6 +56,12 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex
string category, int eventId, string? exception, bool hasState, string? stateMessage, IReadOnlyList>? stateProperties,
DateTimeOffset stamp)
{
+ bool sanitizeControlCharacters = FormatterOptions.SanitizeControlCharacters;
+ message = ConsoleControlCharacterSanitizer.Sanitize(message, sanitizeControlCharacters);
+ category = ConsoleControlCharacterSanitizer.Sanitize(category, sanitizeControlCharacters)!;
+ exception = ConsoleControlCharacterSanitizer.Sanitize(exception, sanitizeControlCharacters);
+ stateMessage = ConsoleControlCharacterSanitizer.Sanitize(stateMessage, sanitizeControlCharacters);
+
const int DefaultBufferSize = 1024;
using (var output = new PooledByteBufferWriter(DefaultBufferSize))
{
@@ -65,7 +71,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex
var timestampFormat = FormatterOptions.TimestampFormat;
if (timestampFormat != null)
{
- writer.WriteString("Timestamp", stamp.ToString(timestampFormat));
+ writer.WriteString("Timestamp", ConsoleControlCharacterSanitizer.Sanitize(stamp.ToString(timestampFormat), sanitizeControlCharacters));
}
writer.WriteNumber(nameof(LogEntry