Skip to content

feat: Allow IScopeObserver to observe trace #4026

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 2, 2025
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 @@

### Features

- The `IScopeObserver` now has an `SetTrace` that allows observing changes to the scope's trace context. The SDK uses this to propagate the `trace ID` to `sentry-native`. This allows Sentry to connect errors coming from all layers of your application ([#4026](https://github.com/getsentry/sentry-dotnet/pull/4026))
- Exception.HResult is now included in the mechanism data for all exceptions ([#4029](https://github.com/getsentry/sentry-dotnet/pull/4029))

### Dependencies
Expand Down
5 changes: 5 additions & 0 deletions src/Sentry/IScopeObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ public interface IScopeObserver
/// Sets the user information.
/// </summary>
public void SetUser(SentryUser? user);

/// <summary>
/// Sets the current trace
/// </summary>
public void SetTrace(SentryId traceId, SpanId parentSpanId);
}
2 changes: 1 addition & 1 deletion src/Sentry/Internal/Hub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ public TransactionContext ContinueTrace(
string? operation = null)
{
var propagationContext = SentryPropagationContext.CreateFromHeaders(_options.DiagnosticLogger, traceHeader, baggageHeader);
ConfigureScope(scope => scope.PropagationContext = propagationContext);
ConfigureScope(scope => scope.SetPropagationContext(propagationContext));

return new TransactionContext(
name: name ?? string.Empty,
Expand Down
10 changes: 10 additions & 0 deletions src/Sentry/Internal/ScopeObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,14 @@ public void SetUser(SentryUser? user)
public abstract void SetUserImpl(SentryUser user);

public abstract void UnsetUserImpl();

public void SetTrace(SentryId traceId, SpanId parentSpanId)
{
_options.DiagnosticLogger?.Log(
SentryLevel.Debug, "{0} Scope Sync - Setting Trace traceId:{1} parentSpanId:{2}", null,
_name, traceId, parentSpanId);
SetTraceImpl(traceId, parentSpanId);
}

public abstract void SetTraceImpl(SentryId traceId, SpanId parentSpanId);
}
5 changes: 5 additions & 0 deletions src/Sentry/Platforms/Android/AndroidScopeObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,9 @@ public void SetUser(SentryUser? user)
_innerObserver?.SetUser(user);
}
}

public void SetTrace(SentryId traceId, SpanId parentSpanId)
{
// TODO: This requires sentry-java 8.4.0
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to follow up later with this once this is added to the Java and Apple SDKs?
If so ideally we create a ticket so folks here can add it later

Copy link
Contributor Author

@bitsandfoxes bitsandfoxes Apr 7, 2025

Choose a reason for hiding this comment

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

Tracking in #4074

}
}
5 changes: 5 additions & 0 deletions src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,9 @@ public void SetUser(SentryUser? user)
_innerObserver?.SetUser(user);
}
}

public void SetTrace(SentryId traceId, SpanId parentSpanId)
{
// TODO: Missing corresponding functionality on the Cocoa SDK
}
}
3 changes: 3 additions & 0 deletions src/Sentry/Platforms/Native/CFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ internal static string GetCacheDirectory(SentryOptions options)
[DllImport("sentry-native")]
internal static extern void sentry_remove_extra(string key);

[DllImport("sentry-native")]
internal static extern void sentry_set_trace(string traceId, string parentSpanId);

internal static Dictionary<long, DebugImage> LoadDebugImages(IDiagnosticLogger? logger)
{
// It only makes sense to load them once because they're cached on the native side anyway. We could force
Expand Down
3 changes: 3 additions & 0 deletions src/Sentry/Platforms/Native/NativeScopeObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public override void SetUserImpl(SentryUser user)

public override void UnsetUserImpl() => C.sentry_remove_user();

public override void SetTraceImpl(SentryId traceId, SpanId parentSpanId) =>
C.sentry_set_trace(traceId.ToString(), parentSpanId.ToString());

private static string GetTimestamp(DateTimeOffset timestamp) =>
// "o": Using ISO 8601 to make sure the timestamp makes it to the bridge correctly.
// https://docs.microsoft.com/en-gb/dotnet/standard/base-types/standard-date-and-time-format-strings#Roundtrip
Expand Down
11 changes: 10 additions & 1 deletion src/Sentry/Scope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public ITransactionTracer? Transaction
}
}

internal SentryPropagationContext PropagationContext { get; set; }
internal SentryPropagationContext PropagationContext { get; private set; }

internal SessionUpdate? SessionUpdate { get; set; }

Expand Down Expand Up @@ -376,6 +376,15 @@ public void UnsetTag(string key)
/// </summary>
public void AddAttachment(SentryAttachment attachment) => _attachments.Add(attachment);

internal void SetPropagationContext(SentryPropagationContext propagationContext)
{
PropagationContext = propagationContext;
if (Options.EnableScopeSync)
{
Options.ScopeObserver?.SetTrace(propagationContext.TraceId, propagationContext.SpanId);
}
}

/// <summary>
/// Resets all the properties and collections within the scope to their default values.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Sentry/Sentry.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

<PropertyGroup Condition="'$(SolutionName)' == 'Sentry.Unity'">
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<!-- To be able to make the internals visible, since we're not signing the assembly for Unity -->
<SignAssembly>false</SignAssembly>
</PropertyGroup>

<ItemGroup Condition="'$(SolutionName)' == 'Sentry.Unity'">
<InternalsVisibleTo Include="Sentry.Unity" />
</ItemGroup>

