From 232fb672243621bcda3407640568e7265fc6e56d Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:38:15 +0200 Subject: [PATCH 01/10] Add FORCE_COLOR environment variable support --- .../Common/src/System/Console/ConsoleUtils.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libraries/Common/src/System/Console/ConsoleUtils.cs b/src/libraries/Common/src/System/Console/ConsoleUtils.cs index f8472390d1f858..70c6ee3195d2e6 100644 --- a/src/libraries/Common/src/System/Console/ConsoleUtils.cs +++ b/src/libraries/Common/src/System/Console/ConsoleUtils.cs @@ -36,10 +36,20 @@ public static bool EmitAnsiColorCodes else { // We also support overriding in the other direction. If we'd otherwise avoid emitting color - // codes but the DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION environment variable is - // set to 1 or true, enable color. - string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); - enabled = envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); + // codes but the FORCE_COLOR environment variable is set (per https://force-color.org/), + // enable color. + string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); + if (forceColor is not null && forceColor.Length > 0) + { + enabled = true; + } + else + { + // We also support the DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION environment variable + // set to 1 or true to enable color. + string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); + enabled = envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); + } } // Store and return the computed answer. From 211984db3cab7dd4d93512dbe366d3724b6ad310 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:19:46 +0200 Subject: [PATCH 02/10] Test FORCE_COLOR and NO_COLOR --- src/libraries/System.Console/tests/Color.cs | 29 +++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Console/tests/Color.cs b/src/libraries/System.Console/tests/Color.cs index 0c43c50c03b424..03f36cc1fae75b 100644 --- a/src/libraries/System.Console/tests/Color.cs +++ b/src/libraries/System.Console/tests/Color.cs @@ -78,16 +78,25 @@ public static bool TermIsSetAndRemoteExecutorIsSupported [ConditionalTheory(nameof(TermIsSetAndRemoteExecutorIsSupported))] [PlatformSpecific(TestPlatforms.AnyUnix)] [SkipOnPlatform(TestPlatforms.Browser | TestPlatforms.iOS | TestPlatforms.MacCatalyst | TestPlatforms.tvOS, "Not supported on Browser, iOS, MacCatalyst, or tvOS.")] - [InlineData(null)] - [InlineData("1")] - [InlineData("true")] - [InlineData("tRuE")] - [InlineData("0")] - [InlineData("false")] - public static void RedirectedOutput_EnvVarSet_EmitsAnsiCodes(string? envVar) + [InlineData("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", null, false)] + [InlineData("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", "1", true)] + [InlineData("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", "true", true)] + [InlineData("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", "tRuE", true)] + [InlineData("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", "0", false)] + [InlineData("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", "false", false)] + [InlineData("FORCE_COLOR", "1", true)] + [InlineData("FORCE_COLOR", "true", true)] + [InlineData("FORCE_COLOR", "any-value", true)] + [InlineData("NO_COLOR", "1", false)] + [InlineData("NO_COLOR", "true", false)] + [InlineData("NO_COLOR", "any-value", false)] + public static void RedirectedOutput_EnvironmentVariableSet_RespectColorPreference(string envVarName, string? envVarValue, bool shouldEmitEscapes) { var psi = new ProcessStartInfo { RedirectStandardOutput = true }; - psi.Environment["DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"] = envVar; + if (envVarValue is not null) + { + psi.Environment[envVarName] = envVarValue; + } for (int i = 0; i < 3; i++) { @@ -113,13 +122,11 @@ public static void RedirectedOutput_EnvVarSet_EmitsAnsiCodes(string? envVar) using RemoteInvokeHandle remote = RemoteExecutor.Invoke(main, i.ToString(CultureInfo.InvariantCulture), new RemoteInvokeOptions() { StartInfo = psi }); - bool expectedEscapes = envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); - string stdout = remote.Process.StandardOutput.ReadToEnd(); string[] parts = stdout.Split("SEPARATOR"); Assert.Equal(3, parts.Length); - Assert.Equal(expectedEscapes, parts[1].Contains(Esc)); + Assert.Equal(shouldEmitEscapes, parts[1].Contains(Esc)); } } } From b742c3680a1a8531a613bdd6bce9bfafd6c178f9 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Sat, 7 Feb 2026 00:20:30 +0200 Subject: [PATCH 03/10] Add to ConsoleLoggerProvider --- .../src/ConsoleLoggerProvider.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs index 68a356de9ba748..492daa2aba18d2 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs @@ -68,6 +68,20 @@ public ConsoleLoggerProvider(IOptionsMonitor options, IEnu [UnsupportedOSPlatformGuard("windows")] private static bool DoesConsoleSupportAnsi() { + // We subscribe to the informal standard from https://no-color.org/. If the NO_COLOR environment + // variable is set, disable ANSI color support. + if (Environment.GetEnvironmentVariable("NO_COLOR") is not null) + { + return false; + } + + // Check FORCE_COLOR (per https://force-color.org/) + string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); + if (forceColor is not null && forceColor.Length > 0) + { + return true; + } + string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); if (envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase))) { From 3457d64ccf45f3db7a44f3e646988db873cb1bfc Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Sat, 7 Feb 2026 12:55:09 +0200 Subject: [PATCH 04/10] Optimize assertions in Color test Refactor assertions to improve clarity and functionality. --- src/libraries/System.Console/tests/Color.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Console/tests/Color.cs b/src/libraries/System.Console/tests/Color.cs index 03f36cc1fae75b..654d6e45baa2d1 100644 --- a/src/libraries/System.Console/tests/Color.cs +++ b/src/libraries/System.Console/tests/Color.cs @@ -67,8 +67,9 @@ public static void RedirectedOutputDoesNotUseAnsiSequences() Console.ResetColor(); Console.Write('4'); - Assert.Equal(0, Encoding.UTF8.GetString(data.ToArray()).ToCharArray().Count(c => c == Esc)); - Assert.Equal("1234", Encoding.UTF8.GetString(data.ToArray())); + string output = Encoding.UTF8.GetString(data.ToArray()); + Assert.Equal(0, output.ToCharArray().Count(c => c == Esc)); + Assert.Contains("1234", output); }); } From c765ed4fac7be0a6664bde4f533d78e7e704deb1 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Sat, 7 Feb 2026 13:06:50 +0200 Subject: [PATCH 05/10] Fix XML comments for ANSI color code methods --- .../Common/src/System/Console/ConsoleUtils.cs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/libraries/Common/src/System/Console/ConsoleUtils.cs b/src/libraries/Common/src/System/Console/ConsoleUtils.cs index 70c6ee3195d2e6..059ccbdc9e3d79 100644 --- a/src/libraries/Common/src/System/Console/ConsoleUtils.cs +++ b/src/libraries/Common/src/System/Console/ConsoleUtils.cs @@ -5,7 +5,7 @@ namespace System { internal static partial class ConsoleUtils { - /// Whether to output ansi color strings. + /// Whether to output ANSI color strings. private static volatile int s_emitAnsiColorCodes = -1; /// Get whether to emit ANSI color codes. @@ -13,43 +13,41 @@ public static bool EmitAnsiColorCodes { get { - // The flag starts at -1. If it's no longer -1, it's 0 or 1 to represent false or true. + // The flag starts at -1. If it's no longer -1, it's 0 or 1 to represent false or true. int emitAnsiColorCodes = s_emitAnsiColorCodes; if (emitAnsiColorCodes != -1) { return Convert.ToBoolean(emitAnsiColorCodes); } - // We've not yet computed whether to emit codes or not. Do so now. We may race with + // We've not yet computed whether to emit codes or not. We may race with // other threads, and that's ok; this is idempotent unless someone is currently changing // the value of the relevant environment variables, in which case behavior here is undefined. + // FORCE_COLOR (per https://force-color.org/) always overrides other settings + string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); + if (!string.IsNullOrEmpty(forceColor)) + { + s_emitAnsiColorCodes = 1; + return true; + } + // By default, we emit ANSI color codes if output isn't redirected, and suppress them if output is redirected. bool enabled = !Console.IsOutputRedirected; if (enabled) { - // We subscribe to the informal standard from https://no-color.org/. If we'd otherwise emit + // We subscribe to the informal standard from https://no-color.org/. If we'd otherwise emit // ANSI color codes but the NO_COLOR environment variable is set, disable emitting them. enabled = Environment.GetEnvironmentVariable("NO_COLOR") is null; } else { - // We also support overriding in the other direction. If we'd otherwise avoid emitting color - // codes but the FORCE_COLOR environment variable is set (per https://force-color.org/), - // enable color. - string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); - if (forceColor is not null && forceColor.Length > 0) - { - enabled = true; - } - else - { - // We also support the DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION environment variable - // set to 1 or true to enable color. - string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); - enabled = envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); - } + // We also support overriding in the other direction. If we'd otherwise avoid emitting color + // codes but the DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION environment variable is + // set to 1 or true, enable color. + string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); + enabled = envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); } // Store and return the computed answer. From 6b04468da6a30c2d9b5a290a88ff402ef0059faa Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Sat, 7 Feb 2026 13:09:27 +0200 Subject: [PATCH 06/10] Reorder --- .../src/ConsoleLoggerProvider.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs index 492daa2aba18d2..df01e8c465bcbb 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs @@ -68,19 +68,18 @@ public ConsoleLoggerProvider(IOptionsMonitor options, IEnu [UnsupportedOSPlatformGuard("windows")] private static bool DoesConsoleSupportAnsi() { - // We subscribe to the informal standard from https://no-color.org/. If the NO_COLOR environment - // variable is set, disable ANSI color support. - if (Environment.GetEnvironmentVariable("NO_COLOR") is not null) - { - return false; - } - - // Check FORCE_COLOR (per https://force-color.org/) + // Check FORCE_COLOR first (per https://force-color.org/) string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); - if (forceColor is not null && forceColor.Length > 0) + if (!string.IsNullOrEmpty(forceColor)) { return true; } + + // Then check NO_COLOR + if (Environment.GetEnvironmentVariable("NO_COLOR") is not null) + { + return false; + } string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); if (envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase))) From e8ce1dd4139c491efc07685a65d3d790c2daa375 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Sat, 7 Feb 2026 13:40:01 +0200 Subject: [PATCH 07/10] space --- .../src/ConsoleLoggerProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs index df01e8c465bcbb..b2e290751b7dc4 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs @@ -74,7 +74,7 @@ private static bool DoesConsoleSupportAnsi() { return true; } - + // Then check NO_COLOR if (Environment.GetEnvironmentVariable("NO_COLOR") is not null) { From 2fb7583ace75c4c4d0b2a9e55dbe0b54258f6c71 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:17:27 +0200 Subject: [PATCH 08/10] Combine mutiple variables --- .../Common/src/System/Console/ConsoleUtils.cs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libraries/Common/src/System/Console/ConsoleUtils.cs b/src/libraries/Common/src/System/Console/ConsoleUtils.cs index 059ccbdc9e3d79..8e25c512f187cc 100644 --- a/src/libraries/Common/src/System/Console/ConsoleUtils.cs +++ b/src/libraries/Common/src/System/Console/ConsoleUtils.cs @@ -23,13 +23,17 @@ public static bool EmitAnsiColorCodes // We've not yet computed whether to emit codes or not. We may race with // other threads, and that's ok; this is idempotent unless someone is currently changing // the value of the relevant environment variables, in which case behavior here is undefined. - - // FORCE_COLOR (per https://force-color.org/) always overrides other settings - string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); - if (!string.IsNullOrEmpty(forceColor)) + + // FORCE_COLOR (per https://force-color.org/) always overrides other settings. + // DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION is a legacy alias for the same behavior. + ReadOnlySpan forceColorNames = ["FORCE_COLOR", "DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"]; + foreach (string forceColorName in forceColorNames) { - s_emitAnsiColorCodes = 1; - return true; + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(forceColorName))) + { + s_emitAnsiColorCodes = 1; + return true; + } } // By default, we emit ANSI color codes if output isn't redirected, and suppress them if output is redirected. @@ -41,14 +45,6 @@ public static bool EmitAnsiColorCodes // ANSI color codes but the NO_COLOR environment variable is set, disable emitting them. enabled = Environment.GetEnvironmentVariable("NO_COLOR") is null; } - else - { - // We also support overriding in the other direction. If we'd otherwise avoid emitting color - // codes but the DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION environment variable is - // set to 1 or true, enable color. - string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); - enabled = envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase)); - } // Store and return the computed answer. s_emitAnsiColorCodes = Convert.ToInt32(enabled); From 98d6829e0ec53412f29b22991eec0d9a2710d318 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Tue, 10 Feb 2026 11:21:02 +0200 Subject: [PATCH 09/10] Combined --- .../src/ConsoleLoggerProvider.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs index b2e290751b7dc4..8c21313e32f10d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs @@ -68,26 +68,23 @@ public ConsoleLoggerProvider(IOptionsMonitor options, IEnu [UnsupportedOSPlatformGuard("windows")] private static bool DoesConsoleSupportAnsi() { - // Check FORCE_COLOR first (per https://force-color.org/) - string? forceColor = Environment.GetEnvironmentVariable("FORCE_COLOR"); - if (!string.IsNullOrEmpty(forceColor)) + // FORCE_COLOR (per https://force-color.org/) always overrides other settings. + // DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION is a legacy alias for the same behavior. + ReadOnlySpan forceColorNames = ["FORCE_COLOR", "DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"]; + foreach (string forceColorName in forceColorNames) { - return true; + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(forceColorName))) + { + return true; + } } - // Then check NO_COLOR + // NO_COLOR disables ANSI color output unless explicitly overridden above. if (Environment.GetEnvironmentVariable("NO_COLOR") is not null) { return false; } - string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"); - if (envVar is not null && (envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase))) - { - // ANSI color support forcibly enabled via environment variable. This logic matches the behaviour - // found in System.ConsoleUtils.EmitAnsiColorCodes. - return true; - } if ( #if NETFRAMEWORK Environment.OSVersion.Platform != PlatformID.Win32NT From dd61e36d8a5d780edd6d0bfd46ac58c8d2d25897 Mon Sep 17 00:00:00 2001 From: kasperk81 <83082615+kasperk81@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:44:26 +0200 Subject: [PATCH 10/10] Fix formatting --- src/libraries/Common/src/System/Console/ConsoleUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/Common/src/System/Console/ConsoleUtils.cs b/src/libraries/Common/src/System/Console/ConsoleUtils.cs index 8e25c512f187cc..89561a3e9ba540 100644 --- a/src/libraries/Common/src/System/Console/ConsoleUtils.cs +++ b/src/libraries/Common/src/System/Console/ConsoleUtils.cs @@ -23,7 +23,7 @@ public static bool EmitAnsiColorCodes // We've not yet computed whether to emit codes or not. We may race with // other threads, and that's ok; this is idempotent unless someone is currently changing // the value of the relevant environment variables, in which case behavior here is undefined. - + // FORCE_COLOR (per https://force-color.org/) always overrides other settings. // DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION is a legacy alias for the same behavior. ReadOnlySpan forceColorNames = ["FORCE_COLOR", "DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION"];