Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
23fc214
added get/set environment to scope
bitsandfoxes Jul 16, 2026
461a85d
updated changelog
bitsandfoxes Jul 17, 2026
b81145f
Merge remote-tracking branch 'origin/main' into feat/scope-set-enviro…
bitsandfoxes Jul 17, 2026
f3b38ec
updated changelog
bitsandfoxes Jul 17, 2026
a71958e
sync scope clear
bitsandfoxes Jul 27, 2026
879e15a
clear
bitsandfoxes Jul 27, 2026
c66e379
apply to replay
bitsandfoxes Jul 27, 2026
0ae192d
Merge branch 'main' into feat/scope-set-environment
bitsandfoxes Jul 27, 2026
2b9303d
merged main
bitsandfoxes Jul 28, 2026
8ad87f7
scope helper
bitsandfoxes Jul 28, 2026
75d361f
comment
bitsandfoxes Jul 28, 2026
8da38a8
made scope apply its environment on other events too
bitsandfoxes Jul 28, 2026
3163e45
Update CHANGELOG.md
bitsandfoxes Jul 29, 2026
9869077
chore: merge main into feat/scope-set-environment
markushi Sep 1, 2026
1a989fa
feat: apply the scope environment to Baggage/DSC and address review f…
markushi Sep 1, 2026
77428a4
fix: read the DSC environment from the combined scope view instead of…
markushi Sep 1, 2026
6aa0973
feat: apply the scope environment to logs, metrics and profiles
markushi Sep 1, 2026
5181bb1
test: stub the ScopeType overload of configureScope in scopes mocks
markushi Sep 2, 2026
658a240
feat(android): sync the scope environment to the NDK scope
markushi Sep 2, 2026
857f45e
Merge branch 'main' into feat/scope-set-environment [pr-shepherd]
sentry-junior[bot] Oct 27, 2026
790faf8
ci: fix Build Job ubuntu-latest [pr-shepherd]
sentry-junior[bot] Oct 27, 2026
8222e1c
test(apollo-5): stub the ScopeType overload of configureScope in scop…
markushi Oct 27, 2026
c672469
chore: move changelog entry to Unreleased
markushi Oct 27, 2026
34463e0
Merge remote-tracking branch 'origin/main' into feat/scope-set-enviro…
markushi Oct 27, 2026
e56cd1e
fix(ndk): catch only RejectedExecutionException when syncing the scop…
markushi Oct 27, 2026
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

### Features