<!-- Platform-specific props included here -->
<Import Project="Platforms\Android\Sentry.Android.props" Condition="'$(TargetPlatformIdentifier)' == 'android'" />
<Import Project="Platforms\Cocoa\Sentry.Cocoa.props" Condition="'$(TargetPlatformIdentifier)' == 'ios' Or '$(TargetPlatformIdentifier)' == 'maccatalyst'" />
Expand Down
7 changes: 7 additions & 0 deletions src/Sentry/SentrySdk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ internal static IDisposable UseHub(IHub hub)
return new DisposeHandle(hub);
}

/// <summary>
/// Allows to set the trace
/// </summary>
internal static void SetTrace(SentryId traceId, SpanId parentSpanId) =>
CurrentHub.ConfigureScope(scope =>
scope.SetPropagationContext(new SentryPropagationContext(traceId, parentSpanId)));

/// <summary>
/// Flushes the queue of captured events until the timeout set in <see cref="SentryOptions.FlushTimeout"/>
/// is reached.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ namespace Sentry
void AddBreadcrumb(Sentry.Breadcrumb breadcrumb);
void SetExtra(string key, object? value);
void SetTag(string key, string value);
void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId);
void SetUser(Sentry.SentryUser? user);
void UnsetTag(string key);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ namespace Sentry
void AddBreadcrumb(Sentry.Breadcrumb breadcrumb);
void SetExtra(string key, object? value);
void SetTag(string key, string value);
void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId);
void SetUser(Sentry.SentryUser? user);
void UnsetTag(string key);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ namespace Sentry
void AddBreadcrumb(Sentry.Breadcrumb breadcrumb);
void SetExtra(string key, object? value);
void SetTag(string key, string value);
void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId);
void SetUser(Sentry.SentryUser? user);
void UnsetTag(string key);
}
Expand Down
8 changes: 4 additions & 4 deletions test/Sentry.Tests/HubTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ public void GetTraceHeader_NoSpanActive_ReturnsHeaderFromPropagationContext()
var propagationContext = new SentryPropagationContext(
SentryId.Parse("75302ac48a024bde9a3b3734a82e36c8"),
SpanId.Parse("2000000000000000"));
hub.ConfigureScope(scope => scope.PropagationContext = propagationContext);
hub.ConfigureScope(scope => scope.SetPropagationContext(propagationContext));

// Act
var header = hub.GetTraceHeader();
Expand Down Expand Up @@ -1052,7 +1052,7 @@ public void GetBaggage_NoSpanActive_ReturnsBaggageFromPropagationContext()
var hub = _fixture.GetSut();
var propagationContext = new SentryPropagationContext(
SentryId.Parse("43365712692146d08ee11a729dfbcaca"), SpanId.Parse("1000000000000000"));
hub.ConfigureScope(scope => scope.PropagationContext = propagationContext);
hub.ConfigureScope(scope => scope.SetPropagationContext(propagationContext));

// Act
var baggage = hub.GetBaggage();
Expand All @@ -1069,7 +1069,7 @@ public void ContinueTrace_SetsPropagationContextAndReturnsTransactionContext()
var hub = _fixture.GetSut();
var propagationContext = new SentryPropagationContext(
SentryId.Parse("43365712692146d08ee11a729dfbcaca"), SpanId.Parse("1000000000000000"));
hub.ConfigureScope(scope => scope.PropagationContext = propagationContext);
hub.ConfigureScope(scope => scope.SetPropagationContext(propagationContext));

var traceHeader = new SentryTraceHeader(SentryId.Parse("5bd5f6d346b442dd9177dce9302fd737"),
SpanId.Parse("2000000000000000"), null);
Expand Down Expand Up @@ -1104,7 +1104,7 @@ public void ContinueTrace_ReceivesHeadersAsStrings_SetsPropagationContextAndRetu
var hub = _fixture.GetSut();
var propagationContext = new SentryPropagationContext(
SentryId.Parse("43365712692146d08ee11a729dfbcaca"), SpanId.Parse("1000000000000000"));
hub.ConfigureScope(scope => scope.PropagationContext = propagationContext);
hub.ConfigureScope(scope => scope.SetPropagationContext(propagationContext));
var traceHeader = "5bd5f6d346b442dd9177dce9302fd737-2000000000000000";
var baggageHeader = "sentry-trace_id=5bd5f6d346b442dd9177dce9302fd737, sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=1.0";

Expand Down
25 changes: 25 additions & 0 deletions test/Sentry.Tests/ScopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,31 @@ public void SetTag_NullValue_DoesNotThrowArgumentNullException()

Assert.Null(exception);
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void SetPropagationContext_ObserverExist_ObserverSetsTraceIfEnabled(bool enableScopeSync)
{
// Arrange
var observer = Substitute.For<IScopeObserver>();
var scope = new Scope(new SentryOptions
{
ScopeObserver = observer,
EnableScopeSync = enableScopeSync
});
var propagationContext = new SentryPropagationContext();
var expectedTraceId = propagationContext.TraceId;
var expectedSpanId = propagationContext.SpanId;
var expectedCount = enableScopeSync ? 1 : 0;

// Act
scope.SetPropagationContext(propagationContext);

// Assert
scope.PropagationContext.Should().Be(propagationContext);
observer.Received(expectedCount).SetTrace(Arg.Is(expectedTraceId), Arg.Is(expectedSpanId));
}
}

public static class ScopeTestExtensions
Expand Down
Loading