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 @@ -10,6 +10,7 @@
### Fixes

- Templates are no longer sent with Structured Logs that have no parameters ([#4544](https://github.com/getsentry/sentry-dotnet/pull/4544))
- Parent-Span-IDs are no longer sent with Structured Logs when recorded without an active Span ([#4565](https://github.com/getsentry/sentry-dotnet/pull/4565))
- Upload linked PDBs to fix non-IL-stripped symbolication for iOS ([#4527](https://github.com/getsentry/sentry-dotnet/pull/4527))
- In MAUI Android apps, generate and inject UUID to APK and upload ProGuard mapping to Sentry with the UUID ([#4532](https://github.com/getsentry/sentry-dotnet/pull/4532))
- Fixed WASM0001 warning when building Blazor WebAssembly projects ([#4519](https://github.com/getsentry/sentry-dotnet/pull/4519))
Expand Down
6 changes: 3 additions & 3 deletions src/Sentry.Extensions.Logging/SentryStructuredLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
}

var timestamp = _clock.GetUtcNow();
var traceHeader = _hub.GetTraceHeader() ?? SentryTraceHeader.Empty;
SentryLog.GetTraceIdAndSpanId(_hub, out var traceId, out var spanId);

var level = logLevel.ToSentryLogLevel();
Debug.Assert(level != default);
Expand Down Expand Up @@ -81,11 +81,11 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
}
}

SentryLog log = new(timestamp, traceHeader.TraceId, level, message)
SentryLog log = new(timestamp, traceId, level, message)
{
Template = template,
Parameters = parameters.DrainToImmutable(),
ParentSpanId = traceHeader.SpanId,
ParentSpanId = spanId,
};

log.SetDefaultAttributes(_options, _sdk);
Expand Down
24 changes: 1 addition & 23 deletions src/Sentry.Serilog/SentrySink.Structured.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal sealed partial class SentrySink
{
private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEvent logEvent, string formatted, string? template)
{
GetTraceIdAndSpanId(hub, out var traceId, out var spanId);
SentryLog.GetTraceIdAndSpanId(hub, out var traceId, out var spanId);
GetStructuredLoggingParametersAndAttributes(logEvent, out var parameters, out var attributes);

SentryLog log = new(logEvent.Timestamp, traceId, logEvent.Level.ToSentryLogLevel(), formatted)
Expand All @@ -27,28 +27,6 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve
hub.Logger.CaptureLog(log);
}

private static void GetTraceIdAndSpanId(IHub hub, out SentryId traceId, out SpanId? spanId)
{
var span = hub.GetSpan();
if (span is not null)
{
traceId = span.TraceId;
spanId = span.SpanId;
return;
}

var scope = hub.GetScope();
if (scope is not null)
{
traceId = scope.PropagationContext.TraceId;
spanId = scope.PropagationContext.SpanId;
return;
}

traceId = SentryId.Empty;
spanId = null;
}

private static void GetStructuredLoggingParametersAndAttributes(LogEvent logEvent, out ImmutableArray<KeyValuePair<string, object>> parameters, out List<KeyValuePair<string, object>> attributes)
{
var propertyNames = new HashSet<string>();
Expand Down
6 changes: 3 additions & 3 deletions src/Sentry/Internal/DefaultSentryStructuredLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal DefaultSentryStructuredLogger(IHub hub, SentryOptions options, ISystemC
private protected override void CaptureLog(SentryLogLevel level, string template, object[]? parameters, Action<SentryLog>? configureLog)
{
var timestamp = _clock.GetUtcNow();
var traceHeader = _hub.GetTraceHeader() ?? SentryTraceHeader.Empty;
SentryLog.GetTraceIdAndSpanId(_hub, out var traceId, out var spanId);

string message;
try
Expand All @@ -51,11 +51,11 @@ private protected override void CaptureLog(SentryLogLevel level, string template
@params = builder.DrainToImmutable();
}

SentryLog log = new(timestamp, traceHeader.TraceId, level, message)
SentryLog log = new(timestamp, traceId, level, message)
{
Template = template,
Parameters = @params,
ParentSpanId = traceHeader.SpanId,
ParentSpanId = spanId,
};

try
Expand Down
26 changes: 26 additions & 0 deletions src/Sentry/SentryLog.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
using Sentry.Internal;
using Sentry.Protocol;

namespace Sentry;
Expand Down Expand Up @@ -243,4 +244,29 @@ internal void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger)

writer.WriteEndObject();
}

internal static void GetTraceIdAndSpanId(IHub hub, out SentryId traceId, out SpanId? spanId)
{
var activeSpan = hub.GetSpan();
if (activeSpan is not null)
{
traceId = activeSpan.TraceId;
spanId = activeSpan.SpanId;
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
spanId = null;

var scope = hub.GetScope();
if (scope is not null)
{
traceId = scope.PropagationContext.TraceId;
return;
}

Debug.Assert(hub is not Hub, "In case of a 'full' Hub, there is always a Scope. Otherwise (disabled) there is no Scope, but this branch should be unreachable.");
traceId = SentryId.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ public Fixture()
public void EnableLogs(bool isEnabled) => Options.Value.Experimental.EnableLogs = isEnabled;
public void SetMinimumLogLevel(LogLevel logLevel) => Options.Value.ExperimentalLogging.MinimumLogLevel = logLevel;

public void WithTraceHeader(SentryId traceId, SpanId parentSpanId)
public void WithActiveSpan(SentryId traceId, SpanId parentSpanId)
{
var traceHeader = new SentryTraceHeader(traceId, parentSpanId, null);
Hub.GetTraceHeader().Returns(traceHeader);
var span = Substitute.For<ISpan>();
span.TraceId.Returns(traceId);
span.SpanId.Returns(parentSpanId);
Hub.GetSpan().Returns(span);
}

public SentryStructuredLogger GetSut()
Expand Down Expand Up @@ -83,7 +85,7 @@ public void Log_LogLevel_CaptureLog(LogLevel logLevel, SentryLogLevel expectedLe
{
var traceId = SentryId.Create();
var parentSpanId = SpanId.Create();
_fixture.WithTraceHeader(traceId, parentSpanId);
_fixture.WithActiveSpan(traceId, parentSpanId);
var logger = _fixture.GetSut();

EventId eventId = new(123, "EventName");
Expand Down Expand Up @@ -127,15 +129,18 @@ public void Log_LogLevelNone_DoesNotCaptureLog()
}

[Fact]
public void Log_WithoutTraceHeader_CaptureLog()
public void Log_WithoutActiveSpan_CaptureLog()
{
var scope = new Scope(_fixture.Options.Value);
_fixture.Hub.GetSpan().Returns((ISpan?)null);
_fixture.Hub.SubstituteConfigureScope(scope);
var logger = _fixture.GetSut();

logger.Log(LogLevel.Information, new EventId(123, "EventName"), new InvalidOperationException("message"), "Message with {Argument}.", "argument");

var log = _fixture.CapturedLogs.Dequeue();
log.TraceId.Should().Be(SentryTraceHeader.Empty.TraceId);
log.ParentSpanId.Should().Be(SentryTraceHeader.Empty.SpanId);
log.TraceId.Should().Be(scope.PropagationContext.TraceId);
log.ParentSpanId.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 : _fixture.Scope.PropagationContext.SpanId);
log.ParentSpanId.Should().Be(withActiveSpan ? _fixture.Hub.GetSpan()!.SpanId : null);

log.TryGetAttribute("sentry.environment", out object? environment).Should().BeTrue();
environment.Should().Be("test-environment");
Expand Down
52 changes: 52 additions & 0 deletions test/Sentry.Tests/SentryLogTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,58 @@ public void WriteTo_Attributes_AsJson()
entry => entry.Message.Should().Match("*null*is not supported*ignored*")
);
}

[Fact]
public void GetTraceIdAndSpanId_WithActiveSpan_HasBothTraceIdAndSpanId()
{
// Arrange
var span = Substitute.For<ISpan>();
span.TraceId.Returns(SentryId.Create());
span.SpanId.Returns(SpanId.Create());

var hub = Substitute.For<IHub>();
hub.GetSpan().Returns(span);

// Act
SentryLog.GetTraceIdAndSpanId(hub, out var traceId, out var spanId);

// Assert
traceId.Should().Be(span.TraceId);
spanId.Should().Be(span.SpanId);
}

[Fact]
public void GetTraceIdAndSpanId_WithoutActiveSpan_HasOnlyTraceIdButNoSpanId()
{
// Arrange
var hub = Substitute.For<IHub>();
hub.GetSpan().Returns((ISpan)null);

var scope = new Scope();
hub.SubstituteConfigureScope(scope);

// Act
SentryLog.GetTraceIdAndSpanId(hub, out var traceId, out var spanId);

// Assert
traceId.Should().Be(scope.PropagationContext.TraceId);
spanId.Should().BeNull();
}

[Fact]
public void GetTraceIdAndSpanId_WithoutIds_ShouldBeUnreachable()
{
// Arrange
var hub = Substitute.For<IHub>();
hub.GetSpan().Returns((ISpan)null);

// Act
SentryLog.GetTraceIdAndSpanId(hub, out var traceId, out var spanId);

// Assert
traceId.Should().Be(SentryId.Empty);
spanId.Should().BeNull();
}
}

file static class AssertExtensions
Expand Down
21 changes: 13 additions & 8 deletions test/Sentry.Tests/SentryStructuredLoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ public Fixture()

Hub.IsEnabled.Returns(true);

var traceHeader = new SentryTraceHeader(TraceId, ParentSpanId.Value, null);
Hub.GetTraceHeader().Returns(traceHeader);
var span = Substitute.For<ISpan>();
span.TraceId.Returns(TraceId);
span.SpanId.Returns(ParentSpanId.Value);
Hub.GetSpan().Returns(span);

ExpectedAttributes = new Dictionary<string, string>(1)
{
Expand All @@ -46,11 +48,14 @@ public Fixture()

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

public void WithoutTraceHeader()
public void WithoutActiveSpan()
{
Hub.GetTraceHeader().Returns((SentryTraceHeader?)null);
TraceId = SentryId.Empty;
ParentSpanId = SpanId.Empty;
Hub.GetSpan().Returns((ISpan?)null);

var scope = new Scope();
Hub.SubstituteConfigureScope(scope);
TraceId = scope.PropagationContext.TraceId;
ParentSpanId = null;
}

public SentryStructuredLogger GetSut() => SentryStructuredLogger.Create(Hub, Options, Clock, BatchSize, BatchTimeout);
Expand Down Expand Up @@ -93,9 +98,9 @@ public void Create_Disabled_CachedDisabledInstance()
}

[Fact]
public void Log_WithoutTraceHeader_CapturesEnvelope()
public void Log_WithoutActiveSpan_CapturesEnvelope()
{
_fixture.WithoutTraceHeader();
_fixture.WithoutActiveSpan();
_fixture.Options.Experimental.EnableLogs = true;
var logger = _fixture.GetSut();

Expand Down
Loading