diff --git a/src/Cli.Tests/CustomLoggerTests.cs b/src/Cli.Tests/CustomLoggerTests.cs index cce73f0f75..c02e218f0a 100644 --- a/src/Cli.Tests/CustomLoggerTests.cs +++ b/src/Cli.Tests/CustomLoggerTests.cs @@ -77,8 +77,8 @@ public void LogOutput_UsesAbbreviatedLogLevelLabels(LogLevel logLevel, string ex string actual = expectStderr ? stderr : stdout; string other = expectStderr ? stdout : stderr; - Assert.IsTrue(actual.StartsWith(expectedPrefix), - $"Expected output to start with '{expectedPrefix}' but got: '{actual}'"); + Assert.IsTrue(actual.Contains(expectedPrefix), + $"Expected output to contain '{expectedPrefix}' but got: '{actual}'"); StringAssert.Contains(actual, Message); Assert.AreEqual(string.Empty, other, $"Did not expect output on the other stream but got: '{other}'"); diff --git a/src/Cli/CustomLoggerProvider.cs b/src/Cli/CustomLoggerProvider.cs index a4625b0924..89fbe8b8eb 100644 --- a/src/Cli/CustomLoggerProvider.cs +++ b/src/Cli/CustomLoggerProvider.cs @@ -25,6 +25,8 @@ public ILogger CreateLogger(string categoryName) public class CustomConsoleLogger : ILogger { + private const string UtcTimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z'"; + private readonly LogLevel _minimumLogLevel; // Minimum LogLevel for CLI output. @@ -124,13 +126,14 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except // Apply colors so the abbreviation matches the visual style of engine logs. // try/finally guarantees the original colors are restored even if Write throws, // otherwise the console would be left tinted (e.g. red on error) for subsequent output. + string mcpTimestamp = DateTime.UtcNow.ToString(UtcTimestampFormat); ConsoleColor mcpOriginalForeGroundColor = Console.ForegroundColor; ConsoleColor mcpOriginalBackGroundColor = Console.BackgroundColor; try { Console.ForegroundColor = _logLevelToForeGroundConsoleColorMap.GetValueOrDefault(logLevel, ConsoleColor.White); Console.BackgroundColor = _logLevelToBackGroundConsoleColorMap.GetValueOrDefault(logLevel, ConsoleColor.Black); - Console.Error.Write($"{mcpAbbreviation}:"); + Console.Error.Write($"{mcpTimestamp} {mcpAbbreviation}:"); } finally { @@ -153,6 +156,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } TextWriter writer = logLevel >= LogLevel.Error ? Console.Error : Console.Out; + string timestamp = DateTime.UtcNow.ToString(UtcTimestampFormat); // try/finally guarantees the original colors are restored even if Write throws, // otherwise the console would be left tinted (e.g. red on error) for subsequent output. ConsoleColor originalForeGroundColor = Console.ForegroundColor; @@ -161,7 +165,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { Console.ForegroundColor = _logLevelToForeGroundConsoleColorMap.GetValueOrDefault(logLevel, ConsoleColor.White); Console.BackgroundColor = _logLevelToBackGroundConsoleColorMap.GetValueOrDefault(logLevel, ConsoleColor.Black); - writer.Write($"{abbreviation}:"); + writer.Write($"{timestamp} {abbreviation}:"); } finally { diff --git a/src/Service/Program.cs b/src/Service/Program.cs index 76af52ba97..ddef5851a2 100644 --- a/src/Service/Program.cs +++ b/src/Service/Program.cs @@ -26,6 +26,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.ApplicationInsights; +using Microsoft.Extensions.Logging.Console; using OpenTelemetry.Exporter; using OpenTelemetry.Logs; using OpenTelemetry.Resources; @@ -194,6 +195,11 @@ public static IHostBuilder CreateHostBuilder(string[] args, bool runMcpStdio, st else { logging.SetMinimumLevel(LogLevelProvider.CurrentLogLevel); + logging.AddSimpleConsole(options => + { + options.TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z' "; + options.UseUtcTimestamp = true; + }); } // Add filter for dynamic log level changes (e.g., via MCP logging/setLevel) @@ -464,7 +470,14 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel( // When LogLevel.None, skip the console logger entirely for true silence. if (LogLevelProvider.CurrentLogLevel != LogLevel.None) { - builder.AddConsole(options => + builder.AddSimpleConsole(options => + { + options.TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z' "; + options.UseUtcTimestamp = true; + }); + // Route all levels to stderr to keep stdout clean for MCP JSON-RPC. + // Uses Services.Configure (not AddConsole) so no second provider is registered. + builder.Services.Configure(options => { options.LogToStandardErrorThreshold = LogLevel.Trace; }); @@ -472,7 +485,11 @@ public static ILoggerFactory GetLoggerFactoryForLogLevel( } else { - builder.AddConsole(); + builder.AddSimpleConsole(options => + { + options.TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss.fff'Z' "; + options.UseUtcTimestamp = true; + }); } }); }