From 9e7c42325254da768ed288a7be07efb3a27c09d5 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Wed, 5 Mar 2025 17:26:00 +0100 Subject: [PATCH 1/8] observe trace --- src/Sentry/IScopeObserver.cs | 5 +++++ src/Sentry/Internal/Hub.cs | 2 +- src/Sentry/Internal/ScopeObserver.cs | 5 +++++ src/Sentry/Scope.cs | 11 ++++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Sentry/IScopeObserver.cs b/src/Sentry/IScopeObserver.cs index 3757122d83..78dcfe3229 100644 --- a/src/Sentry/IScopeObserver.cs +++ b/src/Sentry/IScopeObserver.cs @@ -29,4 +29,9 @@ public interface IScopeObserver /// Sets the user information. /// void SetUser(SentryUser? user); + + /// + /// Sets the current trace + /// + void SetTrace(SentryId traceId, SpanId parentSpanId); } diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index cd00686250..e7234b6356 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -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, diff --git a/src/Sentry/Internal/ScopeObserver.cs b/src/Sentry/Internal/ScopeObserver.cs index 8e99626e4e..5036d99776 100644 --- a/src/Sentry/Internal/ScopeObserver.cs +++ b/src/Sentry/Internal/ScopeObserver.cs @@ -81,6 +81,11 @@ public void SetUser(SentryUser? user) } } + public void SetTrace(SentryId traceId, SpanId parentSpanId) + { + throw new NotImplementedException(); + } + public abstract void SetUserImpl(SentryUser user); public abstract void UnsetUserImpl(); diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 9cd253b216..7e87eb6007 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -233,7 +233,7 @@ public ITransactionTracer? Transaction } } - internal SentryPropagationContext PropagationContext { get; set; } + internal SentryPropagationContext PropagationContext { get; private set; } internal SessionUpdate? SessionUpdate { get; set; } @@ -376,6 +376,15 @@ public void UnsetTag(string key) /// 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); + } + } + /// /// Resets all the properties and collections within the scope to their default values. /// From 65facbeeb16dcd0b329de269d9c191a334164728 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 28 Mar 2025 10:15:41 +0100 Subject: [PATCH 2/8] merge error --- src/Sentry/IScopeObserver.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Sentry/IScopeObserver.cs b/src/Sentry/IScopeObserver.cs index 10f6fd9b62..a61c787c6c 100644 --- a/src/Sentry/IScopeObserver.cs +++ b/src/Sentry/IScopeObserver.cs @@ -28,8 +28,6 @@ public interface IScopeObserver /// /// Sets the user information. /// - void SetUser(SentryUser? user); - public void SetUser(SentryUser? user); /// From 5fa7b5bd1847e76a8dc37710ddb64bc4c911e6c4 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 28 Mar 2025 09:32:35 +0000 Subject: [PATCH 3/8] Format code --- src/Sentry/IScopeObserver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry/IScopeObserver.cs b/src/Sentry/IScopeObserver.cs index a61c787c6c..2e8c8ec6ab 100644 --- a/src/Sentry/IScopeObserver.cs +++ b/src/Sentry/IScopeObserver.cs @@ -29,9 +29,9 @@ public interface IScopeObserver /// Sets the user information. /// public void SetUser(SentryUser? user); - + /// /// Sets the current trace /// - void SetTrace(SentryId traceId, SpanId parentSpanId); + public void SetTrace(SentryId traceId, SpanId parentSpanId); } From 13351a48f51032c4738d58a50884cc09a80d7021 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 28 Mar 2025 15:57:14 +0100 Subject: [PATCH 4/8] finalized observer --- src/Sentry/IScopeObserver.cs | 2 +- src/Sentry/Internal/ScopeObserver.cs | 13 +++++++--- src/Sentry/InternalSentrySdk.cs | 14 +++++++++++ .../Platforms/Android/AndroidScopeObserver.cs | 5 ++++ .../Platforms/Cocoa/CocoaScopeObserver.cs | 5 ++++ src/Sentry/Platforms/Native/CFunctions.cs | 3 +++ .../Platforms/Native/NativeScopeObserver.cs | 3 +++ test/Sentry.Tests/HubTests.cs | 8 +++--- test/Sentry.Tests/ScopeTests.cs | 25 +++++++++++++++++++ 9 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/Sentry/InternalSentrySdk.cs diff --git a/src/Sentry/IScopeObserver.cs b/src/Sentry/IScopeObserver.cs index a61c787c6c..c31493e16c 100644 --- a/src/Sentry/IScopeObserver.cs +++ b/src/Sentry/IScopeObserver.cs @@ -29,7 +29,7 @@ public interface IScopeObserver /// Sets the user information. /// public void SetUser(SentryUser? user); - + /// /// Sets the current trace /// diff --git a/src/Sentry/Internal/ScopeObserver.cs b/src/Sentry/Internal/ScopeObserver.cs index 5036d99776..9b78030baf 100644 --- a/src/Sentry/Internal/ScopeObserver.cs +++ b/src/Sentry/Internal/ScopeObserver.cs @@ -81,12 +81,17 @@ public void SetUser(SentryUser? user) } } + public abstract void SetUserImpl(SentryUser user); + + public abstract void UnsetUserImpl(); + public void SetTrace(SentryId traceId, SpanId parentSpanId) { - throw new NotImplementedException(); + _options.DiagnosticLogger?.Log( + SentryLevel.Debug,"{0} Scope Sync - Setting Trace traceId:{1} parentSpanId:{2}", null, + _name, traceId, parentSpanId); + SetTraceImpl(traceId, parentSpanId); } - public abstract void SetUserImpl(SentryUser user); - - public abstract void UnsetUserImpl(); + public abstract void SetTraceImpl(SentryId traceId, SpanId parentSpanId); } diff --git a/src/Sentry/InternalSentrySdk.cs b/src/Sentry/InternalSentrySdk.cs new file mode 100644 index 0000000000..b495c835d7 --- /dev/null +++ b/src/Sentry/InternalSentrySdk.cs @@ -0,0 +1,14 @@ +namespace Sentry; + +/// +/// Sentry SDK internal API methods meant for being used by the Sentry Unity SDK +/// +public static class InternalSentrySdk +{ + /// + /// Allows to set the trace + /// + public static void SetTrace(SentryId traceId, SpanId parentSpanId) => + SentrySdk.CurrentHub.ConfigureScope(scope => + scope.SetPropagationContext(new SentryPropagationContext(traceId, parentSpanId))); +} diff --git a/src/Sentry/Platforms/Android/AndroidScopeObserver.cs b/src/Sentry/Platforms/Android/AndroidScopeObserver.cs index dcedfa611e..c2a272061f 100644 --- a/src/Sentry/Platforms/Android/AndroidScopeObserver.cs +++ b/src/Sentry/Platforms/Android/AndroidScopeObserver.cs @@ -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 + } } diff --git a/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs b/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs index f36271f320..d4e7def7a8 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs @@ -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 + } } diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 05be757fa9..0b5465bd6b 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -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 LoadDebugImages(IDiagnosticLogger? logger) { // It only makes sense to load them once because they're cached on the native side anyway. We could force diff --git a/src/Sentry/Platforms/Native/NativeScopeObserver.cs b/src/Sentry/Platforms/Native/NativeScopeObserver.cs index 68b8bc6e57..b278bf1e83 100644 --- a/src/Sentry/Platforms/Native/NativeScopeObserver.cs +++ b/src/Sentry/Platforms/Native/NativeScopeObserver.cs @@ -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 diff --git a/test/Sentry.Tests/HubTests.cs b/test/Sentry.Tests/HubTests.cs index d22326b119..f0c141617e 100644 --- a/test/Sentry.Tests/HubTests.cs +++ b/test/Sentry.Tests/HubTests.cs @@ -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(); @@ -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(); @@ -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); @@ -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"; diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index ece48d5170..8040666b10 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -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(); + 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 From 2df0cd981d715efc48acd5bae1b74dba201713a4 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 31 Mar 2025 14:27:06 +0200 Subject: [PATCH 5/8] alternative to internalsentrysdk --- src/Sentry/InternalSentrySdk.cs | 14 -------------- src/Sentry/Sentry.csproj | 6 ++++++ src/Sentry/SentrySdk.cs | 7 +++++++ 3 files changed, 13 insertions(+), 14 deletions(-) delete mode 100644 src/Sentry/InternalSentrySdk.cs diff --git a/src/Sentry/InternalSentrySdk.cs b/src/Sentry/InternalSentrySdk.cs deleted file mode 100644 index b495c835d7..0000000000 --- a/src/Sentry/InternalSentrySdk.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Sentry; - -/// -/// Sentry SDK internal API methods meant for being used by the Sentry Unity SDK -/// -public static class InternalSentrySdk -{ - /// - /// Allows to set the trace - /// - public static void SetTrace(SentryId traceId, SpanId parentSpanId) => - SentrySdk.CurrentHub.ConfigureScope(scope => - scope.SetPropagationContext(new SentryPropagationContext(traceId, parentSpanId))); -} diff --git a/src/Sentry/Sentry.csproj b/src/Sentry/Sentry.csproj index f7eb60f38a..49632a39aa 100644 --- a/src/Sentry/Sentry.csproj +++ b/src/Sentry/Sentry.csproj @@ -16,8 +16,14 @@ netstandard2.0;netstandard2.1 + + false + + + + diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 401a0fa6f0..8c5efbd967 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -209,6 +209,13 @@ internal static IDisposable UseHub(IHub hub) return new DisposeHandle(hub); } + /// + /// Allows to set the trace + /// + internal static void SetTrace(SentryId traceId, SpanId parentSpanId) => + CurrentHub.ConfigureScope(scope => + scope.SetPropagationContext(new SentryPropagationContext(traceId, parentSpanId))); + /// /// Flushes the queue of captured events until the timeout set in /// is reached. From 8daf952d1f301de993d9d98f77de4a79ac2ece2f Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 31 Mar 2025 14:39:03 +0200 Subject: [PATCH 6/8] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed7eb87c3..dba8a54740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 From 172c6755ebdd9824fd311c053da65c5fa1faf1f5 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Mon, 31 Mar 2025 12:41:19 +0000 Subject: [PATCH 7/8] Format code --- src/Sentry/Internal/ScopeObserver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Sentry/Internal/ScopeObserver.cs b/src/Sentry/Internal/ScopeObserver.cs index 9b78030baf..feb1411747 100644 --- a/src/Sentry/Internal/ScopeObserver.cs +++ b/src/Sentry/Internal/ScopeObserver.cs @@ -88,7 +88,7 @@ public void SetUser(SentryUser? user) public void SetTrace(SentryId traceId, SpanId parentSpanId) { _options.DiagnosticLogger?.Log( - SentryLevel.Debug,"{0} Scope Sync - Setting Trace traceId:{1} parentSpanId:{2}", null, + SentryLevel.Debug, "{0} Scope Sync - Setting Trace traceId:{1} parentSpanId:{2}", null, _name, traceId, parentSpanId); SetTraceImpl(traceId, parentSpanId); } From f3e40827732d436de889da6fbfc4c04b2e243648 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Tue, 1 Apr 2025 17:06:41 +0200 Subject: [PATCH 8/8] verify --- test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt | 1 + test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt | 1 + test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 67795954b7..93d72d716c 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -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); } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 67795954b7..93d72d716c 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -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); } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 0c06282d34..75fa5ad89d 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -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); }