Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
[OTEL1001]OpenTelemetry.Logs.LogRecordData.LogRecordData() -> void
[OTEL1001]OpenTelemetry.Logs.LogRecordData.LogRecordData(in System.Diagnostics.ActivityContext activityContext) -> void
[OTEL1001]OpenTelemetry.Logs.LogRecordData.LogRecordData(System.Diagnostics.Activity? activity) -> void
[OTEL1001]OpenTelemetry.Logs.LogRecordData.ObservedTimestamp.get -> System.DateTime
[OTEL1001]OpenTelemetry.Logs.LogRecordData.ObservedTimestamp.set -> void
[OTEL1001]OpenTelemetry.Logs.LogRecordData.Severity.get -> OpenTelemetry.Logs.LogRecordSeverity?
[OTEL1001]OpenTelemetry.Logs.LogRecordData.Severity.set -> void
[OTEL1001]OpenTelemetry.Logs.LogRecordData.SeverityText.get -> string?
Expand Down
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Added `ObservedTimestamp` property to `LogRecordData`. Note that `LogRecordData`
is only public in pre-release versions of the package.
([#6979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6979))

## 1.15.1

Released 2026-Mar-27
Expand Down
22 changes: 19 additions & 3 deletions src/OpenTelemetry.Api/Logs/LogRecordData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ struct LogRecordData
{
internal DateTime TimestampBacking = DateTime.UtcNow;

internal DateTime ObservedTimestampBacking = DateTime.UtcNow;
Copy link
Copy Markdown
Member

@Kielek Kielek Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider, I think that we should avoid any drifts between these values by default.

Suggested change
internal DateTime ObservedTimestampBacking = DateTime.UtcNow;
internal DateTime ObservedTimestampBacking = TimestampBacking;


/// <summary>
/// Initializes a new instance of the <see cref="LogRecordData"/> struct.
/// </summary>
/// <remarks>
/// Notes:
/// <list type="bullet">
/// <item>The <see cref="Timestamp"/> property is initialized to <see
/// <item>The <see cref="Timestamp"/> and <see cref="ObservedTimestamp"/> properties are initialized to <see
/// cref="DateTime.UtcNow"/> automatically.</item>
/// <item>The <see cref="TraceId"/>, <see cref="SpanId"/>, and <see
/// cref="TraceFlags"/> properties will be set using the <see
Expand All @@ -50,7 +52,7 @@ public LogRecordData()
/// Initializes a new instance of the <see cref="LogRecordData"/> struct.
/// </summary>
/// <remarks>
/// Note: The <see cref="Timestamp"/> property is initialized to <see
/// Note: <see cref="Timestamp"/> and <see cref="ObservedTimestamp"/> properties are initialized to <see
/// cref="DateTime.UtcNow"/> automatically.
/// </remarks>
/// <param name="activity">Optional <see cref="Activity"/> used to populate
Expand All @@ -65,7 +67,7 @@ public LogRecordData(Activity? activity)
/// Initializes a new instance of the <see cref="LogRecordData"/> struct.
/// </summary>
/// <remarks>
/// Note: The <see cref="Timestamp"/> property is initialized to <see
/// Note: <see cref="Timestamp"/> and <see cref="ObservedTimestamp"/> properties are initialized to <see
/// cref="DateTime.UtcNow"/> automatically.
/// </remarks>
/// <param name="activityContext"><see cref="ActivityContext"/> used to
Expand All @@ -92,6 +94,20 @@ public DateTime Timestamp
set => this.TimestampBacking = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value;
}

/// <summary>
/// Gets or sets the timestamp when the log was recorded by OpenTelemetry's code.
/// </summary>
/// <remarks>
/// Note: If <see cref="ObservedTimestamp"/> is set to a value with <see
/// cref="DateTimeKind.Local"/> it will be automatically converted to
/// UTC using <see cref="DateTime.ToUniversalTime"/>.
/// </remarks>
public DateTime ObservedTimestamp
{
readonly get => this.ObservedTimestampBacking;
set { this.ObservedTimestampBacking = value.Kind == DateTimeKind.Local ? value.ToUniversalTime() : value; }
}

/// <summary>
/// Gets or sets the log <see cref="ActivityTraceId"/>.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/OpenTelemetry.Exporter.Console/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* `ObservedTimestamp` will now be exported for logs.
([#6979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6979))

## 1.15.1

Released 2026-Mar-27
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public override ExportResult Export(in Batch<LogRecord> batch)
foreach (var logRecord in batch)
{
this.WriteLine($"{"LogRecord.Timestamp:",-RightPaddingLength}{logRecord.Timestamp:yyyy-MM-ddTHH:mm:ss.fffffffZ}");
this.WriteLine($"{"LogRecord.ObservedTimestamp:",-RightPaddingLength}{logRecord.ObservedTimestamp:yyyy-MM-ddTHH:mm:ss.fffffffZ}");

if (logRecord.TraceId != default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* `observed_time_unix_nano` will no longer always be identical to `time_unix_nano`
when using the logs bridge API. By default, it will instead be set to the actual
observed time of the log record.
([#6979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6979))

## 1.15.1

Released 2026-Mar-27
Expand Down
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar change should be done for ConsoleExporter.
Together with Timestamp, we should log also Observed timestamp.

Please add also CHANGELOG entry.

Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ internal static int WriteLogRecord(byte[] buffer, int writePosition, SdkLimitOpt
otlpTagWriterState.WritePosition += ReserveSizeForLength;

var timestamp = (ulong)logRecord.Timestamp.ToUnixTimeNanoseconds();
var observedTimestamp = logRecord.ObservedTimestamp == logRecord.Timestamp ? timestamp
Comment thread
Kielek marked this conversation as resolved.
: (ulong)logRecord.ObservedTimestamp.ToUnixTimeNanoseconds();
otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed64WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Time_Unix_Nano, timestamp);
otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed64WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Observed_Time_Unix_Nano, timestamp);
otlpTagWriterState.WritePosition = ProtobufSerializer.WriteFixed64WithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Observed_Time_Unix_Nano, observedTimestamp);

otlpTagWriterState.WritePosition = ProtobufSerializer.WriteEnumWithTag(otlpTagWriterState.Buffer, otlpTagWriterState.WritePosition, ProtobufOtlpLogFieldNumberConstants.LogRecord_Severity_Number, logRecord.Severity.HasValue ? (int)logRecord.Severity : 0);

Expand Down
2 changes: 2 additions & 0 deletions src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OpenTelemetry.Logs.LogRecord.ObservedTimestamp.get -> System.DateTime
OpenTelemetry.Logs.LogRecord.ObservedTimestamp.set -> void
3 changes: 3 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Added `ObservedTimestamp` property to `LogRecord`.
([#6979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6979))

## 1.15.1

Released 2026-Mar-27
Expand Down
1 change: 1 addition & 0 deletions src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
ref var data = ref record.Data;

data.TimestampBacking = DateTime.UtcNow;
data.ObservedTimestampBacking = data.TimestampBacking;

SetLogRecordSeverityFields(ref data, logLevel);

Expand Down
15 changes: 15 additions & 0 deletions src/OpenTelemetry/Logs/LogRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ internal LogRecord(
this.Data = new(activity)
{
TimestampBacking = timestamp,
ObservedTimestampBacking = timestamp,

Body = formattedMessage,
};
Expand Down Expand Up @@ -114,6 +115,20 @@ public DateTime Timestamp
set => this.Data.Timestamp = value;
}

/// <summary>
/// Gets or sets the observed timestamp.
/// </summary>
/// <remarks>
/// Note: If <see cref="ObservedTimestamp"/> is set to a value with <see
/// cref="DateTimeKind.Local"/> it will be automatically converted to
/// UTC using <see cref="DateTime.ToUniversalTime"/>.
/// </remarks>
public DateTime ObservedTimestamp
{
get => this.Data.ObservedTimestamp;
set => this.Data.ObservedTimestamp = value;
}

/// <summary>
/// Gets or sets the log <see cref="ActivityTraceId"/>.
/// </summary>
Expand Down
19 changes: 19 additions & 0 deletions test/OpenTelemetry.Api.Tests/Logs/LogRecordDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ record = default;
Assert.Equal(now.ToUniversalTime(), record.Timestamp);
}

[Fact]
public void ObservedTimestampTest()
{
var nowUtc = DateTime.UtcNow;

var record = new LogRecordData();
Assert.True(record.ObservedTimestamp >= nowUtc);

record = default;
Assert.Equal(DateTime.MinValue, record.ObservedTimestamp);

var now = DateTime.Now;

record.ObservedTimestamp = now;

Assert.Equal(DateTimeKind.Utc, record.ObservedTimestamp.Kind);
Assert.Equal(now.ToUniversalTime(), record.ObservedTimestamp);
}

[Fact]
public void SetActivityContextTest()
{
Expand Down
23 changes: 23 additions & 0 deletions test/OpenTelemetry.Tests/Logs/LogRecordTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,29 @@ public void CheckOriginalFormatAtArbitraryPosition(bool includeFormattedMessage,
originalFormatAttribute.Value);
}

[Fact]
public void ObservedTimestampTest()
{
using var loggerFactory = InitializeLoggerFactory(out var exportedItems);
var logger = loggerFactory.CreateLogger<LogRecordTests>();

var before = DateTime.UtcNow;
logger.Log();
var after = DateTime.UtcNow;

var record = exportedItems[0];

// ObservedTimestamp is set by the SDK to when the log was captured.
Assert.InRange(record.ObservedTimestamp, before, after);
Assert.Equal(DateTimeKind.Utc, record.ObservedTimestamp.Kind);

// Verify the setter converts local time to UTC.
var localNow = DateTime.Now;
record.ObservedTimestamp = localNow;
Assert.Equal(DateTimeKind.Utc, record.ObservedTimestamp.Kind);
Assert.Equal(localNow.ToUniversalTime(), record.ObservedTimestamp);
}

private static ILoggerFactory InitializeLoggerFactory(out List<LogRecord> exportedItems, Action<OpenTelemetryLoggerOptions>? configure = null)
{
var items = exportedItems = [];
Expand Down
Loading