From cc9de07cc1f9169f48cc2e507c0ec3468cb4d8bb Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Thu, 9 Jul 2026 15:53:45 +0200 Subject: [PATCH 01/10] sync the environment --- src/Sentry/IScopeObserver.cs | 5 ++ src/Sentry/Internal/ScopeObserver.cs | 9 +++ .../Platforms/Android/AndroidScopeObserver.cs | 12 ++++ .../Platforms/Cocoa/CocoaScopeObserver.cs | 12 ++++ src/Sentry/Platforms/Native/CFunctions.cs | 3 + .../Platforms/Native/NativeScopeObserver.cs | 3 + src/Sentry/Scope.cs | 19 +++++- test/Sentry.Tests/ScopeTests.cs | 63 +++++++++++++++++++ 8 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/Sentry/IScopeObserver.cs b/src/Sentry/IScopeObserver.cs index 870a160a30..3d9dcdce90 100644 --- a/src/Sentry/IScopeObserver.cs +++ b/src/Sentry/IScopeObserver.cs @@ -35,6 +35,11 @@ public interface IScopeObserver /// public void SetTrace(SentryId traceId, SpanId parentSpanId); + /// + /// Sets the environment. + /// + public void SetEnvironment(string? environment); + /// /// Adds an attachment. /// diff --git a/src/Sentry/Internal/ScopeObserver.cs b/src/Sentry/Internal/ScopeObserver.cs index 28f4676f84..f316beabf4 100644 --- a/src/Sentry/Internal/ScopeObserver.cs +++ b/src/Sentry/Internal/ScopeObserver.cs @@ -95,6 +95,15 @@ public void SetTrace(SentryId traceId, SpanId parentSpanId) public abstract void SetTraceImpl(SentryId traceId, SpanId parentSpanId); + public void SetEnvironment(string? environment) + { + _options.DiagnosticLogger?.Log(SentryLevel.Debug, + "{0} Scope Sync - Setting Environment e:\"{1}\"", null, _name, environment); + SetEnvironmentImpl(environment); + } + + public abstract void SetEnvironmentImpl(string? environment); + public void AddAttachment(SentryAttachment attachment) { _options.DiagnosticLogger?.Log(SentryLevel.Debug, diff --git a/src/Sentry/Platforms/Android/AndroidScopeObserver.cs b/src/Sentry/Platforms/Android/AndroidScopeObserver.cs index e18b215562..c4d3e91640 100644 --- a/src/Sentry/Platforms/Android/AndroidScopeObserver.cs +++ b/src/Sentry/Platforms/Android/AndroidScopeObserver.cs @@ -112,6 +112,18 @@ public void SetTrace(SentryId traceId, SpanId parentSpanId) } } + public void SetEnvironment(string? environment) + { + try + { + // TODO: Missing corresponding scope-level functionality on the Android SDK + } + finally + { + _innerObserver?.SetEnvironment(environment); + } + } + public void AddAttachment(SentryAttachment attachment) { try diff --git a/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs b/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs index f92156a4be..46f8aebf5e 100644 --- a/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs +++ b/src/Sentry/Platforms/Cocoa/CocoaScopeObserver.cs @@ -120,6 +120,18 @@ public void SetTrace(SentryId traceId, SpanId parentSpanId) } } + public void SetEnvironment(string? environment) + { + try + { + SentryCocoaSdk.ConfigureScope(scope => scope.SetEnvironment(environment)); + } + finally + { + _innerObserver?.SetEnvironment(environment); + } + } + public void AddAttachment(SentryAttachment attachment) { try diff --git a/src/Sentry/Platforms/Native/CFunctions.cs b/src/Sentry/Platforms/Native/CFunctions.cs index 5faa149720..72105155e8 100644 --- a/src/Sentry/Platforms/Native/CFunctions.cs +++ b/src/Sentry/Platforms/Native/CFunctions.cs @@ -251,6 +251,9 @@ internal static string GetCacheDirectory(SentryOptions options) [DllImport("sentry-native")] internal static extern void sentry_set_trace(string traceId, string parentSpanId); + [DllImport("sentry-native")] + internal static extern void sentry_set_environment(string? environment); + 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 f9034df998..1d6956a9f4 100644 --- a/src/Sentry/Platforms/Native/NativeScopeObserver.cs +++ b/src/Sentry/Platforms/Native/NativeScopeObserver.cs @@ -43,6 +43,9 @@ public override void SetUserImpl(SentryUser user) public override void SetTraceImpl(SentryId traceId, SpanId parentSpanId) => C.sentry_set_trace(traceId.ToString(), parentSpanId.ToString()); + public override void SetEnvironmentImpl(string? environment) => + C.sentry_set_environment(environment); + public override void AddAttachmentImpl(SentryAttachment attachment) { // TODO: Missing corresponding functionality on the Native SDK diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 62b926cb7f..601cee2c1a 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -144,8 +144,25 @@ public SentryUser User /// public string? Distribution { get; set; } + private string? _environment; + /// - public string? Environment { get; set; } + public string? Environment + { + get => _environment; + set + { + if (_environment != value) + { + _environment = value; + if (Options.EnableScopeSync && + Options.ScopeObserver is { } observer) + { + observer.SetEnvironment(value); + } + } + } + } // TransactionName is kept for legacy purposes because // SentryEvent still makes use of it. diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index ccb1b277bf..9a02dd1a20 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -696,6 +696,69 @@ public void AddBreadcrumb_ObserverExist_ObserverAddsBreadcrumbIfEnabled(bool obs observer.Received(expectedCount).AddBreadcrumb(Arg.Is(breadcrumb)); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetEnvironment_ObserverExist_ObserverSetsEnvironmentIfEnabled(bool observerEnable) + { + // Arrange + var observer = Substitute.For(); + var scope = new Scope(new SentryOptions + { + ScopeObserver = observer, + EnableScopeSync = observerEnable + }); + var expectedEnvironment = "staging"; + var expectedCount = observerEnable ? 1 : 0; + + // Act + scope.Environment = expectedEnvironment; + + // Assert + observer.Received(expectedCount).SetEnvironment(Arg.Is(expectedEnvironment)); + } + + [Fact] + public void SetEnvironment_Null_ObserverClearsEnvironment() + { + // Arrange + var observer = Substitute.For(); + var scope = new Scope(new SentryOptions + { + ScopeObserver = observer, + EnableScopeSync = true + }) + { + Environment = "staging" + }; + observer.ClearReceivedCalls(); + + // Act + scope.Environment = null; + + // Assert + observer.Received(1).SetEnvironment(Arg.Is((string?)null)); + } + + [Fact] + public void SetEnvironment_SameValue_ObserverNotifiedOnce() + { + // Arrange + var observer = Substitute.For(); + var scope = new Scope(new SentryOptions + { + ScopeObserver = observer, + EnableScopeSync = true + }); + + // Act + scope.Environment = "staging"; + scope.Environment = "staging"; + + // Assert + observer.Received(1).SetEnvironment(Arg.Is("staging")); + } + [Fact] public void Filtered_tags_are_not_set() { From 925a20a9617ade7a8a02af6589aec5b2ecb6364b Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 9 Jul 2026 14:12:47 +0000 Subject: [PATCH 02/10] 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 f316beabf4..0ba44c98a5 100644 --- a/src/Sentry/Internal/ScopeObserver.cs +++ b/src/Sentry/Internal/ScopeObserver.cs @@ -97,7 +97,7 @@ public void SetTrace(SentryId traceId, SpanId parentSpanId) public void SetEnvironment(string? environment) { - _options.DiagnosticLogger?.Log(SentryLevel.Debug, + _options.DiagnosticLogger?.Log(SentryLevel.Debug, "{0} Scope Sync - Setting Environment e:\"{1}\"", null, _name, environment); SetEnvironmentImpl(environment); } From f8f932ad61313616d1f5c7ccc23126dd3aa4f9c0 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 10 Jul 2026 13:04:17 +0200 Subject: [PATCH 03/10] fetch/set environment early --- src/Sentry/Internal/ScopeObserver.cs | 2 +- src/Sentry/Scope.cs | 26 ++++++++++++++++++------- src/Sentry/SentrySdk.cs | 3 +++ test/Sentry.Tests/ScopeTests.cs | 29 ++++++++++++++++++++-------- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/Sentry/Internal/ScopeObserver.cs b/src/Sentry/Internal/ScopeObserver.cs index f316beabf4..0ba44c98a5 100644 --- a/src/Sentry/Internal/ScopeObserver.cs +++ b/src/Sentry/Internal/ScopeObserver.cs @@ -97,7 +97,7 @@ public void SetTrace(SentryId traceId, SpanId parentSpanId) public void SetEnvironment(string? environment) { - _options.DiagnosticLogger?.Log(SentryLevel.Debug, + _options.DiagnosticLogger?.Log(SentryLevel.Debug, "{0} Scope Sync - Setting Environment e:\"{1}\"", null, _name, environment); SetEnvironmentImpl(environment); } diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 601cee2c1a..26a84f43de 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -152,14 +152,24 @@ public string? Environment get => _environment; set { - if (_environment != value) + if (_environment == value) + { + return; + } + + if (value is null) + { + Options.LogWarning("Environment cannot be null. Reverting to default value from the options."); + _environment = Options.Environment; + } + else { _environment = value; - if (Options.EnableScopeSync && - Options.ScopeObserver is { } observer) - { - observer.SetEnvironment(value); - } + } + + if (Options is { EnableScopeSync: true, ScopeObserver: { } observer }) + { + observer.SetEnvironment(_environment); } } } @@ -305,6 +315,8 @@ internal Scope(SentryOptions? options, SentryPropagationContext? propagationCont { Options = options ?? new SentryOptions(); PropagationContext = new SentryPropagationContext(propagationContext); + + _environment = Options.Environment; } // For testing. Should explicitly require SentryOptions. @@ -423,7 +435,7 @@ public void Clear() User = new(); Release = default; Distribution = default; - Environment = default; + Environment = Options.Environment; // Restore to keep in sync with native TransactionName = default; Transaction = default; Fingerprint = Array.Empty(); diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index a6e31c57af..e0067fafb8 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -63,6 +63,9 @@ internal static IHub InitHub(SentryOptions options) #pragma warning restore 0162 #pragma warning restore CS0162 // Unreachable code detected + // This happens before the native SDKs get initialized + options.Environment = options.SettingLocator.GetEnvironment(); + // Initialize native platform SDKs here if (options.InitNativeSdks) { diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index 9a02dd1a20..9c2cf3fffc 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -708,7 +708,7 @@ public void SetEnvironment_ObserverExist_ObserverSetsEnvironmentIfEnabled(bool o ScopeObserver = observer, EnableScopeSync = observerEnable }); - var expectedEnvironment = "staging"; + const string expectedEnvironment = "staging"; var expectedCount = observerEnable ? 1 : 0; // Act @@ -719,25 +719,38 @@ public void SetEnvironment_ObserverExist_ObserverSetsEnvironmentIfEnabled(bool o } [Fact] - public void SetEnvironment_Null_ObserverClearsEnvironment() + public void SetEnvironment_Null_EnvironmentSetToOptionEnvironment() { // Arrange + const string expectedEnvironment = "staging"; + var scope = new Scope(new SentryOptions { Environment = expectedEnvironment }); + + // Act + scope.Environment = null; + + // Assert + scope.Environment.Should().Be(expectedEnvironment); + } + + [Fact] + public void SetEnvironment_Null_ObserverReceivesOptionEnvironment() + { + // Arrange + const string expectedEnvironment = "staging"; var observer = Substitute.For(); var scope = new Scope(new SentryOptions { ScopeObserver = observer, - EnableScopeSync = true - }) - { - Environment = "staging" - }; + EnableScopeSync = true, + Environment = expectedEnvironment + }); observer.ClearReceivedCalls(); // Act scope.Environment = null; // Assert - observer.Received(1).SetEnvironment(Arg.Is((string?)null)); + observer.Received(1).SetEnvironment(Arg.Is(expectedEnvironment)); } [Fact] From d2b5eb59f5621f1a7cb5bfeb8001eb36392304f7 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 10 Jul 2026 11:43:17 +0000 Subject: [PATCH 04/10] Accept API verifier changes --- test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt | 1 + 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 + 4 files changed, 4 insertions(+) diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 4b2ecc6f40..024203f121 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -220,6 +220,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 4b2ecc6f40..024203f121 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -220,6 +220,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 4b2ecc6f40..024203f121 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -220,6 +220,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 211c1e6ce2..2d91b6cea8 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -208,6 +208,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); From c6268d02335b54a768e8403b9399383316aea068 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 10 Jul 2026 15:10:20 +0200 Subject: [PATCH 05/10] verify --- test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt | 1 + 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 | 3 ++- 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 4b2ecc6f40..024203f121 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -220,6 +220,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 4b2ecc6f40..024203f121 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -220,6 +220,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 4b2ecc6f40..024203f121 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -220,6 +220,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 211c1e6ce2..8394533269 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -208,6 +208,7 @@ namespace Sentry void AddAttachment(Sentry.SentryAttachment attachment); void AddBreadcrumb(Sentry.Breadcrumb breadcrumb); void ClearAttachments(); + void SetEnvironment(string? environment); void SetExtra(string key, object? value); void SetTag(string key, string value); void SetTrace(Sentry.SentryId traceId, Sentry.SpanId parentSpanId); @@ -2029,4 +2030,4 @@ public static class SentryExceptionExtensions public static void AddSentryContext(this System.Exception ex, string name, System.Collections.Generic.IReadOnlyDictionary data) { } public static void AddSentryTag(this System.Exception ex, string name, string value) { } public static void SetSentryMechanism(this System.Exception ex, string type, string? description = null, bool? handled = default, bool? terminal = default) { } -} \ No newline at end of file +} From ce47fbb68ab2842e4fc58f38c81ddb858e43abe7 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 10 Jul 2026 15:23:29 +0200 Subject: [PATCH 06/10] read from scope first --- src/Sentry/Protocol/SentryAttributes.cs | 7 ++-- src/Sentry/Scope.cs | 1 + src/Sentry/SentryLog.cs | 3 +- src/Sentry/SentryMetric.Factory.cs | 2 +- src/Sentry/SentrySdk.cs | 1 + test/Sentry.Tests/SentryLogTests.cs | 36 ++++++++++++++++ test/Sentry.Tests/SentryMetricTests.cs | 55 +++++++++++++++++++++---- 7 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/Sentry/Protocol/SentryAttributes.cs b/src/Sentry/Protocol/SentryAttributes.cs index 711860e480..71b967b3d1 100644 --- a/src/Sentry/Protocol/SentryAttributes.cs +++ b/src/Sentry/Protocol/SentryAttributes.cs @@ -97,17 +97,18 @@ internal void SetAttribute(string key, int value) this[key] = new SentryAttribute(value, "integer"); } - internal void SetDefaultAttributes(SentryOptions options, SdkVersion sdk) + internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion? sdk = null) { - var environment = options.SettingLocator.GetEnvironment(); + var environment = scope?.Environment ?? options.SettingLocator.GetEnvironment(); SetAttribute("sentry.environment", environment); - var release = options.SettingLocator.GetRelease(); + var release = scope?.Release ?? options.SettingLocator.GetRelease(); if (release is not null) { SetAttribute("sentry.release", release); } + sdk ??= scope?.Sdk ?? SdkVersion.Instance; if (sdk.Name is { } name) { SetAttribute("sentry.sdk.name", name); diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 26a84f43de..a21bf37cd5 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -317,6 +317,7 @@ internal Scope(SentryOptions? options, SentryPropagationContext? propagationCont PropagationContext = new SentryPropagationContext(propagationContext); _environment = Options.Environment; + Release = Options.Release; } // For testing. Should explicitly require SentryOptions. diff --git a/src/Sentry/SentryLog.cs b/src/Sentry/SentryLog.cs index 8e74be21e8..4bea633e35 100644 --- a/src/Sentry/SentryLog.cs +++ b/src/Sentry/SentryLog.cs @@ -136,8 +136,7 @@ public bool TryGetAttribute(string key, [NotNullWhen(true)] out object? value) = internal void SetDefaultAttributes(SentryOptions options, Scope? scope, SdkVersion? sdk = null) { // Core Attributes - sdk ??= scope?.Sdk ?? SdkVersion.Instance; - Attributes.SetDefaultAttributes(options, sdk); + Attributes.SetDefaultAttributes(options, scope, sdk); // Server Attributes if (!string.IsNullOrEmpty(options.ServerName)) diff --git a/src/Sentry/SentryMetric.Factory.cs b/src/Sentry/SentryMetric.Factory.cs index 7c9621ce84..3cc51b655d 100644 --- a/src/Sentry/SentryMetric.Factory.cs +++ b/src/Sentry/SentryMetric.Factory.cs @@ -20,7 +20,7 @@ private static SentryMetric CreateCore(IHub hub, SentryOptions options, IS }; scope ??= hub.GetScope(); - metric.Attributes.SetDefaultAttributes(options, scope?.Sdk ?? SdkVersion.Instance); + metric.Attributes.SetDefaultAttributes(options, scope); return metric; } diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index e0067fafb8..b8b4b993f9 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -65,6 +65,7 @@ internal static IHub InitHub(SentryOptions options) // This happens before the native SDKs get initialized options.Environment = options.SettingLocator.GetEnvironment(); + options.Release = options.SettingLocator.GetRelease(); // Initialize native platform SDKs here if (options.InitNativeSdks) diff --git a/test/Sentry.Tests/SentryLogTests.cs b/test/Sentry.Tests/SentryLogTests.cs index c6481be437..ae1b08fda2 100644 --- a/test/Sentry.Tests/SentryLogTests.cs +++ b/test/Sentry.Tests/SentryLogTests.cs @@ -76,6 +76,42 @@ public void Protocol_Default_VerifyAttributes() log.Attributes.ShouldNotContain("not-found"); } + [Fact] + public void SetDefaultAttributes_ScopeOverridesOptions_UsesScopeValues() + { + var options = new SentryOptions + { + Environment = "options-environment", + Release = "options-release", + }; + var scope = new Scope(options) + { + Environment = "scope-environment", + Release = "scope-release", + }; + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, scope); + + log.Attributes.ShouldContain("sentry.environment", "scope-environment"); + log.Attributes.ShouldContain("sentry.release", "scope-release"); + } + + [Fact] + public void SetDefaultAttributes_NullScope_FallsBackToOptionsSettings() + { + var options = new SentryOptions + { + Release = "options-release", + }; + var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); + + log.SetDefaultAttributes(options, scope: null); + + log.Attributes.ShouldContain("sentry.environment", options.SettingLocator.GetEnvironment()); + log.Attributes.ShouldContain("sentry.release", options.SettingLocator.GetRelease()); + } + [Fact] public void SetDefaultAttributes_OptionsServerName_SetsServerAddress() { diff --git a/test/Sentry.Tests/SentryMetricTests.cs b/test/Sentry.Tests/SentryMetricTests.cs index d369324cb7..006bccefd8 100644 --- a/test/Sentry.Tests/SentryMetricTests.cs +++ b/test/Sentry.Tests/SentryMetricTests.cs @@ -44,10 +44,13 @@ public void Protocol_Default_VerifyAttributes() Environment = "my-environment", Release = "my-release", }; - var sdk = new SdkVersion + var scope = new Scope(options) { - Name = "Sentry.Test.SDK", - Version = "1.2.3-test+Sentry", + Sdk = + { + Name = "Sentry.Test.SDK", + Version = "1.2.3-test+Sentry" + } }; var metric = new SentryMetric(Timestamp, TraceId, SentryMetricType.Counter, "sentry_tests.sentry_metric_tests.counter", 1) @@ -56,7 +59,7 @@ public void Protocol_Default_VerifyAttributes() Unit = "test_unit", }; metric.SetAttribute("attribute", "value"); - metric.Attributes.SetDefaultAttributes(options, sdk); + metric.Attributes.SetDefaultAttributes(options, scope); metric.Timestamp.Should().Be(Timestamp); metric.TraceId.Should().Be(TraceId); @@ -69,11 +72,47 @@ public void Protocol_Default_VerifyAttributes() metric.Attributes.ShouldContain("attribute", "value"); metric.Attributes.ShouldContain("sentry.environment", options.Environment); metric.Attributes.ShouldContain("sentry.release", options.Release); - metric.Attributes.ShouldContain("sentry.sdk.name", sdk.Name); - metric.Attributes.ShouldContain("sentry.sdk.version", sdk.Version); + metric.Attributes.ShouldContain("sentry.sdk.name", scope.Sdk.Name); + metric.Attributes.ShouldContain("sentry.sdk.version", scope.Sdk.Version); metric.Attributes.ShouldNotContain("not-found"); } + [Fact] + public void SetDefaultAttributes_ScopeOverridesOptions_UsesScopeValues() + { + var options = new SentryOptions + { + Environment = "options-environment", + Release = "options-release", + }; + var scope = new Scope(options) + { + Environment = "scope-environment", + Release = "scope-release", + }; + var metric = new SentryMetric(Timestamp, TraceId, SentryMetricType.Counter, "sentry_tests.sentry_metric_tests.counter", 1); + + metric.Attributes.SetDefaultAttributes(options, scope); + + metric.Attributes.ShouldContain("sentry.environment", "scope-environment"); + metric.Attributes.ShouldContain("sentry.release", "scope-release"); + } + + [Fact] + public void SetDefaultAttributes_NullScope_FallsBackToOptionsSettings() + { + var options = new SentryOptions + { + Release = "options-release", + }; + var metric = new SentryMetric(Timestamp, TraceId, SentryMetricType.Counter, "sentry_tests.sentry_metric_tests.counter", 1); + + metric.Attributes.SetDefaultAttributes(options, scope: null); + + metric.Attributes.ShouldContain("sentry.environment", options.SettingLocator.GetEnvironment()); + metric.Attributes.ShouldContain("sentry.release", options.SettingLocator.GetRelease()); + } + [Fact] public void WriteTo_Envelope_MinimalSerializedSentryMetric() { @@ -84,7 +123,7 @@ public void WriteTo_Envelope_MinimalSerializedSentryMetric() }; var metric = new SentryMetric(Timestamp, TraceId, SentryMetricType.Counter, "sentry_tests.sentry_metric_tests.counter", 1); - metric.Attributes.SetDefaultAttributes(options, new SdkVersion()); + metric.Attributes.SetDefaultAttributes(options, new Scope()); var envelope = Envelope.FromMetric(new TraceMetric([metric])); @@ -162,7 +201,7 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryMetric() metric.SetAttribute("boolean-attribute", true); metric.SetAttribute("integer-attribute", 3); metric.SetAttribute("double-attribute", 4.4); - metric.Attributes.SetDefaultAttributes(options, new SdkVersion { Name = "Sentry.Test.SDK", Version = "1.2.3-test+Sentry" }); + metric.Attributes.SetDefaultAttributes(options, new Scope { Sdk = { Name = "Sentry.Test.SDK", Version = "1.2.3-test+Sentry" } }); var envelope = EnvelopeItem.FromMetric(new TraceMetric([metric])); From 55b2d5dcc829c078d22637a5f7ffc0efb13cc196 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Fri, 10 Jul 2026 15:45:03 +0200 Subject: [PATCH 07/10] clear release --- src/Sentry/Scope.cs | 2 +- test/Sentry.Tests/ScopeTests.cs | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index a21bf37cd5..cad9b94758 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -434,7 +434,7 @@ public void Clear() Request = new(); Contexts.Clear(); User = new(); - Release = default; + Release = Options.Release; Distribution = default; Environment = Options.Environment; // Restore to keep in sync with native TransactionName = default; diff --git a/test/Sentry.Tests/ScopeTests.cs b/test/Sentry.Tests/ScopeTests.cs index 9c2cf3fffc..ff0c9b5a17 100644 --- a/test/Sentry.Tests/ScopeTests.cs +++ b/test/Sentry.Tests/ScopeTests.cs @@ -426,6 +426,29 @@ public void Clear_SetsPropertiesToDefaultValues() } } + [Fact] + public void Clear_OptionsHaveEnvironmentAndRelease_RestoresToOptionsValues() + { + // Arrange + var options = new SentryOptions + { + Environment = "options-environment", + Release = "options-release", + }; + var sut = new Scope(options); + sut.ApplyFakeValues(); + + // Act + sut.Clear(); + + // Assert + using (new AssertionScope()) + { + sut.Environment.Should().Be("options-environment"); + sut.Release.Should().Be("options-release"); + } + } + [Fact] public void Clear_ResetsPropagationContext() { From 52db5d34e5af4c87ef6d1920ba412c027e0ce549 Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 13 Jul 2026 10:34:58 +0200 Subject: [PATCH 08/10] comment and changelog --- CHANGELOG.md | 1 + src/Sentry/Scope.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08dd24178d..7809e148a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features ✨ - feat: Add exponential backoff and log deduplication to Spotlight transport by @mattico in [#5025](https://github.com/getsentry/sentry-dotnet/pull/5025) +- feat: The `Environment` set on the `Scope` now gets synchronized to the native layers (`sentry-cocoa` and `sentry-native`) by @bitsandfoxes [#5365](https://github.com/getsentry/sentry-dotnet/pull/5365) ## 6.6.0 diff --git a/src/Sentry/Scope.cs b/src/Sentry/Scope.cs index 26a84f43de..363fac1367 100644 --- a/src/Sentry/Scope.cs +++ b/src/Sentry/Scope.cs @@ -316,6 +316,7 @@ internal Scope(SentryOptions? options, SentryPropagationContext? propagationCont Options = options ?? new SentryOptions(); PropagationContext = new SentryPropagationContext(propagationContext); + // Skip scope sync with backing field. The native SDKs already received the environment set on the options. _environment = Options.Environment; } From 184ea532e4c58a8d6b06c27fe9d63e49fa4e5f2f Mon Sep 17 00:00:00 2001 From: Stefan Jandl Date: Mon, 13 Jul 2026 10:41:12 +0200 Subject: [PATCH 09/10] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stefan Pölz <38893694+Flash0ver@users.noreply.github.com> --- test/Sentry.Tests/SentryLogTests.cs | 1 + test/Sentry.Tests/SentryMetricTests.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/Sentry.Tests/SentryLogTests.cs b/test/Sentry.Tests/SentryLogTests.cs index ae1b08fda2..f828d3c659 100644 --- a/test/Sentry.Tests/SentryLogTests.cs +++ b/test/Sentry.Tests/SentryLogTests.cs @@ -102,6 +102,7 @@ public void SetDefaultAttributes_NullScope_FallsBackToOptionsSettings() { var options = new SentryOptions { + Environment = "options-environment", Release = "options-release", }; var log = new SentryLog(Timestamp, TraceId, SentryLogLevel.Info, "message"); diff --git a/test/Sentry.Tests/SentryMetricTests.cs b/test/Sentry.Tests/SentryMetricTests.cs index 006bccefd8..ef7c366897 100644 --- a/test/Sentry.Tests/SentryMetricTests.cs +++ b/test/Sentry.Tests/SentryMetricTests.cs @@ -103,6 +103,7 @@ public void SetDefaultAttributes_NullScope_FallsBackToOptionsSettings() { var options = new SentryOptions { + Environment = "options-environment", Release = "options-release", }; var metric = new SentryMetric(Timestamp, TraceId, SentryMetricType.Counter, "sentry_tests.sentry_metric_tests.counter", 1); @@ -123,7 +124,7 @@ public void WriteTo_Envelope_MinimalSerializedSentryMetric() }; var metric = new SentryMetric(Timestamp, TraceId, SentryMetricType.Counter, "sentry_tests.sentry_metric_tests.counter", 1); - metric.Attributes.SetDefaultAttributes(options, new Scope()); + metric.Attributes.SetDefaultAttributes(options, new Scope(options)); var envelope = Envelope.FromMetric(new TraceMetric([metric])); @@ -201,7 +202,7 @@ public void WriteTo_EnvelopeItem_MaximalSerializedSentryMetric() metric.SetAttribute("boolean-attribute", true); metric.SetAttribute("integer-attribute", 3); metric.SetAttribute("double-attribute", 4.4); - metric.Attributes.SetDefaultAttributes(options, new Scope { Sdk = { Name = "Sentry.Test.SDK", Version = "1.2.3-test+Sentry" } }); + metric.Attributes.SetDefaultAttributes(options, new Scope(options) { Sdk = { Name = "Sentry.Test.SDK", Version = "1.2.3-test+Sentry" } }); var envelope = EnvelopeItem.FromMetric(new TraceMetric([metric])); From 4db9f38deae4ea9852e594ed95eccbcc383bc8fb Mon Sep 17 00:00:00 2001 From: bitsandfoxes Date: Mon, 13 Jul 2026 11:08:10 +0200 Subject: [PATCH 10/10] updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7809e148a2..055ed0a59a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ - feat: Add exponential backoff and log deduplication to Spotlight transport by @mattico in [#5025](https://github.com/getsentry/sentry-dotnet/pull/5025) - feat: The `Environment` set on the `Scope` now gets synchronized to the native layers (`sentry-cocoa` and `sentry-native`) by @bitsandfoxes [#5365](https://github.com/getsentry/sentry-dotnet/pull/5365) +### Fixes + +- fix: When setting `Environment` and `Release` on the `Scope` these are now also getting applied on emitted Metrics and Logs by bitsandfoxes [#5376](https://github.com/getsentry/sentry-dotnet/pull/5376) + ## 6.6.0 ### Features ✨