diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogRecordMetadata.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogRecordMetadata.cs
new file mode 100644
index 00000000000000..9c1b142e25d9d7
--- /dev/null
+++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/IBufferedLogRecordMetadata.cs
@@ -0,0 +1,39 @@
+// 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.Diagnostics;
+
+namespace Microsoft.Extensions.Logging
+{
+ ///
+ /// Holds metadata for a buffered log record.
+ ///
+ ///
+ /// If the logging infrastructure decides to buffer log records in memory, it will ensure that the
+ /// TState value delivered to logging providers implements this interface. The logging providers
+ /// can then use the metadata to augment the log records they emit.
+ ///
+ public interface IBufferedLogRecordMetadata
+ {
+ ///
+ /// Gets the time when the log record was recorded.
+ ///
+ public DateTimeOffset? CreationTime { get; }
+
+ ///
+ /// Gets the ID of the thread that created the log record.
+ ///
+ public int? ManagedThreadId { get; }
+
+ ///
+ /// Gets the ID of the span in effect when the log record was created.
+ ///
+ public ActivitySpanId? SpanId { get; }
+
+ ///
+ /// Gets the ID of the trace in effect when the log record was created.
+ ///
+ public ActivityTraceId? TraceId { get; }
+ }
+}
diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
index 945e6ebb23584e..a14e4969ad7563 100644
--- a/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
+++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/JsonConsoleFormatter.cs
@@ -34,14 +34,27 @@ public override void Write(in LogEntry logEntry, IExternalScopeP
return;
}
+ DateTimeOffset stamp;
+ if (logEntry.State is IBufferedLogRecordMetadata metadata)
+ {
+
+ // TODO: should this be adjusted between UTC and local time
+ stamp = FormatterOptions.UseUtcTimestamp ? metadata.CreationTime : metadata.CreationTime.ToLocalTime();
+ }
+ else
+ {
+ 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,
- logEntry.State != null, logEntry.State?.ToString(), logEntry.State as IReadOnlyCollection>);
+ logEntry.State != null, logEntry.State?.ToString(), logEntry.State as IReadOnlyCollection>, stamp);
}
private void WriteInternal(IExternalScopeProvider? scopeProvider, TextWriter textWriter, string message, LogLevel logLevel,
- string category, int eventId, Exception? exception, bool hasState, string? stateMessage, IReadOnlyCollection>? stateProperties)
+ string category, int eventId, Exception? exception, bool hasState, string? stateMessage, IReadOnlyCollection>? stateProperties,
+ DateTimeOffset stamp)
{
const int DefaultBufferSize = 1024;
using (var output = new PooledByteBufferWriter(DefaultBufferSize))
@@ -52,8 +65,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