-
-
Notifications
You must be signed in to change notification settings - Fork 466
feat: Add strict trace continuation support #5136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,15 +2,20 @@ | |
|
|
||
| import io.sentry.util.Objects; | ||
| import java.net.URI; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
|
|
||
| final class Dsn { | ||
| private static final @NotNull Pattern ORG_ID_PATTERN = Pattern.compile("^o(\\d+)\\."); | ||
|
|
||
| private final @NotNull String projectId; | ||
| private final @Nullable String path; | ||
| private final @Nullable String secretKey; | ||
| private final @NotNull String publicKey; | ||
| private final @NotNull URI sentryUri; | ||
| private @Nullable String orgId; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
|
|
||
| /* | ||
| / The project ID which the authenticated user is bound to. | ||
|
|
@@ -84,8 +89,25 @@ URI getSentryUri() { | |
| sentryUri = | ||
| new URI( | ||
| scheme, null, uri.getHost(), uri.getPort(), path + "api/" + projectId, null, null); | ||
|
|
||
| // Extract org ID from host (e.g., "o123.ingest.sentry.io" -> "123") | ||
| final String host = uri.getHost(); | ||
| if (host != null) { | ||
| final Matcher matcher = ORG_ID_PATTERN.matcher(host); | ||
| if (matcher.find()) { | ||
| orgId = matcher.group(1); | ||
| } | ||
| } | ||
| } catch (Throwable e) { | ||
| throw new IllegalArgumentException(e); | ||
| } | ||
| } | ||
|
|
||
| public @Nullable String getOrgId() { | ||
| return orgId; | ||
| } | ||
|
|
||
| void setOrgId(final @Nullable String orgId) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No setter needed here. |
||
| this.orgId = orgId; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,13 +23,27 @@ public static PropagationContext fromHeaders( | |
| final @NotNull ILogger logger, | ||
| final @Nullable String sentryTraceHeaderString, | ||
| final @Nullable List<String> baggageHeaderStrings) { | ||
| return fromHeaders(logger, sentryTraceHeaderString, baggageHeaderStrings, null); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could use |
||
| } | ||
|
|
||
| public static @NotNull PropagationContext fromHeaders( | ||
| final @NotNull ILogger logger, | ||
| final @Nullable String sentryTraceHeaderString, | ||
| final @Nullable List<String> baggageHeaderStrings, | ||
| final @Nullable SentryOptions options) { | ||
| if (sentryTraceHeaderString == null) { | ||
| return new PropagationContext(); | ||
| } | ||
|
|
||
| try { | ||
| final @NotNull SentryTraceHeader traceHeader = new SentryTraceHeader(sentryTraceHeaderString); | ||
| final @NotNull Baggage baggage = Baggage.fromHeader(baggageHeaderStrings, logger); | ||
|
|
||
| if (options != null && !shouldContinueTrace(options, baggage)) { | ||
| logger.log(SentryLevel.DEBUG, "Not continuing trace due to org ID mismatch."); | ||
| return new PropagationContext(); | ||
| } | ||
|
|
||
| return fromHeaders(traceHeader, baggage, null); | ||
| } catch (InvalidSentryTraceHeaderException e) { | ||
| logger.log(SentryLevel.DEBUG, e, "Failed to parse Sentry trace header: %s", e.getMessage()); | ||
|
|
@@ -149,4 +163,25 @@ public void setSampled(final @Nullable Boolean sampled) { | |
| // should never be null since we ensure it in ctor | ||
| return sampleRand == null ? 0.0 : sampleRand; | ||
| } | ||
|
|
||
| static boolean shouldContinueTrace( | ||
| final @NotNull SentryOptions options, final @Nullable Baggage baggage) { | ||
| final @Nullable String sdkOrgId = options.getEffectiveOrgId(); | ||
| final @Nullable String baggageOrgId = baggage != null ? baggage.getOrgId() : null; | ||
|
|
||
| // Mismatched org IDs always reject regardless of strict mode | ||
| if (sdkOrgId != null && baggageOrgId != null && !sdkOrgId.equals(baggageOrgId)) { | ||
| return false; | ||
| } | ||
|
|
||
| // In strict mode, both must be present and match (unless both are missing) | ||
| if (options.isStrictTraceContinuation()) { | ||
| if (sdkOrgId == null && baggageOrgId == null) { | ||
| return true; | ||
| } | ||
| return sdkOrgId != null && sdkOrgId.equals(baggageOrgId); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -427,6 +427,21 @@ public class SentryOptions { | |
| /** Whether to propagate W3C traceparent HTTP header. */ | ||
| private boolean propagateTraceparent = false; | ||
|
|
||
| /** | ||
| * Controls whether the SDK requires matching org IDs from incoming baggage to continue a trace. | ||
| * When true, both the SDK's org ID and the incoming baggage org ID must be present and match. | ||
| * When false, a mismatch between present org IDs will still start a new trace, but missing org | ||
| * IDs on either side are tolerated. | ||
| */ | ||
| private boolean strictTraceContinuation = false; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add both of these options to |
||
|
|
||
| /** | ||
| * An optional organization ID. The SDK will try to extract it from the DSN in most cases but you | ||
| * can provide it explicitly for self-hosted and Relay setups. This value is used for trace | ||
| * propagation and for features like {@link #strictTraceContinuation}. | ||
| */ | ||
| private @Nullable String orgId; | ||
|
|
||
| /** Proguard UUID. */ | ||
| private @Nullable String proguardUuid; | ||
|
|
||
|
|
@@ -2287,6 +2302,37 @@ public void setPropagateTraceparent(final boolean propagateTraceparent) { | |
| this.propagateTraceparent = propagateTraceparent; | ||
| } | ||
|
|
||
| public boolean isStrictTraceContinuation() { | ||
| return strictTraceContinuation; | ||
| } | ||
|
|
||
| public void setStrictTraceContinuation(final boolean strictTraceContinuation) { | ||
| this.strictTraceContinuation = strictTraceContinuation; | ||
| } | ||
|
|
||
| public @Nullable String getOrgId() { | ||
| return orgId; | ||
| } | ||
|
|
||
| public void setOrgId(final @Nullable String orgId) { | ||
| this.orgId = orgId; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the effective org ID, preferring the explicit config option over the DSN-parsed value. | ||
| */ | ||
| public @Nullable String getEffectiveOrgId() { | ||
| if (orgId != null) { | ||
| return orgId; | ||
| } | ||
| try { | ||
| final @Nullable String dsnOrgId = retrieveParsedDsn().getOrgId(); | ||
| return dsnOrgId; | ||
| } catch (Throwable e) { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns a Proguard UUID. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add some description here for customers to explain what this means for them.
We should also mention the new options.