- Added `Environment` to the `Scope`, allowing you to overwrite the `Environment` set on the options ([#5772](https://github.com/getsentry/sentry-java/pull/5772))
- The scope `Environment` is also applied to the trace envelope header (DSC), logs, metrics and profiles, so all telemetry of a trace reports the same environment

### Fixes

- Keep UI profiling running when `startProfiler` is called before a requested stop takes effect at the end of the current chunk ([#6291](https://github.com/getsentry/sentry-java/pull/6291))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ private void backfillScope(
setRequest(event);
setUser(event);
setScopeTags(event);
setScopeEnvironment(event);
setBreadcrumbs(event);
setExtras(event);
setContexts(event);
Comment thread
bitsandfoxes marked this conversation as resolved.
Expand Down Expand Up @@ -492,6 +493,16 @@ private void setEnvironment(
}
}

private void setScopeEnvironment(final @NotNull SentryBaseEvent event) {
if (event.getEnvironment() == null) {
final @Nullable String scopeEnvironment =
readFromDisk(options, PersistingScopeObserver.ENVIRONMENT_FILENAME, String.class);
if (scopeEnvironment != null) {
event.setEnvironment(scopeEnvironment);
}
}
}

private void setDebugMeta(
final @NotNull SentryBaseEvent event, final @NotNull OptionsSource optionsSource) {
DebugMeta debugMeta = event.getDebugMeta();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,18 @@ class ApplicationExitInfoEventProcessorTest {
assertEquals("release", processed.environment)
}

@Test
fun `if scope environment is persisted, it takes precedence over the options environment`() {
val hint = HintUtils.createWithTypeCheckHint(BackfillableHint())

val processor = fixture.getSut(tmpDir, populateOptionsCache = true)
fixture.persistScope(PersistingScopeObserver.ENVIRONMENT_FILENAME, "staging")

val processed = processor.process(SentryEvent(), hint)

assertEquals("staging", processed!!.environment)
}

@Test
fun `if release is not persisted and app was not updated, uses release from options`() {
val hint = HintUtils.createWithTypeCheckHint(AbnormalExitHint(timestamp = 2_000))
Expand Down Expand Up @@ -542,6 +554,7 @@ class ApplicationExitInfoEventProcessorTest {
fixture.options.release = "io.sentry.samples@1.2.0+232"
fixture.options.environment = "production"
fixture.options.dist = "custom-dist"
fixture.persistScope(PersistingScopeObserver.ENVIRONMENT_FILENAME, "staging")
setLastUpdateTime(1_000)

val processed = processor.process(SentryEvent(), hint)!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.sentry.IScopes
import io.sentry.PropagationContext
import io.sentry.Scope.IWithPropagationContext
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryLevel.INFO
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.util.LazyEvaluator
Expand Down Expand Up @@ -85,6 +86,9 @@ class SentryGestureListenerClickTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(anyOrNull<ScopeType>(), any())
doAnswer {
(it.arguments[0] as IWithPropagationContext).accept(propagationContext)
propagationContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.sentry.IScopes
import io.sentry.PropagationContext
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryLevel
import io.sentry.SentryLevel.INFO
import io.sentry.android.core.SentryAndroidOptions
Expand Down Expand Up @@ -82,6 +83,9 @@ class SentryGestureListenerScrollTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(anyOrNull<ScopeType>(), any())
doAnswer {
(it.arguments[0] as Scope.IWithPropagationContext).accept(propagationContext)
propagationContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.sentry.IScope
import io.sentry.IScopes
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryTracer
import io.sentry.SpanContext
import io.sentry.SpanId
Expand All @@ -31,6 +32,7 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
import org.mockito.ArgumentCaptor
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.check
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.doAnswer
Expand Down Expand Up @@ -94,6 +96,9 @@ class SentryGestureListenerTracingTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(anyOrNull<ScopeType>(), any())

return SentryGestureListener(activity, scopes, options)
}
Expand Down
1 change: 1 addition & 0 deletions sentry-android-ndk/api/sentry-android-ndk.api
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public final class io/sentry/android/ndk/NdkScopeObserver : io/sentry/ScopeObser
public fun clearAttachments ()V
public fun removeExtra (Ljava/lang/String;)V
public fun removeTag (Ljava/lang/String;)V
public fun setEnvironment (Ljava/lang/String;)V
public fun setExtra (Ljava/lang/String;Ljava/lang/String;)V
public fun setTag (Ljava/lang/String;Ljava/lang/String;)V
public fun setTrace (Lio/sentry/SpanContext;Lio/sentry/IScope;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.sentry.util.Objects;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -53,6 +54,15 @@ public void setUser(final @Nullable User user) {
}
}

@Override
public void setEnvironment(@Nullable String environment) {
try {
options.getExecutorService().submit(() -> nativeScope.setEnvironment(environment));
} catch (RejectedExecutionException e) {
options.getLogger().log(SentryLevel.ERROR, e, "Scope sync setEnvironment has an error.");
}
Comment thread
cursor[bot] marked this conversation as resolved.
}

@Override
public void addBreadcrumb(final @NotNull Breadcrumb crumb) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,15 @@ class NdkScopeObserverTest {

verify(fixture.nativeScope).clearAttachments()
}

@Test
fun `set environments forwards call to native scope`() {
val sut = fixture.getSut()

sut.setEnvironment(null)
verify(fixture.nativeScope).setEnvironment(eq(null))

sut.setEnvironment("production")
verify(fixture.nativeScope).setEnvironment(eq("production"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import io.sentry.IScopes
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryOptions
import io.sentry.SentryReplayEvent.ReplayType
import io.sentry.android.replay.util.ReplayShadowMediaCodec
Expand Down Expand Up @@ -72,6 +73,9 @@ class ReplaySmokeTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())

on { rateLimiter }.doReturn(rateLimiter)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.sentry.DataCategory
import io.sentry.IScopes
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryEnvelope
import io.sentry.SentryEnvelopeHeader
import io.sentry.SentryOptions
Expand Down Expand Up @@ -67,6 +68,9 @@ class BufferCaptureStrategyTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())
}
var persistedSegment = LinkedHashMap<String, String?>()
val replayCache =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.sentry.DateUtils
import io.sentry.IScopes
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryOptions
import io.sentry.SentryReplayEvent
import io.sentry.SentryReplayEvent.ReplayType
Expand Down Expand Up @@ -86,6 +87,9 @@ class SessionCaptureStrategyTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())
}
var persistedSegment = LinkedHashMap<String, String?>()
val replayCache =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.sentry.IScopes
import io.sentry.ITransaction
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS
Expand Down Expand Up @@ -71,6 +72,9 @@ class SentryApollo3InterceptorTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())
}
private var httpInterceptor =
SentryApollo3HttpInterceptor(scopes, captureFailedRequests = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import io.sentry.IScopes
import io.sentry.ITransaction
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS
Expand Down Expand Up @@ -85,6 +86,9 @@ abstract class SentryApollo4HttpInterceptorTest(
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())
}
private var httpInterceptor =
SentryApollo4HttpInterceptor(scopes, captureFailedRequests = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.sentry.IScopes
import io.sentry.ITransaction
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryOptions
import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS
import io.sentry.SentryTraceHeader
Expand Down Expand Up @@ -88,6 +89,9 @@ abstract class SentryApollo5HttpInterceptorTest(
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())
}
private var httpInterceptor =
SentryApollo5HttpInterceptor(scopes, captureFailedRequests = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import io.sentry.IScopes
import io.sentry.ITransaction
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
Expand Down Expand Up @@ -62,6 +63,9 @@ class SentryApolloInterceptorTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(it)
.configureScope(anyOrNull<ScopeType>(), any())
}
private var interceptor = SentryApolloInterceptor(scopes)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import io.sentry.IScopes
import io.sentry.KeyValueCollectionBehavior
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.SentryOptions
import io.sentry.exception.ExceptionMechanismException
import io.sentry.protocol.Request
Expand All @@ -28,6 +29,7 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertSame
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
Expand Down Expand Up @@ -91,6 +93,9 @@ class ExceptionReporterTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(anyOrNull<ScopeType>(), any())

return exceptionReporter
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.sentry.ISpan
import io.sentry.NoOpSpan
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.Sentry
import io.sentry.SentryOptions
import io.sentry.SentryTraceHeader
Expand All @@ -33,6 +34,7 @@ import org.apache.kafka.common.TopicPartition
import org.apache.kafka.common.header.Header
import org.apache.kafka.common.header.Headers
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.eq
Expand Down Expand Up @@ -64,6 +66,9 @@ class SentryKafkaProducerTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(Scope(options)) }
.whenever(scopes)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(Scope(options)) }
.whenever(scopes)
.configureScope(anyOrNull<ScopeType>(), any())
delegate = mock()
whenever(delegate.send(any(), any())).thenReturn(CompletableFuture.completedFuture(null))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.sentry.IScopes
import io.sentry.KeyValueCollectionBehavior
import io.sentry.Scope
import io.sentry.ScopeCallback
import io.sentry.ScopeType
import io.sentry.Sentry
import io.sentry.SentryEvent
import io.sentry.SentryLevel
Expand Down Expand Up @@ -99,6 +100,9 @@ class SentryKtorClientPluginTest {
doAnswer { (it.arguments[0] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(any())
doAnswer { (it.arguments[1] as ScopeCallback).run(scope) }
.whenever(scopes)
.configureScope(anyOrNull<ScopeType>(), any())

sentryTracer = SentryTracer(TransactionContext("name", "op"), scopes)

Expand Down
Loading
Loading