Skip to content

Commit bbab8da

Browse files
authored
Merge 4545735 into 8ec9ca8
2 parents 8ec9ca8 + 4545735 commit bbab8da

11 files changed

Lines changed: 389 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add strict trace continuation support ([#5136](https://github.com/getsentry/sentry-java/pull/5136))
8+
- The SDK now extracts `org_id` from the DSN host and propagates it via `sentry-org_id` in the baggage header.
9+
- When an incoming trace has a mismatched `org_id`, the SDK starts a new trace instead of continuing the foreign one.
10+
- New option `strictTraceContinuation` (default `false`): when enabled, both the SDK's org ID and the incoming baggage org ID must be present and match for a trace to be continued.
11+
- New option `orgId`: allows explicitly setting the organization ID for self-hosted and Relay setups where it cannot be extracted from the DSN.
12+
313
## 8.34.0
414

515
### Features

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ final class ManifestMetadataReader {
167167

168168
static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding";
169169

170+
static final String STRICT_TRACE_CONTINUATION = "io.sentry.strict-trace-continuation";
171+
static final String ORG_ID = "io.sentry.org-id";
172+
170173
static final String SPOTLIGHT_ENABLE = "io.sentry.spotlight.enable";
171174

172175
static final String SPOTLIGHT_CONNECTION_URL = "io.sentry.spotlight.url";
@@ -658,6 +661,18 @@ static void applyMetadata(
658661
feedbackOptions.setShowBranding(
659662
readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding()));
660663

664+
options.setStrictTraceContinuation(
665+
readBool(
666+
metadata,
667+
logger,
668+
STRICT_TRACE_CONTINUATION,
669+
options.isStrictTraceContinuation()));
670+
671+
final @Nullable String orgId = readString(metadata, logger, ORG_ID, null);
672+
if (orgId != null) {
673+
options.setOrgId(orgId);
674+
}
675+
661676
options.setEnableSpotlight(
662677
readBool(metadata, logger, SPOTLIGHT_ENABLE, options.isEnableSpotlight()));
663678

sentry/api/sentry.api

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public final class io/sentry/Baggage {
4747
public static fun fromHeader (Ljava/util/List;ZLio/sentry/ILogger;)Lio/sentry/Baggage;
4848
public fun get (Ljava/lang/String;)Ljava/lang/String;
4949
public fun getEnvironment ()Ljava/lang/String;
50+
public fun getOrgId ()Ljava/lang/String;
5051
public fun getPublicKey ()Ljava/lang/String;
5152
public fun getRelease ()Ljava/lang/String;
5253
public fun getReplayId ()Ljava/lang/String;
@@ -62,6 +63,7 @@ public final class io/sentry/Baggage {
6263
public fun isShouldFreeze ()Z
6364
public fun set (Ljava/lang/String;Ljava/lang/String;)V
6465
public fun setEnvironment (Ljava/lang/String;)V
66+
public fun setOrgId (Ljava/lang/String;)V
6567
public fun setPublicKey (Ljava/lang/String;)V
6668
public fun setRelease (Ljava/lang/String;)V
6769
public fun setReplayId (Ljava/lang/String;)V
@@ -81,6 +83,7 @@ public final class io/sentry/Baggage {
8183
public final class io/sentry/Baggage$DSCKeys {
8284
public static final field ALL Ljava/util/List;
8385
public static final field ENVIRONMENT Ljava/lang/String;
86+
public static final field ORG_ID Ljava/lang/String;
8487
public static final field PUBLIC_KEY Ljava/lang/String;
8588
public static final field RELEASE Ljava/lang/String;
8689
public static final field REPLAY_ID Ljava/lang/String;
@@ -501,6 +504,7 @@ public final class io/sentry/ExternalOptions {
501504
public fun getInAppExcludes ()Ljava/util/List;
502505
public fun getInAppIncludes ()Ljava/util/List;
503506
public fun getMaxRequestBodySize ()Lio/sentry/SentryOptions$RequestSize;
507+
public fun getOrgId ()Ljava/lang/String;
504508
public fun getPrintUncaughtStackTrace ()Ljava/lang/Boolean;
505509
public fun getProfileLifecycle ()Lio/sentry/ProfileLifecycle;
506510
public fun getProfileSessionSampleRate ()Ljava/lang/Double;
@@ -528,6 +532,7 @@ public final class io/sentry/ExternalOptions {
528532
public fun isGlobalHubMode ()Ljava/lang/Boolean;
529533
public fun isSendDefaultPii ()Ljava/lang/Boolean;
530534
public fun isSendModules ()Ljava/lang/Boolean;
535+
public fun isStrictTraceContinuation ()Ljava/lang/Boolean;
531536
public fun setCaptureOpenTelemetryEvents (Ljava/lang/Boolean;)V
532537
public fun setCron (Lio/sentry/SentryOptions$Cron;)V
533538
public fun setDebug (Ljava/lang/Boolean;)V
@@ -550,6 +555,7 @@ public final class io/sentry/ExternalOptions {
550555
public fun setIgnoredErrors (Ljava/util/List;)V
551556
public fun setIgnoredTransactions (Ljava/util/List;)V
552557
public fun setMaxRequestBodySize (Lio/sentry/SentryOptions$RequestSize;)V
558+
public fun setOrgId (Ljava/lang/String;)V
553559
public fun setPrintUncaughtStackTrace (Ljava/lang/Boolean;)V
554560
public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V
555561
public fun setProfileSessionSampleRate (Ljava/lang/Double;)V
@@ -564,6 +570,7 @@ public final class io/sentry/ExternalOptions {
564570
public fun setSendModules (Ljava/lang/Boolean;)V
565571
public fun setServerName (Ljava/lang/String;)V
566572
public fun setSpotlightConnectionUrl (Ljava/lang/String;)V
573+
public fun setStrictTraceContinuation (Ljava/lang/Boolean;)V
567574
public fun setTag (Ljava/lang/String;Ljava/lang/String;)V
568575
public fun setTracesSampleRate (Ljava/lang/Double;)V
569576
}
@@ -2267,6 +2274,7 @@ public final class io/sentry/PropagationContext {
22672274
public static fun fromExistingTrace (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/Double;)Lio/sentry/PropagationContext;
22682275
public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext;
22692276
public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext;
2277+
public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;Lio/sentry/SentryOptions;)Lio/sentry/PropagationContext;
22702278
public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext;
22712279
public fun getBaggage ()Lio/sentry/Baggage;
22722280
public fun getParentSpanId ()Lio/sentry/SpanId;
@@ -3569,6 +3577,7 @@ public class io/sentry/SentryOptions {
35693577
public fun getDistribution ()Lio/sentry/SentryOptions$DistributionOptions;
35703578
public fun getDistributionController ()Lio/sentry/IDistributionApi;
35713579
public fun getDsn ()Ljava/lang/String;
3580+
public fun getEffectiveOrgId ()Ljava/lang/String;
35723581
public fun getEnvelopeDiskCache ()Lio/sentry/cache/IEnvelopeCache;
35733582
public fun getEnvelopeReader ()Lio/sentry/IEnvelopeReader;
35743583
public fun getEnvironment ()Ljava/lang/String;
@@ -3609,6 +3618,7 @@ public class io/sentry/SentryOptions {
36093618
public fun getOnOversizedEvent ()Lio/sentry/SentryOptions$OnOversizedEventCallback;
36103619
public fun getOpenTelemetryMode ()Lio/sentry/SentryOpenTelemetryMode;
36113620
public fun getOptionsObservers ()Ljava/util/List;
3621+
public fun getOrgId ()Ljava/lang/String;
36123622
public fun getOutboxPath ()Ljava/lang/String;
36133623
public fun getPerformanceCollectors ()Ljava/util/List;
36143624
public fun getProfileLifecycle ()Lio/sentry/ProfileLifecycle;
@@ -3679,6 +3689,7 @@ public class io/sentry/SentryOptions {
36793689
public fun isSendDefaultPii ()Z
36803690
public fun isSendModules ()Z
36813691
public fun isStartProfilerOnAppStart ()Z
3692+
public fun isStrictTraceContinuation ()Z
36823693
public fun isTraceOptionsRequests ()Z
36833694
public fun isTraceSampling ()Z
36843695
public fun isTracingEnabled ()Z
@@ -3762,6 +3773,7 @@ public class io/sentry/SentryOptions {
37623773
public fun setOnDiscard (Lio/sentry/SentryOptions$OnDiscardCallback;)V
37633774
public fun setOnOversizedEvent (Lio/sentry/SentryOptions$OnOversizedEventCallback;)V
37643775
public fun setOpenTelemetryMode (Lio/sentry/SentryOpenTelemetryMode;)V
3776+
public fun setOrgId (Ljava/lang/String;)V
37653777
public fun setPrintUncaughtStackTrace (Z)V
37663778
public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V
37673779
public fun setProfileSessionSampleRate (Ljava/lang/Double;)V
@@ -3793,6 +3805,7 @@ public class io/sentry/SentryOptions {
37933805
public fun setSpotlightConnectionUrl (Ljava/lang/String;)V
37943806
public fun setSslSocketFactory (Ljavax/net/ssl/SSLSocketFactory;)V
37953807
public fun setStartProfilerOnAppStart (Z)V
3808+
public fun setStrictTraceContinuation (Z)V
37963809
public fun setTag (Ljava/lang/String;Ljava/lang/String;)V
37973810
public fun setThreadChecker (Lio/sentry/util/thread/IThreadChecker;)V
37983811
public fun setTraceOptionsRequests (Z)V

sentry/src/main/java/io/sentry/Baggage.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ public static Baggage fromEvent(
186186
baggage.setPublicKey(options.retrieveParsedDsn().getPublicKey());
187187
baggage.setRelease(event.getRelease());
188188
baggage.setEnvironment(event.getEnvironment());
189+
baggage.setOrgId(options.getEffectiveOrgId());
189190
baggage.setTransaction(transaction);
190191
// we don't persist sample rate
191192
baggage.setSampleRate(null);
@@ -450,6 +451,16 @@ public void setReplayId(final @Nullable String replayId) {
450451
set(DSCKeys.REPLAY_ID, replayId);
451452
}
452453

454+
@ApiStatus.Internal
455+
public @Nullable String getOrgId() {
456+
return get(DSCKeys.ORG_ID);
457+
}
458+
459+
@ApiStatus.Internal
460+
public void setOrgId(final @Nullable String orgId) {
461+
set(DSCKeys.ORG_ID, orgId);
462+
}
463+
453464
/**
454465
* Sets / updates a value, but only if the baggage is still mutable.
455466
*
@@ -501,6 +512,7 @@ public void setValuesFromTransaction(
501512
if (replayId != null && !SentryId.EMPTY_ID.equals(replayId)) {
502513
setReplayId(replayId.toString());
503514
}
515+
setOrgId(sentryOptions.getEffectiveOrgId());
504516
setSampleRate(sampleRate(samplingDecision));
505517
setSampled(StringUtils.toString(sampled(samplingDecision)));
506518
setSampleRand(sampleRand(samplingDecision));
@@ -536,6 +548,7 @@ public void setValuesFromScope(
536548
if (!SentryId.EMPTY_ID.equals(replayId)) {
537549
setReplayId(replayId.toString());
538550
}
551+
setOrgId(options.getEffectiveOrgId());
539552
setTransaction(null);
540553
setSampleRate(null);
541554
setSampled(null);
@@ -632,6 +645,7 @@ public static final class DSCKeys {
632645
public static final String SAMPLE_RAND = "sentry-sample_rand";
633646
public static final String SAMPLED = "sentry-sampled";
634647
public static final String REPLAY_ID = "sentry-replay_id";
648+
public static final String ORG_ID = "sentry-org_id";
635649

636650
public static final List<String> ALL =
637651
Arrays.asList(
@@ -644,6 +658,7 @@ public static final class DSCKeys {
644658
SAMPLE_RATE,
645659
SAMPLE_RAND,
646660
SAMPLED,
647-
REPLAY_ID);
661+
REPLAY_ID,
662+
ORG_ID);
648663
}
649664
}

sentry/src/main/java/io/sentry/Dsn.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
import io.sentry.util.Objects;
44
import java.net.URI;
5+
import java.util.regex.Matcher;
6+
import java.util.regex.Pattern;
57
import org.jetbrains.annotations.NotNull;
68
import org.jetbrains.annotations.Nullable;
79

810
final class Dsn {
11+
private static final @NotNull Pattern ORG_ID_PATTERN = Pattern.compile("^o(\\d+)\\.");
12+
913
private final @NotNull String projectId;
1014
private final @Nullable String path;
1115
private final @Nullable String secretKey;
1216
private final @NotNull String publicKey;
1317
private final @NotNull URI sentryUri;
18+
private @Nullable String orgId;
1419

1520
/*
1621
/ The project ID which the authenticated user is bound to.
@@ -87,8 +92,27 @@ URI getSentryUri() {
8792
sentryUri =
8893
new URI(
8994
scheme, null, uri.getHost(), uri.getPort(), path + "api/" + projectId, null, null);
95+
96+
// Extract org ID from host (e.g., "o123.ingest.sentry.io" -> "123")
97+
String extractedOrgId = null;
98+
final String host = uri.getHost();
99+
if (host != null) {
100+
final Matcher matcher = ORG_ID_PATTERN.matcher(host);
101+
if (matcher.find()) {
102+
extractedOrgId = matcher.group(1);
103+
}
104+
}
105+
orgId = extractedOrgId;
90106
} catch (Throwable e) {
91107
throw new IllegalArgumentException(e);
92108
}
93109
}
110+
111+
public @Nullable String getOrgId() {
112+
return orgId;
113+
}
114+
115+
public void setOrgId(final @Nullable String orgId) {
116+
this.orgId = orgId;
117+
}
94118
}

sentry/src/main/java/io/sentry/ExternalOptions.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public final class ExternalOptions {
6363
private @Nullable String profilingTracesDirPath;
6464
private @Nullable ProfileLifecycle profileLifecycle;
6565

66+
private @Nullable Boolean strictTraceContinuation;
67+
private @Nullable String orgId;
68+
6669
private @Nullable SentryOptions.Cron cron;
6770

6871
@SuppressWarnings("unchecked")
@@ -213,6 +216,10 @@ public final class ExternalOptions {
213216
options.setCron(cron);
214217
}
215218

219+
options.setStrictTraceContinuation(
220+
propertiesProvider.getBooleanProperty("strict-trace-continuation"));
221+
options.setOrgId(propertiesProvider.getProperty("org-id"));
222+
216223
options.setEnableSpotlight(propertiesProvider.getBooleanProperty("enable-spotlight"));
217224
options.setSpotlightConnectionUrl(propertiesProvider.getProperty("spotlight-connection-url"));
218225
options.setProfileSessionSampleRate(
@@ -589,6 +596,22 @@ public void setProfilingTracesDirPath(@Nullable String profilingTracesDirPath) {
589596
this.profilingTracesDirPath = profilingTracesDirPath;
590597
}
591598

599+
public @Nullable Boolean isStrictTraceContinuation() {
600+
return strictTraceContinuation;
601+
}
602+
603+
public void setStrictTraceContinuation(final @Nullable Boolean strictTraceContinuation) {
604+
this.strictTraceContinuation = strictTraceContinuation;
605+
}
606+
607+
public @Nullable String getOrgId() {
608+
return orgId;
609+
}
610+
611+
public void setOrgId(final @Nullable String orgId) {
612+
this.orgId = orgId;
613+
}
614+
592615
public @Nullable ProfileLifecycle getProfileLifecycle() {
593616
return profileLifecycle;
594617
}

sentry/src/main/java/io/sentry/PropagationContext.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,33 @@ public static PropagationContext fromHeaders(
2323
final @NotNull ILogger logger,
2424
final @Nullable String sentryTraceHeaderString,
2525
final @Nullable List<String> baggageHeaderStrings) {
26+
@Nullable SentryOptions options = null;
27+
try {
28+
options = Sentry.getCurrentScopes().getOptions();
29+
} catch (Throwable ignored) {
30+
// options may not be available if Sentry is not initialized
31+
}
32+
return fromHeaders(logger, sentryTraceHeaderString, baggageHeaderStrings, options);
33+
}
34+
35+
public static @NotNull PropagationContext fromHeaders(
36+
final @NotNull ILogger logger,
37+
final @Nullable String sentryTraceHeaderString,
38+
final @Nullable List<String> baggageHeaderStrings,
39+
final @Nullable SentryOptions options) {
2640
if (sentryTraceHeaderString == null) {
2741
return new PropagationContext();
2842
}
2943

3044
try {
3145
final @NotNull SentryTraceHeader traceHeader = new SentryTraceHeader(sentryTraceHeaderString);
3246
final @NotNull Baggage baggage = Baggage.fromHeader(baggageHeaderStrings, logger);
47+
48+
if (options != null && !shouldContinueTrace(options, baggage)) {
49+
logger.log(SentryLevel.DEBUG, "Not continuing trace due to org ID mismatch.");
50+
return new PropagationContext();
51+
}
52+
3353
return fromHeaders(traceHeader, baggage, null);
3454
} catch (InvalidSentryTraceHeaderException e) {
3555
logger.log(SentryLevel.DEBUG, e, "Failed to parse Sentry trace header: %s", e.getMessage());
@@ -149,4 +169,25 @@ public void setSampled(final @Nullable Boolean sampled) {
149169
// should never be null since we ensure it in ctor
150170
return sampleRand == null ? 0.0 : sampleRand;
151171
}
172+
173+
static boolean shouldContinueTrace(
174+
final @NotNull SentryOptions options, final @Nullable Baggage baggage) {
175+
final @Nullable String sdkOrgId = options.getEffectiveOrgId();
176+
final @Nullable String baggageOrgId = baggage != null ? baggage.getOrgId() : null;
177+
178+
// Mismatched org IDs always reject regardless of strict mode
179+
if (sdkOrgId != null && baggageOrgId != null && !sdkOrgId.equals(baggageOrgId)) {
180+
return false;
181+
}
182+
183+
// In strict mode, both must be present and match (unless both are missing)
184+
if (options.isStrictTraceContinuation()) {
185+
if (sdkOrgId == null && baggageOrgId == null) {
186+
return true;
187+
}
188+
return sdkOrgId != null && sdkOrgId.equals(baggageOrgId);
189+
}
190+
191+
return true;
192+
}
152193
}

sentry/src/main/java/io/sentry/Scopes.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,8 @@ public void reportFullyDisplayed() {
11351135
final @Nullable String sentryTrace, final @Nullable List<String> baggageHeaders) {
11361136
@NotNull
11371137
PropagationContext propagationContext =
1138-
PropagationContext.fromHeaders(getOptions().getLogger(), sentryTrace, baggageHeaders);
1138+
PropagationContext.fromHeaders(
1139+
getOptions().getLogger(), sentryTrace, baggageHeaders, getOptions());
11391140
configureScope(
11401141
(scope) -> {
11411142
scope.withPropagationContext(

0 commit comments

Comments
 (0)