diff --git a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs index cb319ed4864..7a0e5a9106f 100644 --- a/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs +++ b/src/Aspire.Dashboard/Components/Pages/ConsoleLogs.razor.cs @@ -471,7 +471,7 @@ private void LoadLogs(ConsoleLogsSubscription newConsoleLogsSubscription) // Console logs are filtered in the UI by the timestamp of the log entry. var timestampFilterDate = GetFilteredDateFromRemove(); - var logParser = new LogParser(); + var logParser = new LogParser(ConsoleColor.Black); await foreach (var batch in subscription.ConfigureAwait(true)) { if (batch.Count is 0) diff --git a/src/Aspire.Dashboard/ConsoleLogs/AnsiParser.cs b/src/Aspire.Dashboard/ConsoleLogs/AnsiParser.cs index b037a023946..2d6e7b3f357 100644 --- a/src/Aspire.Dashboard/ConsoleLogs/AnsiParser.cs +++ b/src/Aspire.Dashboard/ConsoleLogs/AnsiParser.cs @@ -64,7 +64,7 @@ public static string StripControlSequences(string text) return outputBuilder?.ToString() ?? text; } - public static ConversionResult ConvertToHtml(string? text, ParserState? priorResidualState = null) + public static ConversionResult ConvertToHtml(string? text, ParserState? priorResidualState = null, ConsoleColor? defaultBackgroundColor = null) { var textStartIndex = -1; var textLength = 0; @@ -147,7 +147,7 @@ public static ConversionResult ConvertToHtml(string? text, ParserState? priorRes // Ignore everything else and don't write sequence to the output. if (finalByte == DisplayAttributesFinalByte) { - ProcessParameters(ref newState, parameters); + ProcessParameters(defaultBackgroundColor, ref newState, parameters); } continue; @@ -197,7 +197,7 @@ public static ConversionResult ConvertToHtml(string? text, ParserState? priorRes return new(outputBuilder.ToString(), currentState); } - private static void ProcessParameters(ref ParserState newState, int[] parameters) + private static void ProcessParameters(ConsoleColor? defaultBackgroundColor, ref ParserState newState, int[] parameters) { for (var i = 0; i < parameters.Length; i++) { @@ -228,7 +228,9 @@ private static void ProcessParameters(ref ParserState newState, int[] parameters } else if (TryGetBackgroundColor(parameter, out color)) { - newState.BackgroundColor = color; + // Don't set the background color if it matches the default background color. + // Skipping setting it improves appearance when row mouseover slightly changes color. + newState.BackgroundColor = (color != defaultBackgroundColor) ? color : null; } else if (parameter == DefaultBackgroundCode) { @@ -516,14 +518,14 @@ private static string ProcessStateChange(ParserState currentState, ParserState n { return state.ForegroundColor switch { - ConsoleColor.Black => state.Bright ? "ansi-fg-brightblack" : "ansi-fg-black", - ConsoleColor.Blue => state.Bright ? "ansi-fg-brightblue" : "ansi-fg-blue", - ConsoleColor.Cyan => state.Bright ? "ansi-fg-brightcyan" : "ansi-fg-cyan", - ConsoleColor.Green => state.Bright ? "ansi-fg-brightgreen" : "ansi-fg-green", + ConsoleColor.Black => state.Bright ? "ansi-fg-brightblack" : "ansi-fg-black", + ConsoleColor.Blue => state.Bright ? "ansi-fg-brightblue" : "ansi-fg-blue", + ConsoleColor.Cyan => state.Bright ? "ansi-fg-brightcyan" : "ansi-fg-cyan", + ConsoleColor.Green => state.Bright ? "ansi-fg-brightgreen" : "ansi-fg-green", ConsoleColor.Magenta => state.Bright ? "ansi-fg-brightmagenta" : "ansi-fg-magenta", - ConsoleColor.Red => state.Bright ? "ansi-fg-brightred" : "ansi-fg-red", - ConsoleColor.White => state.Bright ? "ansi-fg-brightwhite" : "ansi-fg-white", - ConsoleColor.Yellow => state.Bright ? "ansi-fg-brightyellow" : "ansi-fg-yellow", + ConsoleColor.Red => state.Bright ? "ansi-fg-brightred" : "ansi-fg-red", + ConsoleColor.White => state.Bright ? "ansi-fg-brightwhite" : "ansi-fg-white", + ConsoleColor.Yellow => state.Bright ? "ansi-fg-brightyellow" : "ansi-fg-yellow", _ => "" }; } @@ -532,14 +534,14 @@ private static string ProcessStateChange(ParserState currentState, ParserState n { return state.BackgroundColor switch { - ConsoleColor.Black => "ansi-bg-black", - ConsoleColor.Blue => "ansi-bg-blue", - ConsoleColor.Cyan => "ansi-bg-cyan", - ConsoleColor.Green => "ansi-bg-green", + ConsoleColor.Black => "ansi-bg-black", + ConsoleColor.Blue => "ansi-bg-blue", + ConsoleColor.Cyan => "ansi-bg-cyan", + ConsoleColor.Green => "ansi-bg-green", ConsoleColor.Magenta => "ansi-bg-magenta", - ConsoleColor.Red => "ansi-bg-red", - ConsoleColor.White => "ansi-bg-white", - ConsoleColor.Yellow => "ansi-bg-yellow", + ConsoleColor.Red => "ansi-bg-red", + ConsoleColor.White => "ansi-bg-white", + ConsoleColor.Yellow => "ansi-bg-yellow", _ => "" }; } diff --git a/src/Aspire.Dashboard/ConsoleLogs/LogParser.cs b/src/Aspire.Dashboard/ConsoleLogs/LogParser.cs index cdba273e7f4..537cc36f753 100644 --- a/src/Aspire.Dashboard/ConsoleLogs/LogParser.cs +++ b/src/Aspire.Dashboard/ConsoleLogs/LogParser.cs @@ -8,8 +8,14 @@ namespace Aspire.Dashboard.ConsoleLogs; internal sealed class LogParser { + private readonly ConsoleColor _defaultBackgroundColor; private AnsiParser.ParserState? _residualState; + public LogParser(ConsoleColor defaultBackgroundColor) + { + _defaultBackgroundColor = defaultBackgroundColor; + } + public LogEntry CreateLogEntry(string rawText, bool isErrorOutput) { // Several steps to do here: @@ -39,7 +45,7 @@ public LogEntry CreateLogEntry(string rawText, bool isErrorOutput) var updatedText = WebUtility.HtmlEncode(s); // 3b. Parse the content to look for ANSI Control Sequences and color them if possible - var conversionResult = AnsiParser.ConvertToHtml(updatedText, _residualState); + var conversionResult = AnsiParser.ConvertToHtml(updatedText, _residualState, _defaultBackgroundColor); updatedText = conversionResult.ConvertedText; _residualState = conversionResult.ResidualState; diff --git a/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/LogEntriesTests.cs b/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/LogEntriesTests.cs index 7fc31b63cb1..c7619d9287a 100644 --- a/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/LogEntriesTests.cs +++ b/tests/Aspire.Dashboard.Tests/ConsoleLogsTests/LogEntriesTests.cs @@ -18,7 +18,7 @@ private static LogEntries CreateLogEntries(int? maximumEntryCount = null, int? b private static void AddLogLine(LogEntries logEntries, string content, bool isError) { - var logParser = new LogParser(); + var logParser = new LogParser(ConsoleColor.Black); var logEntry = logParser.CreateLogEntry(content, isError); logEntries.InsertSorted(logEntry); } @@ -268,7 +268,7 @@ public void InsertSorted_TrimsToMaximumEntryCount_OutOfOrder() public void CreateLogEntry_AnsiAndUrl_HasUrlAnchor() { // Arrange - var parser = new LogParser(); + var parser = new LogParser(ConsoleColor.Black); // Act var entry = parser.CreateLogEntry("\x1b[36mhttps://www.example.com\u001b[0m", isErrorOutput: false); @@ -276,4 +276,19 @@ public void CreateLogEntry_AnsiAndUrl_HasUrlAnchor() // Assert Assert.Equal("https://www.example.com", entry.Content); } + + [Theory] + [InlineData(ConsoleColor.Black, @"info: LoggerName")] + [InlineData(ConsoleColor.Blue, @"info: LoggerName")] + public void CreateLogEntry_DefaultBackgroundColor_SkipMatchingColor(ConsoleColor defaultBackgroundColor, string output) + { + // Arrange + var parser = new LogParser(defaultBackgroundColor); + + // Act + var entry = parser.CreateLogEntry("\u001b[40m\u001b[32minfo\u001b[39m\u001b[22m\u001b[49m: LoggerName", isErrorOutput: false); + + // Assert + Assert.Equal(output, entry.Content); + } }