diff --git a/CHANGELOG.md b/CHANGELOG.md index 54f8f56e94..452dee8c45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/samples/Sentry.Samples.Console.Basic/Program.cs b/samples/Sentry.Samples.Console.Basic/Program.cs index a75b3d379e..889dcde450 100644 --- a/samples/Sentry.Samples.Console.Basic/Program.cs +++ b/samples/Sentry.Samples.Console.Basic/Program.cs @@ -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 { @@ -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!"); } diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index c5d526253d..6567811762 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -84,7 +84,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { Template = template, Parameters = parameters.DrainToImmutable(), - ParentSpanId = spanId, + SpanId = spanId, }; log.SetDefaultAttributes(_options, _sdk); diff --git a/src/Sentry.Serilog/SentrySink.Structured.cs b/src/Sentry.Serilog/SentrySink.Structured.cs index 41e3f95693..cdff9166ac 100644 --- a/src/Sentry.Serilog/SentrySink.Structured.cs +++ b/src/Sentry.Serilog/SentrySink.Structured.cs @@ -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); diff --git a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs index 90f6df853a..3e42cc7005 100644 --- a/src/Sentry/Internal/DefaultSentryStructuredLogger.cs +++ b/src/Sentry/Internal/DefaultSentryStructuredLogger.cs @@ -55,7 +55,7 @@ private protected override void CaptureLog(SentryLogLevel level, string template { Template = template, Parameters = @params, - ParentSpanId = spanId, + SpanId = spanId, }; try diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index b3f7b78f5c..c57a08a84d 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -64,7 +64,7 @@ internal SentryLog(DateTimeOffset timestamp, SentryId traceId, SentryLogLevel le /// /// The span id of the span that was active when the log was collected. /// - public SpanId? ParentSpanId { get; init; } + public SpanId? SpanId { get; init; } /// /// Gets the attribute value associated with the specified key. @@ -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); @@ -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(); @@ -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(); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs index 3153c0526a..05e43b9e31 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs @@ -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(); span.TraceId.Returns(traceId); - span.SpanId.Returns(parentSpanId); + span.SpanId.Returns(spanId); Hub.GetSpan().Returns(span); } @@ -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"); @@ -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[] { 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"); @@ -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] diff --git a/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs b/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs index aa0806ad25..afef537dd7 100644 --- a/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs +++ b/test/Sentry.Serilog.Tests/SentrySinkTests.Structured.cs @@ -112,7 +112,7 @@ public void Emit_StructuredLogging_LogEvent(bool withActiveSpan) log.Parameters[1].Should().BeEquivalentTo(new KeyValuePair("Sequence", "[41, 42, 43]")); log.Parameters[2].Should().BeEquivalentTo(new KeyValuePair("Dictionary", """[("key": "value")]""")); log.Parameters[3].Should().BeEquivalentTo(new KeyValuePair("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"); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index e99eeb9ebd..e32ad6e60c 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -625,7 +625,7 @@ namespace Sentry [System.Runtime.CompilerServices.RequiredMember] public string Message { get; init; } public System.Collections.Immutable.ImmutableArray> 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; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index e99eeb9ebd..e32ad6e60c 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -625,7 +625,7 @@ namespace Sentry [System.Runtime.CompilerServices.RequiredMember] public string Message { get; init; } public System.Collections.Immutable.ImmutableArray> 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; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index e99eeb9ebd..e32ad6e60c 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -625,7 +625,7 @@ namespace Sentry [System.Runtime.CompilerServices.RequiredMember] public string Message { get; init; } public System.Collections.Immutable.ImmutableArray> 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; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index cf6bd61ad2..bd8e747a1b 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -610,7 +610,7 @@ namespace Sentry public Sentry.SentryLogLevel Level { get; init; } public string Message { get; init; } public System.Collections.Immutable.ImmutableArray> 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; } diff --git a/test/Sentry.Tests/SentryLogTests.cs b/test/Sentry.Tests/SentryLogTests.cs index 505249b95e..fe14705ad1 100644 --- a/test/Sentry.Tests/SentryLogTests.cs +++ b/test/Sentry.Tests/SentryLogTests.cs @@ -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); @@ -40,7 +40,7 @@ public void Protocol_Default_VerifyAttributes() { Template = "template", Parameters = ImmutableArray.Create(new KeyValuePair("param", "params")), - ParentSpanId = ParentSpanId, + SpanId = SpanId, }; log.SetAttribute("attribute", "value"); log.SetDefaultAttributes(options, sdk); @@ -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[] { 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(); @@ -83,7 +83,7 @@ public void WriteTo_NoParameters_NoTemplate(bool hasParameters) { Template = "template", Parameters = parameters, - ParentSpanId = ParentSpanId, + SpanId = SpanId, }; // Act @@ -176,7 +176,7 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryLog() { Template = "template", Parameters = ImmutableArray.Create(new KeyValuePair("0", "string"), new KeyValuePair("1", false), new KeyValuePair("2", 1), new KeyValuePair("3", 2.2)), - ParentSpanId = ParentSpanId, + SpanId = SpanId, }; log.SetAttribute("string-attribute", "string-value"); log.SetAttribute("boolean-attribute", true); @@ -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": { @@ -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" } } } @@ -416,7 +413,7 @@ public void GetTraceIdAndSpanId_WithActiveSpan_HasBothTraceIdAndSpanId() // Arrange var span = Substitute.For(); span.TraceId.Returns(SentryId.Create()); - span.SpanId.Returns(SpanId.Create()); + span.SpanId.Returns(Sentry.SpanId.Create()); var hub = Substitute.For(); hub.GetSpan().Returns(span); diff --git a/test/Sentry.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Tests/SentryStructuredLoggerTests.cs index b4e9e8c775..008a56df19 100644 --- a/test/Sentry.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Tests/SentryStructuredLoggerTests.cs @@ -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(); span.TraceId.Returns(TraceId); - span.SpanId.Returns(ParentSpanId.Value); + span.SpanId.Returns(SpanId.Value); Hub.GetSpan().Returns(span); ExpectedAttributes = new Dictionary(1) @@ -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 ExpectedAttributes { get; } @@ -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); @@ -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[] { 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) {