From 5a8095c6f778814c12df660419a8c0c96b4e9a63 Mon Sep 17 00:00:00 2001 From: Martin Taillefer Date: Thu, 6 Jun 2024 12:20:07 -0700 Subject: [PATCH 1/6] Introduce IBufferedLogger --- ...crosoft.Extensions.Logging.Abstractions.cs | 17 +++++ ...oft.Extensions.Logging.Abstractions.csproj | 1 + .../src/BufferedLogRecord.cs | 69 +++++++++++++++++++ .../src/IBufferedLogger.cs | 37 ++++++++++ ...oft.Extensions.Logging.Abstractions.csproj | 1 + .../src/ConsoleFormatter.cs | 2 +- .../src/ConsoleLogger.cs | 30 +++++++- .../src/JsonConsoleFormatter.cs | 48 ++++++++----- .../src/SimpleConsoleFormatter.cs | 32 ++++++--- .../src/SystemdConsoleFormatter.cs | 32 ++++++--- .../ConsoleLoggerTest.cs | 64 +++++++++++++++++ ...ft.Extensions.Logging.Console.Tests.csproj | 2 +- 12 files changed, 296 insertions(+), 39 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs create mode 100644 src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs index eac0fb38df2c4d..4f203b845c066d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs @@ -155,6 +155,23 @@ public enum LogLevel Critical = 5, None = 6, } + public abstract class BufferedLogRecord + { + public abstract System.DateTimeOffset Timestamp { get; } + public abstract Microsoft.Extensions.Logging.LogLevel LogLevel { get; } + public abstract Microsoft.Extensions.Logging.EventId EventId { get; } + public abstract string? Exception { get; } + public abstract System.Diagnostics.ActivitySpanId? ActivitySpanId { get; } + public abstract System.Diagnostics.ActivityTraceId? ActivityTraceId { get; } + public abstract int? ManagedThreadId { get; } + public abstract string? FormattedMessage { get; } + public abstract string? MessageTemplate { get; } + public abstract System.Collections.Generic.IReadOnlyList> Attributes { get; } + } + public interface IBufferedLogger + { + void LogRecords(System.Collections.Generic.IReadOnlyList records); + } } namespace Microsoft.Extensions.Logging.Abstractions { diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.csproj index 90242dbf8b96f2..ff6d90a86e99b2 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.csproj @@ -9,5 +9,6 @@ + diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs new file mode 100644 index 00000000000000..d558f124cc3434 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Extensions.Logging +{ + /// + /// State representing a buffered log record. + /// + /// + /// Objects of this type are reused over time to reduce + /// allocations. + /// + public abstract class BufferedLogRecord + { + /// + /// Gets the time when the log record was first created. + /// + public abstract DateTimeOffset Timestamp { get; } + + /// + /// Gets the record's log level, indicating it rough importance + /// + public abstract LogLevel LogLevel { get; } + + /// + /// Gets the records event id. + /// + public abstract EventId EventId { get; } + + /// + /// Gets an optional exception string for this record. + /// + public abstract string? Exception { get => null; } + + /// + /// Gets an activity span id for this record, representing the state of the thread that created the record. + /// + public abstract ActivitySpanId? ActivitySpanId { get => null; } + + /// + /// Gets an activity trace id for this record, representing the state of the thread that created the record. + /// + public abstract ActivityTraceId? ActivityTraceId { get => null; } + + /// + /// Gets the ID of the thread that created the log record. + /// + public abstract int? ManagedThreadId { get => null; } + + /// + /// Gets the formatted log message. + /// + public abstract string? FormattedMessage { get => null; } + + /// + /// Gets the original log message template. + /// + public abstract string? MessageTemplate { get => null; } + + /// + /// Gets the variable set of name/value pairs associated with the record. + /// + public abstract IReadOnlyList> Attributes { get => Array.Empty>(); } + } +} diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs new file mode 100644 index 00000000000000..62beaa42171c1c --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.Logging +{ + /// + /// Logging providers can implement this interface to indicate they support buffered logging. + /// + /// + /// A logging provider normally exposes an interface that gets invoked by the + /// logging infrastructure whenever it’s time to log a piece of state. + /// + /// The logging infrastructure will type-test the ILogger object to determine if + /// it supports the IBufferedLogger interface also. If it does, that tells the + /// logging infrastructure that the logging provider supports buffering. Whenever log + /// buffering is enabled, buffered log records will be delivered to the logging provider + /// via the IBufferedLogger interface. + /// + /// If a logging provider does not support log buffering, then it will always be given + /// unbuffered log records. In other words, whether or not buffering is requested by + /// the user, it will not happen for those log providers. + /// + public interface IBufferedLogger + { + /// + /// Delivers a batch of buffered log records to a logging provider. + /// + /// The buffered log records to log. + /// + /// Once this function returns, it should no longer access the records + /// or state referenced by these records. + /// + void LogRecords(IReadOnlyList records); + } +} diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj index a14ac672bb75b8..00d83a691461b6 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj @@ -53,6 +53,7 @@ Microsoft.Extensions.Logging.Abstractions.NullLogger + diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs index d963e6af7f1123..262a976d9e9524 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs @@ -32,7 +32,7 @@ protected ConsoleFormatter(string name) /// Writes the log message to the specified TextWriter. /// /// - /// if the formatter wants to write colors to the console, it can do so by embedding ANSI color codes into the string + /// If the formatter wants to write colors to the console, it can do so by embedding ANSI color codes into the string /// /// The log entry. /// The provider of scope data. diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs index 3a542e6063635b..ebc8fcaacb5bee 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Versioning; @@ -13,7 +14,7 @@ namespace Microsoft.Extensions.Logging.Console /// A logger that writes messages in the console. /// [UnsupportedOSPlatform("browser")] - internal sealed class ConsoleLogger : ILogger + internal sealed class ConsoleLogger : ILogger, IBufferedLogger { private readonly string _name; private readonly ConsoleLoggerProcessor _queueProcessor; @@ -69,6 +70,33 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except _queueProcessor.EnqueueMessage(new LogMessageEntry(computedAnsiString, logAsError: logLevel >= Options.LogToStandardErrorThreshold)); } + /// + public void LogRecords(IReadOnlyList records) + { + ThrowHelper.ThrowIfNull(records); + + t_stringWriter ??= new StringWriter(); + + foreach (var rec in records) + { + var logEntry = new LogEntry(rec.LogLevel, _name, rec.EventId, rec, null, (s, e) => s.FormattedMessage ?? string.Empty); + Formatter.Write(in logEntry, null, t_stringWriter); + + var sb = t_stringWriter.GetStringBuilder(); + if (sb.Length == 0) + { + continue; + } + string computedAnsiString = sb.ToString(); + sb.Clear(); + if (sb.Capacity > 1024) + { + sb.Capacity = 1024; + } + _queueProcessor.EnqueueMessage(new LogMessageEntry(computedAnsiString, logAsError: rec.LogLevel >= Options.LogToStandardErrorThreshold)); + } + } + /// public bool IsEnabled(LogLevel logLevel) { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs index 945e6ebb23584e..3300fe4700c645 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs @@ -28,20 +28,37 @@ public JsonConsoleFormatter(IOptionsMonitor options public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) { - string message = logEntry.Formatter(logEntry.State, logEntry.Exception); - if (logEntry.Exception == null && message == null) + if (logEntry.State is BufferedLogRecord bufferedRecord) { - return; + string message = bufferedRecord.FormattedMessage ?? string.Empty; + if (bufferedRecord.Exception == null && message == null) + { + return; + } + + WriteInternal(scopeProvider, textWriter, message, bufferedRecord.LogLevel, logEntry.Category, bufferedRecord.EventId.Id, bufferedRecord.Exception, + bufferedRecord.Attributes.Count > 0, null, bufferedRecord.Attributes as IReadOnlyList>, bufferedRecord.Timestamp); } + else + { + string message = logEntry.Formatter(logEntry.State, logEntry.Exception); + if (logEntry.Exception == null && message == null) + { + return; + } - // We extract most of the work into a non-generic method to save code size. If this was left in the generic - // method, we'd get generic specialization for all TState parameters, but that's unnecessary. - WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.Category, logEntry.EventId.Id, logEntry.Exception, - logEntry.State != null, logEntry.State?.ToString(), logEntry.State as IReadOnlyCollection>); + DateTimeOffset stamp = FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now; + + // We extract most of the work into a non-generic method to save code size. If this was left in the generic + // method, we'd get generic specialization for all TState parameters, but that's unnecessary. + WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.Category, logEntry.EventId.Id, logEntry.Exception?.ToString(), + logEntry.State != null, logEntry.State?.ToString(), logEntry.State as IReadOnlyList>, stamp); + } } - private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel, - string category, int eventId, Exception? exception, bool hasState, string? stateMessage, IReadOnlyCollection>? stateProperties) + private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string? message, LogLevel logLevel, + string category, int eventId, string? exception, bool hasState, string? stateMessage, IReadOnlyList>? stateProperties, + DateTimeOffset stamp) { const int DefaultBufferSize = 1024; using (var output = new PooledByteBufferWriter(DefaultBufferSize)) @@ -52,8 +69,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex var timestampFormat = FormatterOptions.TimestampFormat; if (timestampFormat != null) { - DateTimeOffset dateTimeOffset = FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now; - writer.WriteString("Timestamp", dateTimeOffset.ToString(timestampFormat)); + writer.WriteString("Timestamp", stamp.ToString(timestampFormat)); } writer.WriteNumber(nameof(LogEntry.EventId), eventId); writer.WriteString(nameof(LogEntry.LogLevel), GetLogLevelString(logLevel)); @@ -62,7 +78,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex if (exception != null) { - writer.WriteString(nameof(Exception), exception.ToString()); + writer.WriteString(nameof(Exception), exception); } if (hasState) @@ -71,7 +87,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex writer.WriteString("Message", stateMessage); if (stateProperties != null) { - foreach (KeyValuePair item in stateProperties) + foreach (KeyValuePair item in stateProperties) { WriteItem(writer, item); } @@ -131,11 +147,11 @@ private void WriteScopeInformation(Utf8JsonWriter writer, IExternalScopeProvider writer.WriteStartArray("Scopes"); scopeProvider.ForEachScope((scope, state) => { - if (scope is IEnumerable> scopeItems) + if (scope is IEnumerable> scopeItems) { state.WriteStartObject(); state.WriteString("Message", scope.ToString()); - foreach (KeyValuePair item in scopeItems) + foreach (KeyValuePair item in scopeItems) { WriteItem(state, item); } @@ -150,7 +166,7 @@ private void WriteScopeInformation(Utf8JsonWriter writer, IExternalScopeProvider } } - private static void WriteItem(Utf8JsonWriter writer, KeyValuePair item) + private static void WriteItem(Utf8JsonWriter writer, KeyValuePair item) { var key = item.Key; switch (item.Value) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs index 79cacb9b3baafa..62743dcda071bc 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs @@ -46,19 +46,32 @@ public void Dispose() public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) { - string message = logEntry.Formatter(logEntry.State, logEntry.Exception); - if (logEntry.Exception == null && message == null) + if (logEntry.State is BufferedLogRecord bufferedRecord) { - return; + string message = bufferedRecord.FormattedMessage ?? string.Empty; + if (bufferedRecord.Exception == null && message == null) + { + return; + } + + WriteInternal(scopeProvider, textWriter, message, bufferedRecord.LogLevel, bufferedRecord.EventId.Id, bufferedRecord.Exception, logEntry.Category, bufferedRecord.Timestamp); } + else + { + string message = logEntry.Formatter(logEntry.State, logEntry.Exception); + if (logEntry.Exception == null && message == null) + { + return; + } - // We extract most of the work into a non-generic method to save code size. If this was left in the generic - // method, we'd get generic specialization for all TState parameters, but that's unnecessary. - WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.EventId.Id, logEntry.Exception, logEntry.Category); + // We extract most of the work into a non-generic method to save code size. If this was left in the generic + // method, we'd get generic specialization for all TState parameters, but that's unnecessary. + WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.EventId.Id, logEntry.Exception?.ToString(), logEntry.Category, GetCurrentDateTime()); + } } private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel, - int eventId, Exception? exception, string category) + int eventId, string? exception, string category, DateTimeOffset stamp) { ConsoleColors logLevelColors = GetLogLevelConsoleColors(logLevel); string logLevelString = GetLogLevelString(logLevel); @@ -67,8 +80,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex string? timestampFormat = FormatterOptions.TimestampFormat; if (timestampFormat != null) { - DateTimeOffset dateTimeOffset = GetCurrentDateTime(); - timestamp = dateTimeOffset.ToString(timestampFormat); + timestamp = stamp.ToString(timestampFormat); } if (timestamp != null) { @@ -114,7 +126,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex if (exception != null) { // exception message - WriteMessage(textWriter, exception.ToString(), singleLine); + WriteMessage(textWriter, exception, singleLine); } if (singleLine) { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs index 2d306fee1d0a4b..d2a110c856e9a4 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs @@ -36,19 +36,32 @@ public void Dispose() public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) { - string message = logEntry.Formatter(logEntry.State, logEntry.Exception); - if (logEntry.Exception == null && message == null) + if (logEntry.State is BufferedLogRecord bufferedRecord) { - return; + string message = bufferedRecord.FormattedMessage ?? string.Empty; + if (bufferedRecord.Exception == null && message == null) + { + return; + } + + WriteInternal(scopeProvider, textWriter, message, bufferedRecord.LogLevel, logEntry.Category, bufferedRecord.EventId.Id, bufferedRecord.Exception, bufferedRecord.Timestamp); } + else + { + string message = logEntry.Formatter(logEntry.State, logEntry.Exception); + if (logEntry.Exception == null && message == null) + { + return; + } - // We extract most of the work into a non-generic method to save code size. If this was left in the generic - // method, we'd get generic specialization for all TState parameters, but that's unnecessary. - WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.Category, logEntry.EventId.Id, logEntry.Exception); + // We extract most of the work into a non-generic method to save code size. If this was left in the generic + // method, we'd get generic specialization for all TState parameters, but that's unnecessary. + WriteInternal(scopeProvider, textWriter, message, logEntry.LogLevel, logEntry.Category, logEntry.EventId.Id, logEntry.Exception?.ToString(), GetCurrentDateTime()); + } } private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel, string category, - int eventId, Exception? exception) + int eventId, string? exception, DateTimeOffset stamp) { // systemd reads messages from standard out line-by-line in a 'message' format. // newline characters are treated as message delimiters, so we must replace them. @@ -64,8 +77,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex string? timestampFormat = FormatterOptions.TimestampFormat; if (timestampFormat != null) { - DateTimeOffset dateTimeOffset = GetCurrentDateTime(); - textWriter.Write(dateTimeOffset.ToString(timestampFormat)); + textWriter.Write(stamp.ToString(timestampFormat)); } // category and event id @@ -90,7 +102,7 @@ private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter tex if (exception != null) { textWriter.Write(' '); - WriteReplacingNewLine(textWriter, exception.ToString()); + WriteReplacingNewLine(textWriter, exception); } // newline delimiter diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs index 5a71dad69bbdbd..280308fd275006 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.RemoteExecutor; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Test.Console; using Microsoft.Extensions.Options; using Xunit; @@ -551,6 +552,69 @@ public void Log_LogsCorrectTimestamp(ConsoleLoggerFormat format, LogLevel level) } } + class BufferedLogRecordImpl : BufferedLogRecord + { + private readonly DateTimeOffset _timestamp; + private readonly LogLevel _level; + private readonly string _exception; + + public BufferedLogRecordImpl(DateTimeOffset timestamp, LogLevel level, string exception) + { + _timestamp = timestamp; + _level = level; + _exception = exception; + } + + public override DateTimeOffset Timestamp => _timestamp; + public override LogLevel LogLevel => _level; + public override EventId EventId => new EventId(0); + public override string? Exception => _exception; + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [MemberData(nameof(FormatsAndLevels))] + public void Log_LogsCorrectOverrideTimestamp(ConsoleLoggerFormat format, LogLevel level) + { + // Arrange + using var t = SetUp(new ConsoleLoggerOptions { TimestampFormat = "yyyy-MM-ddTHH:mm:sszz ", Format = format, UseUtcTimestamp = false }); + var levelPrefix = t.GetLevelPrefix(level); + var logger = t.Logger; + var sink = t.Sink; + var ex = new Exception("Exception message" + Environment.NewLine + "with a second line"); + var now = new DateTimeOffset(DateTime.Now); + var round_now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Offset); + var bufferedRecord = new BufferedLogRecordImpl(now, level, ex.ToString()); + + // Act + logger.LogRecords(new [] { bufferedRecord }); + + // Assert + switch (format) + { + case ConsoleLoggerFormat.Default: + { + Assert.Equal(3, sink.Writes.Count); + Assert.StartsWith(levelPrefix, sink.Writes[1].Message); + Assert.Matches(@"^\d{4}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\s$", sink.Writes[0].Message); + var parsedDateTime = DateTimeOffset.Parse(sink.Writes[0].Message.Trim()); + Assert.Equal(round_now, parsedDateTime); + } + break; + case ConsoleLoggerFormat.Systemd: + { + Assert.Single(sink.Writes); + Assert.StartsWith(levelPrefix, sink.Writes[0].Message); + var regexMatch = Regex.Match(sink.Writes[0].Message, @"^<\d>(\d{4}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2})\s[^\s]"); + Assert.True(regexMatch.Success); + var parsedDateTime = DateTimeOffset.Parse(regexMatch.Groups[1].Value); + Assert.Equal(round_now, parsedDateTime); + } + break; + default: + throw new ArgumentOutOfRangeException(nameof(format)); + } + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [MemberData(nameof(FormatsAndLevels))] public void WriteCore_LogsCorrectTimestampInUtc(ConsoleLoggerFormat format, LogLevel level) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj index 31c7dd27484029..2beeab918e6969 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj @@ -10,6 +10,6 @@ + - From 43061c1fb5ba00191c59334a89de5bda6ea68f68 Mon Sep 17 00:00:00 2001 From: "Martin Taillefer (from Dev Box)" Date: Tue, 16 Jul 2024 14:26:08 -0700 Subject: [PATCH 2/6] Update following review --- ...crosoft.Extensions.Logging.Abstractions.cs | 34 +++++++++---------- .../src/BufferedLogRecord.cs | 16 ++++----- .../src/IBufferedLogger.cs | 8 ++--- .../src/ConsoleLogger.cs | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs index 4f203b845c066d..209c30d4b8dd88 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs @@ -155,23 +155,6 @@ public enum LogLevel Critical = 5, None = 6, } - public abstract class BufferedLogRecord - { - public abstract System.DateTimeOffset Timestamp { get; } - public abstract Microsoft.Extensions.Logging.LogLevel LogLevel { get; } - public abstract Microsoft.Extensions.Logging.EventId EventId { get; } - public abstract string? Exception { get; } - public abstract System.Diagnostics.ActivitySpanId? ActivitySpanId { get; } - public abstract System.Diagnostics.ActivityTraceId? ActivityTraceId { get; } - public abstract int? ManagedThreadId { get; } - public abstract string? FormattedMessage { get; } - public abstract string? MessageTemplate { get; } - public abstract System.Collections.Generic.IReadOnlyList> Attributes { get; } - } - public interface IBufferedLogger - { - void LogRecords(System.Collections.Generic.IReadOnlyList records); - } } namespace Microsoft.Extensions.Logging.Abstractions { @@ -219,4 +202,21 @@ public NullLogger() { } public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { throw null; } public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception? exception, System.Func formatter) { } } + public abstract class BufferedLogRecord + { + public abstract System.DateTimeOffset Timestamp { get; } + public abstract Microsoft.Extensions.Logging.LogLevel LogLevel { get; } + public abstract Microsoft.Extensions.Logging.EventId EventId { get; } + public virtual string? Exception { get; } + public virtual System.Diagnostics.ActivitySpanId? ActivitySpanId { get; } + public virtual System.Diagnostics.ActivityTraceId? ActivityTraceId { get; } + public virtual int? ManagedThreadId { get; } + public virtual string? FormattedMessage { get; } + public virtual string? MessageTemplate { get; } + public virtual System.Collections.Generic.IReadOnlyList> Attributes { get; } + } + public interface IBufferedLogger + { + void LogRecords(System.Collections.Generic.IEnumerable records); + } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs index d558f124cc3434..8430ee01db712e 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; -namespace Microsoft.Extensions.Logging +namespace Microsoft.Extensions.Logging.Abstractions { /// /// State representing a buffered log record. @@ -34,36 +34,36 @@ public abstract class BufferedLogRecord /// /// Gets an optional exception string for this record. /// - public abstract string? Exception { get => null; } + public virtual string? Exception { get => null; } /// /// Gets an activity span id for this record, representing the state of the thread that created the record. /// - public abstract ActivitySpanId? ActivitySpanId { get => null; } + public virtual ActivitySpanId? ActivitySpanId { get => null; } /// /// Gets an activity trace id for this record, representing the state of the thread that created the record. /// - public abstract ActivityTraceId? ActivityTraceId { get => null; } + public virtual ActivityTraceId? ActivityTraceId { get => null; } /// /// Gets the ID of the thread that created the log record. /// - public abstract int? ManagedThreadId { get => null; } + public virtual int? ManagedThreadId { get => null; } /// /// Gets the formatted log message. /// - public abstract string? FormattedMessage { get => null; } + public virtual string? FormattedMessage { get => null; } /// /// Gets the original log message template. /// - public abstract string? MessageTemplate { get => null; } + public virtual string? MessageTemplate { get => null; } /// /// Gets the variable set of name/value pairs associated with the record. /// - public abstract IReadOnlyList> Attributes { get => Array.Empty>(); } + public virtual IReadOnlyList> Attributes { get => Array.Empty>(); } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs index 62beaa42171c1c..dbbfc36dc7f2b9 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; -namespace Microsoft.Extensions.Logging +namespace Microsoft.Extensions.Logging.Abstractions { /// /// Logging providers can implement this interface to indicate they support buffered logging. @@ -29,9 +29,9 @@ public interface IBufferedLogger /// /// The buffered log records to log. /// - /// Once this function returns, it should no longer access the records - /// or state referenced by these records. + /// Once this function returns, the implementation should no longer access the records + /// or state referenced by these records since they will get recycled. /// - void LogRecords(IReadOnlyList records); + void LogRecords(IEnumerable records); } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs index ebc8fcaacb5bee..c09fcc05823650 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs @@ -71,7 +71,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } /// - public void LogRecords(IReadOnlyList records) + public void LogRecords(IEnumerable records) { ThrowHelper.ThrowIfNull(records); From 5f6010711c0974a2799ff12fc624c79d988885ee Mon Sep 17 00:00:00 2001 From: Martin Taillefer Date: Tue, 16 Jul 2024 19:25:59 -0700 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Stephen Toub --- ...crosoft.Extensions.Logging.Abstractions.cs | 2 +- .../src/BufferedLogRecord.cs | 31 ++++++++++--------- .../src/IBufferedLogger.cs | 20 ++++++------ .../src/ConsoleFormatter.cs | 2 +- .../src/ConsoleLogger.cs | 10 +++--- .../ConsoleLoggerTest.cs | 2 +- 6 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs index 209c30d4b8dd88..c924efd9d2e02d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/ref/Microsoft.Extensions.Logging.Abstractions.cs @@ -217,6 +217,6 @@ public abstract class BufferedLogRecord } public interface IBufferedLogger { - void LogRecords(System.Collections.Generic.IEnumerable records); + void LogRecords(System.Collections.Generic.IEnumerable records); } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs index 8430ee01db712e..8f35ff97b35a6d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/BufferedLogRecord.cs @@ -8,11 +8,12 @@ namespace Microsoft.Extensions.Logging.Abstractions { /// - /// State representing a buffered log record. + /// Represents a buffered log record to be written in batch to an . /// /// - /// Objects of this type are reused over time to reduce - /// allocations. + /// Instances of this type may be pooled and reused. Implementations of must + /// not hold onto instance of passed to its method + /// beyond the invocation of that method. /// public abstract class BufferedLogRecord { @@ -22,48 +23,48 @@ public abstract class BufferedLogRecord public abstract DateTimeOffset Timestamp { get; } /// - /// Gets the record's log level, indicating it rough importance + /// Gets the record's logging severity level. /// public abstract LogLevel LogLevel { get; } /// - /// Gets the records event id. + /// Gets the record's event id. /// public abstract EventId EventId { get; } /// - /// Gets an optional exception string for this record. + /// Gets an exception string for this record. /// - public virtual string? Exception { get => null; } + public virtual string? Exception => null; /// - /// Gets an activity span id for this record, representing the state of the thread that created the record. + /// Gets an activity span ID for this record, representing the state of the thread that created the record. /// - public virtual ActivitySpanId? ActivitySpanId { get => null; } + public virtual ActivitySpanId? ActivitySpanId => null; /// - /// Gets an activity trace id for this record, representing the state of the thread that created the record. + /// Gets an activity trace ID for this record, representing the state of the thread that created the record. /// - public virtual ActivityTraceId? ActivityTraceId { get => null; } + public virtual ActivityTraceId? ActivityTraceId => null; /// /// Gets the ID of the thread that created the log record. /// - public virtual int? ManagedThreadId { get => null; } + public virtual int? ManagedThreadId => null; /// /// Gets the formatted log message. /// - public virtual string? FormattedMessage { get => null; } + public virtual string? FormattedMessage => null; /// /// Gets the original log message template. /// - public virtual string? MessageTemplate { get => null; } + public virtual string? MessageTemplate => null; /// /// Gets the variable set of name/value pairs associated with the record. /// - public virtual IReadOnlyList> Attributes { get => Array.Empty>(); } + public virtual IReadOnlyList> Attributes => []; } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs index dbbfc36dc7f2b9..769a0e6b6bf40b 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogger.cs @@ -6,21 +6,23 @@ namespace Microsoft.Extensions.Logging.Abstractions { /// - /// Logging providers can implement this interface to indicate they support buffered logging. + /// Represents the ability of a logging provider to support buffered logging. /// /// - /// A logging provider normally exposes an interface that gets invoked by the + /// A logging provider implements the interface that gets invoked by the /// logging infrastructure whenever it’s time to log a piece of state. /// - /// The logging infrastructure will type-test the ILogger object to determine if - /// it supports the IBufferedLogger interface also. If it does, that tells the + /// A logging provider may also optionally implement the interface. + /// The logging infrastructure may type-test the object to determine if + /// it supports the interface. If it does, that indicates to the /// logging infrastructure that the logging provider supports buffering. Whenever log - /// buffering is enabled, buffered log records will be delivered to the logging provider - /// via the IBufferedLogger interface. + /// buffering is enabled, buffered log records may be delivered to the logging provider + /// in a batch via . /// /// If a logging provider does not support log buffering, then it will always be given - /// unbuffered log records. In other words, whether or not buffering is requested by - /// the user, it will not happen for those log providers. + /// unbuffered log records. If a logging provider does support log buffering, whether its + /// or implementation is used is + /// determined by the log producer. /// public interface IBufferedLogger { @@ -30,7 +32,7 @@ public interface IBufferedLogger /// The buffered log records to log. /// /// Once this function returns, the implementation should no longer access the records - /// or state referenced by these records since they will get recycled. + /// or state referenced by these records since the instances may be reused to represent other logs. /// void LogRecords(IEnumerable records); } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs index 262a976d9e9524..c44aead487ab3c 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleFormatter.cs @@ -32,7 +32,7 @@ protected ConsoleFormatter(string name) /// Writes the log message to the specified TextWriter. /// /// - /// If the formatter wants to write colors to the console, it can do so by embedding ANSI color codes into the string + /// If the formatter wants to write colors to the console, it can do so by embedding ANSI color codes into the string. /// /// The log entry. /// The provider of scope data. diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs index c09fcc05823650..500b1ca0dd1c82 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs @@ -75,24 +75,26 @@ public void LogRecords(IEnumerable records) { ThrowHelper.ThrowIfNull(records); - t_stringWriter ??= new StringWriter(); + StringWriter writer = t_stringWriter ??= new StringWriter(); foreach (var rec in records) { - var logEntry = new LogEntry(rec.LogLevel, _name, rec.EventId, rec, null, (s, e) => s.FormattedMessage ?? string.Empty); - Formatter.Write(in logEntry, null, t_stringWriter); + var logEntry = new LogEntry(rec.LogLevel, _name, rec.EventId, rec, null, static (s, _) => s.FormattedMessage ?? string.Empty); + Formatter.Write(in logEntry, null, writer); - var sb = t_stringWriter.GetStringBuilder(); + var sb = writer.GetStringBuilder(); if (sb.Length == 0) { continue; } + string computedAnsiString = sb.ToString(); sb.Clear(); if (sb.Capacity > 1024) { sb.Capacity = 1024; } + _queueProcessor.EnqueueMessage(new LogMessageEntry(computedAnsiString, logAsError: rec.LogLevel >= Options.LogToStandardErrorThreshold)); } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs index 280308fd275006..900d667f8e198a 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs @@ -552,7 +552,7 @@ public void Log_LogsCorrectTimestamp(ConsoleLoggerFormat format, LogLevel level) } } - class BufferedLogRecordImpl : BufferedLogRecord + private sealed class BufferedLogRecordImpl : BufferedLogRecord { private readonly DateTimeOffset _timestamp; private readonly LogLevel _level; From f8221d0404f5fdf4731a8a5cc4e83eaeb61d5460 Mon Sep 17 00:00:00 2001 From: "Martin Taillefer (from Dev Box)" Date: Tue, 16 Jul 2024 20:05:54 -0700 Subject: [PATCH 4/6] Next batch of review changes --- .../src/JsonConsoleFormatter.cs | 9 ++------- .../src/SimpleConsoleFormatter.cs | 7 +------ .../src/SystemdConsoleFormatter.cs | 7 +------ 3 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs index 3300fe4700c645..cceb6d50f07413 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs @@ -31,13 +31,8 @@ public override void Write(in LogEntry logEntry, IExternalScopeP if (logEntry.State is BufferedLogRecord bufferedRecord) { string message = bufferedRecord.FormattedMessage ?? string.Empty; - if (bufferedRecord.Exception == null && message == null) - { - return; - } - - WriteInternal(scopeProvider, textWriter, message, bufferedRecord.LogLevel, logEntry.Category, bufferedRecord.EventId.Id, bufferedRecord.Exception, - bufferedRecord.Attributes.Count > 0, null, bufferedRecord.Attributes as IReadOnlyList>, bufferedRecord.Timestamp); + WriteInternal(null, textWriter, message, bufferedRecord.LogLevel, logEntry.Category, bufferedRecord.EventId.Id, bufferedRecord.Exception, + bufferedRecord.Attributes.Count > 0, null, bufferedRecord.Attributes, bufferedRecord.Timestamp); } else { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs index 62743dcda071bc..c839b0cb90f9b3 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs @@ -49,12 +49,7 @@ public override void Write(in LogEntry logEntry, IExternalScopeP if (logEntry.State is BufferedLogRecord bufferedRecord) { string message = bufferedRecord.FormattedMessage ?? string.Empty; - if (bufferedRecord.Exception == null && message == null) - { - return; - } - - WriteInternal(scopeProvider, textWriter, message, bufferedRecord.LogLevel, bufferedRecord.EventId.Id, bufferedRecord.Exception, logEntry.Category, bufferedRecord.Timestamp); + WriteInternal(null, textWriter, message, bufferedRecord.LogLevel, bufferedRecord.EventId.Id, bufferedRecord.Exception, logEntry.Category, bufferedRecord.Timestamp); } else { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs index d2a110c856e9a4..b4e9b3e2cb98f3 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs @@ -39,12 +39,7 @@ public override void Write(in LogEntry logEntry, IExternalScopeP if (logEntry.State is BufferedLogRecord bufferedRecord) { string message = bufferedRecord.FormattedMessage ?? string.Empty; - if (bufferedRecord.Exception == null && message == null) - { - return; - } - - WriteInternal(scopeProvider, textWriter, message, bufferedRecord.LogLevel, logEntry.Category, bufferedRecord.EventId.Id, bufferedRecord.Exception, bufferedRecord.Timestamp); + WriteInternal(null, textWriter, message, bufferedRecord.LogLevel, logEntry.Category, bufferedRecord.EventId.Id, bufferedRecord.Exception, bufferedRecord.Timestamp); } else { From 71df06498742e1caee8fe2976daacbdcba60edf9 Mon Sep 17 00:00:00 2001 From: "Martin Taillefer (from Dev Box)" Date: Tue, 16 Jul 2024 20:12:55 -0700 Subject: [PATCH 5/6] Another fix --- .../src/ConsoleLogger.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs index 500b1ca0dd1c82..b63dda3bc20183 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs @@ -90,13 +90,13 @@ public void LogRecords(IEnumerable records) string computedAnsiString = sb.ToString(); sb.Clear(); - if (sb.Capacity > 1024) - { - sb.Capacity = 1024; - } - _queueProcessor.EnqueueMessage(new LogMessageEntry(computedAnsiString, logAsError: rec.LogLevel >= Options.LogToStandardErrorThreshold)); } + + if (sb.Capacity > 1024) + { + sb.Capacity = 1024; + } } /// From afb13cdd40990a7faa2720ef7be240f4adce1f12 Mon Sep 17 00:00:00 2001 From: "Martin Taillefer (from Dev Box)" Date: Tue, 16 Jul 2024 20:23:54 -0700 Subject: [PATCH 6/6] More fixes --- .../Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs | 2 +- .../src/JsonConsoleFormatter.cs | 4 +++- .../src/SimpleConsoleFormatter.cs | 4 +++- .../src/SystemdConsoleFormatter.cs | 4 +++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs index b63dda3bc20183..c030eb059e372a 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLogger.cs @@ -77,12 +77,12 @@ public void LogRecords(IEnumerable records) StringWriter writer = t_stringWriter ??= new StringWriter(); + var sb = writer.GetStringBuilder(); foreach (var rec in records) { var logEntry = new LogEntry(rec.LogLevel, _name, rec.EventId, rec, null, static (s, _) => s.FormattedMessage ?? string.Empty); Formatter.Write(in logEntry, null, writer); - var sb = writer.GetStringBuilder(); if (sb.Length == 0) { continue; diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs index cceb6d50f07413..1be9425aa310b8 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs @@ -42,7 +42,9 @@ public override void Write(in LogEntry logEntry, IExternalScopeP return; } - DateTimeOffset stamp = FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now; + DateTimeOffset stamp = FormatterOptions.TimestampFormat != null + ? (FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now) + : DateTimeOffset.MinValue; // We extract most of the work into a non-generic method to save code size. If this was left in the generic // method, we'd get generic specialization for all TState parameters, but that's unnecessary. diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs index c839b0cb90f9b3..9d99836c45b130 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs @@ -155,7 +155,9 @@ static void WriteReplacing(TextWriter writer, string oldValue, string newValue, private DateTimeOffset GetCurrentDateTime() { - return FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now; + return FormatterOptions.TimestampFormat != null + ? (FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now) + : DateTimeOffset.MinValue; } private static string GetLogLevelString(LogLevel logLevel) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs index b4e9b3e2cb98f3..0df001a0267c75 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/SystemdConsoleFormatter.cs @@ -112,7 +112,9 @@ static void WriteReplacingNewLine(TextWriter writer, string message) private DateTimeOffset GetCurrentDateTime() { - return FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now; + return FormatterOptions.TimestampFormat != null + ? (FormatterOptions.UseUtcTimestamp ? DateTimeOffset.UtcNow : DateTimeOffset.Now) + : DateTimeOffset.MinValue; } private static string GetSyslogSeverityString(LogLevel logLevel)