Skip to content

Commit 0c8d0f3

Browse files
committed
Add support for w3c traceparent header
1 parent 7314dbe commit 0c8d0f3

7 files changed

Lines changed: 261 additions & 5 deletions

File tree

sentry/api/sentry.api

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3405,6 +3405,7 @@ public class io/sentry/SentryOptions {
34053405
public fun isGlobalHubMode ()Ljava/lang/Boolean;
34063406
public fun isPrintUncaughtStackTrace ()Z
34073407
public fun isProfilingEnabled ()Z
3408+
public fun isPropagateTraceparent ()Z
34083409
public fun isSendClientReports ()Z
34093410
public fun isSendDefaultPii ()Z
34103411
public fun isSendModules ()Z
@@ -3492,6 +3493,7 @@ public class io/sentry/SentryOptions {
34923493
public fun setProfilesSampler (Lio/sentry/SentryOptions$ProfilesSamplerCallback;)V
34933494
public fun setProfilingTracesHz (I)V
34943495
public fun setProguardUuid (Ljava/lang/String;)V
3496+
public fun setPropagateTraceparent (Z)V
34953497
public fun setProxy (Lio/sentry/SentryOptions$Proxy;)V
34963498
public fun setReadTimeoutMillis (I)V
34973499
public fun setRelease (Ljava/lang/String;)V
@@ -4322,6 +4324,16 @@ public final class io/sentry/UserFeedback$JsonKeys {
43224324
public fun <init> ()V
43234325
}
43244326

4327+
public final class io/sentry/W3CTraceparentHeader {
4328+
public static final field TRACEPARENT_HEADER Ljava/lang/String;
4329+
public fun <init> (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Ljava/lang/Boolean;)V
4330+
public fun getName ()Ljava/lang/String;
4331+
public fun getSpanId ()Lio/sentry/SpanId;
4332+
public fun getTraceId ()Lio/sentry/protocol/SentryId;
4333+
public fun getValue ()Ljava/lang/String;
4334+
public fun isSampled ()Ljava/lang/Boolean;
4335+
}
4336+
43254337
public final class io/sentry/backpressure/BackpressureMonitor : io/sentry/backpressure/IBackpressureMonitor, java/lang/Runnable {
43264338
public fun <init> (Lio/sentry/SentryOptions;Lio/sentry/IScopes;)V
43274339
public fun close ()V
@@ -7054,8 +7066,10 @@ public final class io/sentry/util/TracingUtils {
70547066

70557067
public final class io/sentry/util/TracingUtils$TracingHeaders {
70567068
public fun <init> (Lio/sentry/SentryTraceHeader;Lio/sentry/BaggageHeader;)V
7069+
public fun <init> (Lio/sentry/SentryTraceHeader;Lio/sentry/BaggageHeader;Lio/sentry/W3CTraceparentHeader;)V
70577070
public fun getBaggageHeader ()Lio/sentry/BaggageHeader;
70587071
public fun getSentryTraceHeader ()Lio/sentry/SentryTraceHeader;
7072+
public fun getW3cTraceparentHeader ()Lio/sentry/W3CTraceparentHeader;
70597073
}
70607074

70617075
public final class io/sentry/util/UUIDGenerator {

sentry/src/main/java/io/sentry/SentryOptions.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,9 @@ public class SentryOptions {
393393
private final @NotNull List<String> defaultTracePropagationTargets =
394394
Collections.singletonList(DEFAULT_PROPAGATION_TARGETS);
395395

396+
/** Whether to propagate W3C traceparent HTTP header. */
397+
private boolean propagateTraceparent = false;
398+
396399
/** Proguard UUID. */
397400
private @Nullable String proguardUuid;
398401

@@ -2110,6 +2113,24 @@ public void setTracePropagationTargets(final @Nullable List<String> tracePropaga
21102113
}
21112114
}
21122115

2116+
/**
2117+
* Returns whether W3C traceparent HTTP header propagation is enabled.
2118+
*
2119+
* @return true if enabled false otherwise
2120+
*/
2121+
public boolean isPropagateTraceparent() {
2122+
return propagateTraceparent;
2123+
}
2124+
2125+
/**
2126+
* Enables or disables W3C traceparent HTTP header propagation.
2127+
*
2128+
* @param propagateTraceparent true if enabled false otherwise
2129+
*/
2130+
public void setPropagateTraceparent(boolean propagateTraceparent) {
2131+
this.propagateTraceparent = propagateTraceparent;
2132+
}
2133+
21132134
/**
21142135
* Returns a Proguard UUID.
21152136
*
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.sentry;
2+
3+
import io.sentry.protocol.SentryId;
4+
import org.jetbrains.annotations.NotNull;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
/** Represents W3C traceparent HTTP header. */
8+
public final class W3CTraceparentHeader {
9+
public static final String TRACEPARENT_HEADER = "traceparent";
10+
11+
private final @NotNull SentryId traceId;
12+
private final @NotNull SpanId spanId;
13+
private final @Nullable Boolean sampled;
14+
15+
public W3CTraceparentHeader(
16+
final @NotNull SentryId traceId,
17+
final @NotNull SpanId spanId,
18+
final @Nullable Boolean sampled) {
19+
this.traceId = traceId;
20+
this.spanId = spanId;
21+
this.sampled = sampled;
22+
}
23+
24+
public @NotNull String getName() {
25+
return TRACEPARENT_HEADER;
26+
}
27+
28+
public @NotNull String getValue() {
29+
String sampledFlag = sampled != null && sampled ? "01" : "00";
30+
return String.format("00-%s-%s-%s", traceId, spanId, sampledFlag);
31+
}
32+
33+
public @NotNull SentryId getTraceId() {
34+
return traceId;
35+
}
36+
37+
public @NotNull SpanId getSpanId() {
38+
return spanId;
39+
}
40+
41+
public @Nullable Boolean isSampled() {
42+
return sampled;
43+
}
44+
}

sentry/src/main/java/io/sentry/util/TracingUtils.java

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import io.sentry.PropagationContext;
1111
import io.sentry.SentryOptions;
1212
import io.sentry.SentryTraceHeader;
13+
import io.sentry.SpanContext;
1314
import io.sentry.TracesSamplingDecision;
15+
import io.sentry.W3CTraceparentHeader;
1416
import java.util.List;
1517
import org.jetbrains.annotations.ApiStatus;
1618
import org.jetbrains.annotations.NotNull;
@@ -59,8 +61,18 @@ public static void setTrace(
5961
final @NotNull SentryOptions sentryOptions = scopes.getOptions();
6062

6163
if (span != null && !span.isNoOp()) {
62-
return new TracingHeaders(
63-
span.toSentryTrace(), span.toBaggageHeader(thirdPartyBaggageHeaders));
64+
final @NotNull SentryTraceHeader sentryTraceHeader = span.toSentryTrace();
65+
final @Nullable BaggageHeader baggageHeader = span.toBaggageHeader(thirdPartyBaggageHeaders);
66+
@Nullable W3CTraceparentHeader w3cTraceparentHeader = null;
67+
68+
if (sentryOptions.isPropagateTraceparent()) {
69+
final @NotNull SpanContext spanContext = span.getSpanContext();
70+
w3cTraceparentHeader =
71+
new W3CTraceparentHeader(
72+
spanContext.getTraceId(), spanContext.getSpanId(), sentryTraceHeader.isSampled());
73+
}
74+
75+
return new TracingHeaders(sentryTraceHeader, baggageHeader, w3cTraceparentHeader);
6476
} else {
6577
final @NotNull PropagationContextHolder returnValue = new PropagationContextHolder();
6678
scopes.configureScope(
@@ -74,12 +86,22 @@ public static void setTrace(
7486
final @NotNull BaggageHeader baggageHeader =
7587
BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders);
7688

77-
return new TracingHeaders(
89+
final @NotNull SentryTraceHeader sentryTraceHeader =
7890
new SentryTraceHeader(
7991
propagationContext.getTraceId(),
8092
propagationContext.getSpanId(),
81-
propagationContext.isSampled()),
82-
baggageHeader);
93+
propagationContext.isSampled());
94+
95+
@Nullable W3CTraceparentHeader w3cTraceparentHeader = null;
96+
if (sentryOptions.isPropagateTraceparent()) {
97+
w3cTraceparentHeader =
98+
new W3CTraceparentHeader(
99+
propagationContext.getTraceId(),
100+
propagationContext.getSpanId(),
101+
propagationContext.isSampled());
102+
}
103+
104+
return new TracingHeaders(sentryTraceHeader, baggageHeader, w3cTraceparentHeader);
83105
}
84106

85107
return null;
@@ -110,12 +132,23 @@ private static final class PropagationContextHolder {
110132
public static final class TracingHeaders {
111133
private final @NotNull SentryTraceHeader sentryTraceHeader;
112134
private final @Nullable BaggageHeader baggageHeader;
135+
private final @Nullable W3CTraceparentHeader w3cTraceparentHeader;
113136

114137
public TracingHeaders(
115138
final @NotNull SentryTraceHeader sentryTraceHeader,
116139
final @Nullable BaggageHeader baggageHeader) {
117140
this.sentryTraceHeader = sentryTraceHeader;
118141
this.baggageHeader = baggageHeader;
142+
this.w3cTraceparentHeader = null;
143+
}
144+
145+
public TracingHeaders(
146+
final @NotNull SentryTraceHeader sentryTraceHeader,
147+
final @Nullable BaggageHeader baggageHeader,
148+
final @Nullable W3CTraceparentHeader w3cTraceparentHeader) {
149+
this.sentryTraceHeader = sentryTraceHeader;
150+
this.baggageHeader = baggageHeader;
151+
this.w3cTraceparentHeader = w3cTraceparentHeader;
119152
}
120153

121154
public @NotNull SentryTraceHeader getSentryTraceHeader() {
@@ -125,6 +158,10 @@ public TracingHeaders(
125158
public @Nullable BaggageHeader getBaggageHeader() {
126159
return baggageHeader;
127160
}
161+
162+
public @Nullable W3CTraceparentHeader getW3cTraceparentHeader() {
163+
return w3cTraceparentHeader;
164+
}
128165
}
129166

130167
/** Checks if a transaction is to be ignored. */

sentry/src/test/java/io/sentry/SentryOptionsTest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,4 +858,17 @@ class SentryOptionsTest {
858858
options.deadlineTimeout = -1L
859859
assertEquals(-1L, options.deadlineTimeout)
860860
}
861+
862+
@Test
863+
fun `propagateTraceparent option defaults to false`() {
864+
val options = SentryOptions()
865+
assertFalse(options.isPropagateTraceparent)
866+
}
867+
868+
@Test
869+
fun `propagateTraceparent option can be changed`() {
870+
val options = SentryOptions()
871+
options.isPropagateTraceparent = true
872+
assertTrue(options.isPropagateTraceparent)
873+
}
861874
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.sentry
2+
3+
import io.sentry.protocol.SentryId
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertNull
7+
import kotlin.test.assertTrue
8+
9+
class W3CTraceparentHeaderTest {
10+
11+
@Test
12+
fun `creates header with sampled true`() {
13+
val traceId = SentryId("12345678123456781234567812345678")
14+
val spanId = SpanId("1234567812345678")
15+
val header = W3CTraceparentHeader(traceId, spanId, true)
16+
17+
assertEquals("traceparent", header.name)
18+
assertEquals("00-12345678123456781234567812345678-1234567812345678-01", header.value)
19+
assertEquals(traceId, header.traceId)
20+
assertEquals(spanId, header.spanId)
21+
assertTrue(header.isSampled() ?: false)
22+
}
23+
24+
@Test
25+
fun `creates header with sampled false`() {
26+
val traceId = SentryId("12345678123456781234567812345678")
27+
val spanId = SpanId("1234567812345678")
28+
val header = W3CTraceparentHeader(traceId, spanId, false)
29+
30+
assertEquals("traceparent", header.name)
31+
assertEquals("00-12345678123456781234567812345678-1234567812345678-00", header.value)
32+
assertEquals(traceId, header.traceId)
33+
assertEquals(spanId, header.spanId)
34+
assertEquals(false, header.isSampled())
35+
}
36+
37+
@Test
38+
fun `creates header with sampled null`() {
39+
val traceId = SentryId("12345678123456781234567812345678")
40+
val spanId = SpanId("1234567812345678")
41+
val header = W3CTraceparentHeader(traceId, spanId, null)
42+
43+
assertEquals("traceparent", header.name)
44+
assertEquals("00-12345678123456781234567812345678-1234567812345678-00", header.value)
45+
assertEquals(traceId, header.traceId)
46+
assertEquals(spanId, header.spanId)
47+
assertNull(header.isSampled())
48+
}
49+
50+
@Test
51+
fun `value follows W3C format`() {
52+
val traceId = SentryId("abcdefabcdefabcdabcdefabcdefabcd")
53+
val spanId = SpanId("abcdefabcdefabcd")
54+
val header = W3CTraceparentHeader(traceId, spanId, true)
55+
56+
val value = header.value
57+
val parts = value.split("-")
58+
59+
assertEquals(4, parts.size)
60+
assertEquals("00", parts[0]) // Version
61+
assertEquals("abcdefabcdefabcdabcdefabcdefabcd", parts[1]) // Trace ID (32 hex chars)
62+
assertEquals("abcdefabcdefabcd", parts[2]) // Span ID (16 hex chars)
63+
assertEquals("01", parts[3]) // Sampled flag
64+
}
65+
}

sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,4 +443,66 @@ class TracingUtilsTest {
443443
assertTrue(sampleRand < 1.0)
444444
assertTrue(sampleRand >= 0.9999)
445445
}
446+
447+
@Test
448+
fun `trace does not return w3c traceparent header when propagateTraceparent is disabled`() {
449+
val fixture = Fixture()
450+
fixture.setup()
451+
fixture.options.isPropagateTraceparent = false
452+
453+
val tracingHeaders = TracingUtils.trace(fixture.scopes, null, fixture.span)
454+
455+
assertNotNull(tracingHeaders)
456+
assertNotNull(tracingHeaders.sentryTraceHeader)
457+
assertNull(tracingHeaders.w3cTraceparentHeader)
458+
}
459+
460+
@Test
461+
fun `trace returns w3c traceparent header when propagateTraceparent is enabled`() {
462+
val fixture = Fixture()
463+
fixture.setup()
464+
fixture.options.isPropagateTraceparent = true
465+
466+
val tracingHeaders = TracingUtils.trace(fixture.scopes, null, fixture.span)
467+
468+
assertNotNull(tracingHeaders)
469+
assertNotNull(tracingHeaders.sentryTraceHeader)
470+
assertNotNull(tracingHeaders.w3cTraceparentHeader)
471+
assertEquals("traceparent", tracingHeaders.w3cTraceparentHeader!!.name)
472+
assertEquals(fixture.span.spanContext.traceId, tracingHeaders.w3cTraceparentHeader!!.traceId)
473+
assertEquals(fixture.span.spanContext.spanId, tracingHeaders.w3cTraceparentHeader!!.spanId)
474+
}
475+
476+
@Test
477+
fun `trace returns w3c traceparent header with correct sampling info`() {
478+
val fixture = Fixture()
479+
fixture.setup()
480+
fixture.options.isPropagateTraceparent = true
481+
482+
val tracingHeaders = TracingUtils.trace(fixture.scopes, null, fixture.span)
483+
484+
assertNotNull(tracingHeaders)
485+
val w3cHeader = tracingHeaders.w3cTraceparentHeader!!
486+
assertEquals(fixture.span.toSentryTrace().isSampled(), w3cHeader.isSampled())
487+
}
488+
489+
@Test
490+
fun `trace returns w3c traceparent header when no span provided and propagateTraceparent is enabled`() {
491+
val fixture = Fixture()
492+
fixture.setup()
493+
fixture.options.isPropagateTraceparent = true
494+
495+
val tracingHeaders = TracingUtils.trace(fixture.scopes, null, null)
496+
497+
assertNotNull(tracingHeaders)
498+
assertNotNull(tracingHeaders.sentryTraceHeader)
499+
assertNotNull(tracingHeaders.w3cTraceparentHeader)
500+
501+
val sentryTrace = tracingHeaders.sentryTraceHeader
502+
val w3cTrace = tracingHeaders.w3cTraceparentHeader!!
503+
504+
assertEquals(sentryTrace.traceId, w3cTrace.traceId)
505+
assertEquals(sentryTrace.spanId, w3cTrace.spanId)
506+
assertEquals(sentryTrace.isSampled(), w3cTrace.isSampled())
507+
}
446508
}

0 commit comments

Comments
 (0)