From d8623c20549a25ef314c25b5834c50ecb0aa12e0 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 8 May 2026 12:15:11 +0200 Subject: [PATCH 1/2] sync default tags during init --- src/Sentry/Platforms/Android/SentrySdk.cs | 4 +- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 4 +- src/Sentry/SentrySdk.cs | 15 ++++++++ test/Sentry.Tests/SentrySdkTests.cs | 46 +++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index d47fe75664..f660e1269b 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -95,8 +95,8 @@ private static void InitSentryAndroidSdk(SentryOptions options) o.CacheDirPath = Path.Combine(cacheDirectoryPath, "android"); } - // NOTE: Tags in options.DefaultTags should not be passed down, because we already call SetTag on each - // one when sending events, which is relayed through the scope observer. + // NOTE: options.DefaultTags are pushed to the scope after the hub initializes (see SentrySdk.InitHub), + // which relays them to the Android SDK via the scope observer so they're attached to native crashes. if (options.HttpProxy is System.Net.WebProxy proxy) { diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index cefea9c50c..9d96a8cf0b 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -50,8 +50,8 @@ private static void InitSentryCocoaSdk(SentryOptions options) // NOTE: options.CacheDirectoryPath - No option for this in Sentry Cocoa, but caching is still enabled // https://github.com/getsentry/sentry-cocoa/issues/1051 - // NOTE: Tags in options.DefaultTags should not be passed down, because we already call SetTag on each - // one when sending events, which is relayed through the scope observer. + // NOTE: options.DefaultTags are pushed to the scope after the hub initializes (see SentrySdk.InitHub), + // which relays them to the Cocoa SDK via the scope observer so they're attached to native crashes. if (options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index fd7a6bd90f..0af778eaf2 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -91,6 +91,21 @@ internal static IHub InitHub(SentryOptions options) } options.PostInitCallbacks.Clear(); + // Default tags are applied per-event by the Enricher to events going through the .NET pipeline, + // but native crashes are captured and uploaded by the native SDK without going through that pipeline. + // Push them to the scope so the scope observer relays them to the native layer and they're attached + // to native crash reports. + if (options is { EnableScopeSync: true, ScopeObserver: not null } && options.DefaultTags.Count > 0) + { + hub.ConfigureScope(static (scope, opts) => + { + foreach (var tag in opts.DefaultTags) + { + scope.SetTag(tag.Key, tag.Value); + } + }, options); + } + // Platform specific check for profiler misconfiguration. #if __IOS__ // No user-facing warning necessary - the integration is part of InitSentryCocoaSdk(). diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 9c5fe1eca0..c13703d400 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -967,6 +967,52 @@ public void InitHub_GlobalModeOff_NoWarningOrErrorLogged() } #endif + [Fact] + public void InitHub_DefaultTagsWithScopeSync_RelayedToScopeObserver() + { + // Arrange + var observer = Substitute.For(); + var options = new SentryOptions + { + Dsn = ValidDsn, + ScopeObserver = observer, + EnableScopeSync = true, + BackgroundWorker = Substitute.For(), + InitNativeSdks = false, + }; + options.DefaultTags["env"] = "production"; + options.DefaultTags["region"] = "us-east-1"; + + // Act + SentrySdk.InitHub(options); + + // Assert + observer.Received(1).SetTag("env", "production"); + observer.Received(1).SetTag("region", "us-east-1"); + } + + [Fact] + public void InitHub_DefaultTagsWithoutScopeSync_NotRelayedToScopeObserver() + { + // Arrange + var observer = Substitute.For(); + var options = new SentryOptions + { + Dsn = ValidDsn, + ScopeObserver = observer, + EnableScopeSync = false, + BackgroundWorker = Substitute.For(), + InitNativeSdks = false, + }; + options.DefaultTags["env"] = "production"; + + // Act + SentrySdk.InitHub(options); + + // Assert + observer.DidNotReceive().SetTag(Arg.Any(), Arg.Any()); + } + [Fact] public void InitHub_DebugEnabled_DebugLogsLogged() { From 04bf99243c8b03ee6f5554dc21d920fcfc7d4c61 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 8 May 2026 12:38:04 +0200 Subject: [PATCH 2/2] invoke scope observer instead --- src/Sentry/Platforms/Android/SentrySdk.cs | 5 +++-- src/Sentry/Platforms/Cocoa/SentrySdk.cs | 5 +++-- src/Sentry/SentrySdk.cs | 15 ++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index f660e1269b..745371796b 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -95,8 +95,9 @@ private static void InitSentryAndroidSdk(SentryOptions options) o.CacheDirPath = Path.Combine(cacheDirectoryPath, "android"); } - // NOTE: options.DefaultTags are pushed to the scope after the hub initializes (see SentrySdk.InitHub), - // which relays them to the Android SDK via the scope observer so they're attached to native crashes. + // NOTE: options.DefaultTags are forwarded to the scope observer in SentrySdk.InitHub so the + // Android SDK attaches them to native crashes. The Enricher continues to apply them to + // managed events at send time. if (options.HttpProxy is System.Net.WebProxy proxy) { diff --git a/src/Sentry/Platforms/Cocoa/SentrySdk.cs b/src/Sentry/Platforms/Cocoa/SentrySdk.cs index 9d96a8cf0b..9fd3f62ef5 100644 --- a/src/Sentry/Platforms/Cocoa/SentrySdk.cs +++ b/src/Sentry/Platforms/Cocoa/SentrySdk.cs @@ -50,8 +50,9 @@ private static void InitSentryCocoaSdk(SentryOptions options) // NOTE: options.CacheDirectoryPath - No option for this in Sentry Cocoa, but caching is still enabled // https://github.com/getsentry/sentry-cocoa/issues/1051 - // NOTE: options.DefaultTags are pushed to the scope after the hub initializes (see SentrySdk.InitHub), - // which relays them to the Cocoa SDK via the scope observer so they're attached to native crashes. + // NOTE: options.DefaultTags are forwarded to the scope observer in SentrySdk.InitHub so the + // Cocoa SDK attaches them to native crashes. The Enricher continues to apply them to + // managed events at send time. if (options.BeforeBreadcrumbInternal is { } beforeBreadcrumb) { diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index 0af778eaf2..a6e31c57af 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -93,17 +93,14 @@ internal static IHub InitHub(SentryOptions options) // Default tags are applied per-event by the Enricher to events going through the .NET pipeline, // but native crashes are captured and uploaded by the native SDK without going through that pipeline. - // Push them to the scope so the scope observer relays them to the native layer and they're attached - // to native crash reports. - if (options is { EnableScopeSync: true, ScopeObserver: not null } && options.DefaultTags.Count > 0) + // Forward them to the scope observer so the native layer attaches them to crash reports. + // Bypassing the .NET scope keeps scope.Tags identical between native and non-native apps. + if (options is { EnableScopeSync: true, ScopeObserver: { } observer } && options.DefaultTags.Count > 0) { - hub.ConfigureScope(static (scope, opts) => + foreach (var tag in options.DefaultTags) { - foreach (var tag in opts.DefaultTags) - { - scope.SetTag(tag.Key, tag.Value); - } - }, options); + observer.SetTag(tag.Key, tag.Value); + } } // Platform specific check for profiler misconfiguration.