Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Remove `SentryLoggingOptions.ExperimentalLogging.MinimumLogLevel`. _Structured Logs_ can now be configured via the `"Sentry"` logging provider (e.g. in `appsettings.json` and `appsettings.{HostEnvironment}.json`) ([#4700](https://github.com/getsentry/sentry-dotnet/pull/4700))
- All logging provider types are _internal_ now in order to ensure configuration as intended ([#4700](https://github.com/getsentry/sentry-dotnet/pull/4700))
- Rename `SentryLog.ParentSpanId` to `SentryLog.SpanId` reflecting the protocol change ([#4778](https://github.com/getsentry/sentry-dotnet/pull/4778))

### Features

Expand Down
7 changes: 5 additions & 2 deletions samples/Sentry.Samples.Console.Basic/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ async Task SecondFunction()
{
// This is an example of capturing a handled exception.
SentrySdk.CaptureException(exception);
span.Finish(exception);

// This is an example of capturing a structured log.
SentrySdk.Logger.LogError(static log => log.SetAttribute("method", nameof(SecondFunction)),
"Error with message: {0}", exception.Message);

span.Finish(exception);
}
finally
{
Expand All @@ -112,9 +114,10 @@ async Task ThirdFunction()
// Simulate doing some work
await Task.Delay(100);

// This is an example of a structured log that is filtered via the 'SetBeforeSendLog' delegate above.
SentrySdk.Logger.LogFatal(static log => log.SetAttribute("suppress", true),
"Crash imminent!");

// This is an example of an unhandled exception. It will be captured automatically.
// This is an example of an unhandled exception. It will be captured automatically.
throw new InvalidOperationException("Something happened that crashed the app!");
}
2 changes: 1 addition & 1 deletion src/Sentry.Extensions.Logging/SentryStructuredLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
{
Template = template,
Parameters = parameters.DrainToImmutable(),
ParentSpanId = spanId,
SpanId = spanId,
};

log.SetDefaultAttributes(_options, _sdk);
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry.Serilog/SentrySink.Structured.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve
{
Template = template,
Parameters = parameters,
ParentSpanId = spanId,
SpanId = spanId,
};

log.SetDefaultAttributes(options, Sdk);
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry/Internal/DefaultSentryStructuredLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template
{
Template = template,
Parameters = @params,
ParentSpanId = spanId,
SpanId = spanId,
};

try
Expand Down
22 changes: 9 additions & 13 deletions src/Sentry/SentryLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel le
/// <summary>
/// The span id of the span that was active when the log was collected.
/// </summary>
public SpanId? ParentSpanId { get; init; }
public SpanId? SpanId { get; init; }
Copy link
Member Author

@Flash0ver Flash0ver Dec 1, 2025

Choose a reason for hiding this comment

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

@bitsandfoxes this change should not break sentry-unity.

However, it may break user code calling:

  • SentryOptions.SetBeforeSendLog(Func<SentryLog, SentryLog?>)


/// <summary>
/// Gets the attribute value associated with the specified key.
Expand Down Expand Up @@ -207,6 +207,12 @@ internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
writer.WritePropertyName("trace_id");
TraceId.WriteTo(writer, logger);

if (SpanId.HasValue)
{
writer.WritePropertyName("span_id");
SpanId.Value.WriteTo(writer, logger);
}

if (severityNumber.HasValue)
{
writer.WriteNumber("severity_number", severityNumber.Value);
Expand Down Expand Up @@ -235,16 +241,6 @@ internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)
SentryAttributeSerializer.WriteAttribute(writer, attribute.Key, attribute.Value, logger);
}

if (ParentSpanId.HasValue)
{
writer.WritePropertyName("sentry.trace.parent_span_id");
writer.WriteStartObject();
writer.WritePropertyName("value");
ParentSpanId.Value.WriteTo(writer, logger);
writer.WriteString("type", "string");
writer.WriteEndObject();
}

writer.WriteEndObject(); // attributes

writer.WriteEndObject();
Expand All @@ -260,8 +256,8 @@ internal static void GetTraceIdAndSpanId(IHub hub, out SentryId traceId, out Spa
return;
}

// set "sentry.trace.parent_span_id" to the ID of the Span that was active when the Log was collected
// do not set "sentry.trace.parent_span_id" if there was no active Span
// set "span_id" to the ID of the Span that was active when the Log was collected
// do not set "span_id" if there was no active Span
spanId = null;

var scope = hub.GetScope();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ public Fixture()
public void EnableHub(bool isEnabled) => Hub.IsEnabled.Returns(isEnabled);
public void EnableLogs(bool isEnabled) => Options.Value.EnableLogs = isEnabled;

public void WithActiveSpan(SentryId traceId, SpanId parentSpanId)
public void WithActiveSpan(SentryId traceId, SpanId spanId)
{
var span = Substitute.For<ISpan>();
span.TraceId.Returns(traceId);
span.SpanId.Returns(parentSpanId);
span.SpanId.Returns(spanId);
Hub.GetSpan().Returns(span);
}

Expand Down Expand Up @@ -82,8 +82,8 @@ public void Dispose()
public void Log_LogLevel_CaptureLog(LogLevel logLevel, SentryLogLevel expectedLevel)
{
var traceId = SentryId.Create();
var parentSpanId = SpanId.Create();
_fixture.WithActiveSpan(traceId, parentSpanId);
var spanId = SpanId.Create();
_fixture.WithActiveSpan(traceId, spanId);
var logger = _fixture.GetSut();

EventId eventId = new(123, "EventName");
Expand All @@ -106,7 +106,7 @@ public void Log_LogLevel_CaptureLog(LogLevel logLevel, SentryLogLevel expectedLe
log.Message.Should().Be("Message with argument.");
log.Template.Should().Be(message);
log.Parameters.Should().BeEquivalentTo(new KeyValuePair<string, object>[] { new("Argument", "argument") });
log.ParentSpanId.Should().Be(parentSpanId);
log.SpanId.Should().Be(spanId);
log.AssertAttribute("sentry.environment", "my-environment");
log.AssertAttribute("sentry.release", "my-release");
log.AssertAttribute("sentry.origin", "auto.log.extensions_logging");
Expand Down Expand Up @@ -139,7 +139,7 @@ public void Log_WithoutActiveSpan_CaptureLog()

var log = _fixture.CapturedLogs.Dequeue();
log.TraceId.Should().Be(scope.PropagationContext.TraceId);
log.ParentSpanId.Should().BeNull();
log.SpanId.Should().BeNull();
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void Emit_StructuredLogging_LogEvent(bool withActiveSpan)
log.Parameters[1].Should().BeEquivalentTo(new KeyValuePair<string, string>("Sequence", "[41, 42, 43]"));
log.Parameters[2].Should().BeEquivalentTo(new KeyValuePair<string, string>("Dictionary", """[("key": "value")]"""));
log.Parameters[3].Should().BeEquivalentTo(new KeyValuePair<string, string>("Structure", """[42, "42"]"""));
log.ParentSpanId.Should().Be(withActiveSpan ? _fixture.Hub.GetSpan()!.SpanId : null);
log.SpanId.Should().Be(withActiveSpan ? _fixture.Hub.GetSpan()!.SpanId : null);

log.TryGetAttribute("sentry.environment", out object? environment).Should().BeTrue();
environment.Should().Be("test-environment");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ namespace Sentry
[System.Runtime.CompilerServices.RequiredMember]
public string Message { get; init; }
public System.Collections.Immutable.ImmutableArray<System.Collections.Generic.KeyValuePair<string, object>> Parameters { get; init; }
public Sentry.SpanId? ParentSpanId { get; init; }
public Sentry.SpanId? SpanId { get; init; }
public string? Template { get; init; }
[System.Runtime.CompilerServices.RequiredMember]
public System.DateTimeOffset Timestamp { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ namespace Sentry
[System.Runtime.CompilerServices.RequiredMember]
public string Message { get; init; }
public System.Collections.Immutable.ImmutableArray<System.Collections.Generic.KeyValuePair<string, object>> Parameters { get; init; }
public Sentry.SpanId? ParentSpanId { get; init; }
public Sentry.SpanId? SpanId { get; init; }
public string? Template { get; init; }
[System.Runtime.CompilerServices.RequiredMember]
public System.DateTimeOffset Timestamp { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ namespace Sentry
[System.Runtime.CompilerServices.RequiredMember]
public string Message { get; init; }
public System.Collections.Immutable.ImmutableArray<System.Collections.Generic.KeyValuePair<string, object>> Parameters { get; init; }
public Sentry.SpanId? ParentSpanId { get; init; }
public Sentry.SpanId? SpanId { get; init; }
public string? Template { get; init; }
[System.Runtime.CompilerServices.RequiredMember]
public System.DateTimeOffset Timestamp { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ namespace Sentry
public Sentry.SentryLogLevel Level { get; init; }
public string Message { get; init; }
public System.Collections.Immutable.ImmutableArray<System.Collections.Generic.KeyValuePair<string, object>> Parameters { get; init; }
public Sentry.SpanId? ParentSpanId { get; init; }
public Sentry.SpanId? SpanId { get; init; }
public string? Template { get; init; }
public System.DateTimeOffset Timestamp { get; init; }
public Sentry.SentryId TraceId { get; init; }
Expand Down
17 changes: 7 additions & 10 deletions test/Sentry.Tests/SentryLogTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class SentryLogTests
{
private static readonly DateTimeOffset Timestamp = new(2025, 04, 22, 14, 51, 00, 789, TimeSpan.FromHours(2));
private static readonly SentryId TraceId = SentryId.Create();
private static readonly SpanId? ParentSpanId = SpanId.Create();
private static readonly SpanId? SpanId = Sentry.SpanId.Create();

private static readonly ISystemClock Clock = new MockClock(Timestamp);

Expand Down Expand Up @@ -40,7 +40,7 @@ public void Protocol_Default_VerifyAttributes()
{
Template = "template",
Parameters = ImmutableArray.Create(new KeyValuePair<string, object>("param", "params")),
ParentSpanId = ParentSpanId,
SpanId = SpanId,
};
log.SetAttribute("attribute", "value");
log.SetDefaultAttributes(options, sdk);
Expand All @@ -51,7 +51,7 @@ public void Protocol_Default_VerifyAttributes()
log.Message.Should().Be("message");
log.Template.Should().Be("template");
log.Parameters.Should().BeEquivalentTo(new KeyValuePair<string, object>[] { new("param", "params"), });
log.ParentSpanId.Should().Be(ParentSpanId);
log.SpanId.Should().Be(SpanId);

// should only show up in sdk integrations
log.TryGetAttribute("sentry.origin", out object origin).Should().BeFalse();
Expand Down Expand Up @@ -83,7 +83,7 @@ public void WriteTo_NoParameters_NoTemplate(bool hasParameters)
{
Template = "template",
Parameters = parameters,
ParentSpanId = ParentSpanId,
SpanId = SpanId,
};

// Act
Expand Down Expand Up @@ -176,7 +176,7 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog()
{
Template = "template",
Parameters = ImmutableArray.Create(new KeyValuePair<string, object>("0", "string"), new KeyValuePair<string, object>("1", false), new KeyValuePair<string, object>("2", 1), new KeyValuePair<string, object>("3", 2.2)),
ParentSpanId = ParentSpanId,
SpanId = SpanId,
};
log.SetAttribute("string-attribute", "string-value");
log.SetAttribute("boolean-attribute", true);
Expand Down Expand Up @@ -212,6 +212,7 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog()
"level": "fatal",
"body": "message",
"trace_id": "{{TraceId.ToString()}}",
"span_id": "{{SpanId.ToString()}}",
"severity_number": 24,
"attributes": {
"sentry.message.template": {
Expand Down Expand Up @@ -265,10 +266,6 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog()
"sentry.sdk.version": {
"value": "1.2.3-test+Sentry",
"type": "string"
},
"sentry.trace.parent_span_id": {
"value": "{{ParentSpanId.ToString()}}",
"type": "string"
}
}
}
Expand Down Expand Up @@ -416,7 +413,7 @@ public void GetTraceIdAndSpanId_WithActiveSpan_HasBothTraceIdAndSpanId()
// Arrange
var span = Substitute.For<ISpan>();
span.TraceId.Returns(SentryId.Create());
span.SpanId.Returns(SpanId.Create());
span.SpanId.Returns(Sentry.SpanId.Create());

var hub = Substitute.For<IHub>();
hub.GetSpan().Returns(span);
Expand Down
10 changes: 5 additions & 5 deletions test/Sentry.Tests/SentryStructuredLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ public Fixture()
BatchSize = 2;
BatchTimeout = Timeout.InfiniteTimeSpan;
TraceId = SentryId.Create();
ParentSpanId = SpanId.Create();
SpanId = Sentry.SpanId.Create();

Hub.IsEnabled.Returns(true);

var span = Substitute.For<ISpan>();
span.TraceId.Returns(TraceId);
span.SpanId.Returns(ParentSpanId.Value);
span.SpanId.Returns(SpanId.Value);
Hub.GetSpan().Returns(span);

ExpectedAttributes = new Dictionary<string, string>(1)
Expand All @@ -44,7 +44,7 @@ public Fixture()
public int BatchSize { get; set; }
public TimeSpan BatchTimeout { get; set; }
public SentryId TraceId { get; private set; }
public SpanId? ParentSpanId { get; private set; }
public SpanId? SpanId { get; private set; }

public Dictionary<string, string> ExpectedAttributes { get; }

Expand All @@ -55,7 +55,7 @@ public void WithoutActiveSpan()
var scope = new Scope();
Hub.SubstituteConfigureScope(scope);
TraceId = scope.PropagationContext.TraceId;
ParentSpanId = null;
SpanId = null;
}

public SentryStructuredLogger GetSut() => SentryStructuredLogger.Create(Hub, Options, Clock, BatchSize, BatchTimeout);
Expand Down Expand Up @@ -288,7 +288,7 @@ public static void AssertLog(this SentryStructuredLoggerTests.Fixture fixture, S
log.Message.Should().Be("Template string with arguments: string, True, 1, 2.2");
log.Template.Should().Be("Template string with arguments: {0}, {1}, {2}, {3}");
log.Parameters.Should().BeEquivalentTo(new KeyValuePair<string, object>[] { new("0", "string"), new("1", true), new("2", 1), new("3", 2.2), });
log.ParentSpanId.Should().Be(fixture.ParentSpanId);
log.SpanId.Should().Be(fixture.SpanId);

foreach (var expectedAttribute in fixture.ExpectedAttributes)
{
Expand Down
Loading