From 363bc4157d7bca373fb1089eede89b5ffa02517f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 29 Jan 2025 10:32:15 +0100 Subject: [PATCH 01/29] wip --- .../opentelemetry/OtelSentryPropagator.java | 4 +++ .../OtelSentrySpanProcessor.java | 10 +++++- .../sentry/opentelemetry/SentrySampler.java | 5 +-- .../spring/boot/jakarta/ApiService.java | 23 ++++++++++++ .../spring/boot/jakarta/PersonController.java | 5 ++- sentry/api/sentry.api | 10 +++++- sentry/src/main/java/io/sentry/Baggage.java | 33 +++++++++++++++++ .../java/io/sentry/PropagationContext.java | 35 ++++++++++++++++--- .../main/java/io/sentry/SamplingContext.java | 21 +++++++++++ sentry/src/main/java/io/sentry/Scopes.java | 5 ++- sentry/src/main/java/io/sentry/Sentry.java | 4 ++- .../src/main/java/io/sentry/SentryTracer.java | 11 +++++- .../main/java/io/sentry/TracesSampler.java | 21 ++++------- .../java/io/sentry/util/TracingUtils.java | 3 ++ sentry/src/test/java/io/sentry/ScopeTest.kt | 2 +- 15 files changed, 164 insertions(+), 28 deletions(-) create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index c5802df2454..580b1e99c33 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -78,6 +78,8 @@ public void inject(final Context context, final C carrier, final TextMapSett final @Nullable BaggageHeader baggageHeader = sentrySpan.toBaggageHeader(Collections.emptyList()); if (baggageHeader != null) { + System.out.println("outgoing baggage:"); + System.out.println(baggageHeader.getValue()); setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); } } @@ -101,6 +103,8 @@ public Context extract( SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(sentryTraceString); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); + System.out.println("incoming baggage:"); + System.out.println(baggageString); final Baggage baggage = Baggage.fromHeader(baggageString); final @NotNull TraceState traceState = TraceState.getDefault(); diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index 521bc9020c0..90e430db6fc 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -82,10 +82,18 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri } final @Nullable Boolean sampled = isSampled(otelSpan, samplingDecision); + // TODO do not access isolation scope directly + final @Nullable Double sampleRand = + scopes.getIsolationScope().getPropagationContext().getSampleRand(); final @NotNull PropagationContext propagationContext = new PropagationContext( - new SentryId(traceId), sentrySpanId, sentryParentSpanId, baggage, sampled); + new SentryId(traceId), + sentrySpanId, + sentryParentSpanId, + baggage, + sampled, + sampleRand); updatePropagationContext(scopes, propagationContext); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java index 6f35fcb9c51..06c5f141ab5 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java @@ -86,7 +86,7 @@ public SamplingResult shouldSample( SpanId randomSpanId = new SpanId(); final @NotNull PropagationContext propagationContext = sentryTraceHeader == null - ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null) + ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null, null) : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId); final @NotNull TransactionContext transactionContext = @@ -95,7 +95,8 @@ public SamplingResult shouldSample( scopes .getOptions() .getInternalTracesSampler() - .sample(new SamplingContext(transactionContext, null)); + .sample( + new SamplingContext(transactionContext, null, propagationContext.getSampleRand())); if (!sentryDecision.getSampled()) { scopes diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java new file mode 100644 index 00000000000..c895b8b979c --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java @@ -0,0 +1,23 @@ +package io.sentry.samples.spring.boot.jakarta; + +import io.sentry.spring.jakarta.tracing.SentrySpan; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class ApiService { + + private final RestClient restClient; + + public ApiService(RestClient restClient) { + this.restClient = restClient; + } + + @SentrySpan("annotation-span") + void apiRequest(final @NotNull String name) { + // restClient.get().uri("http://localhost:8000?q={name}", + // name).retrieve().body(String.class); + restClient.get().uri("http://localhost:8081/articles").retrieve().body(String.class); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java index 5ce11d0a528..fe79122f5ff 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java @@ -20,11 +20,13 @@ public class PersonController { private final PersonService personService; private final Tracer tracer; + private final ApiService apiService; private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); - public PersonController(PersonService personService, Tracer tracer) { + public PersonController(PersonService personService, Tracer tracer, ApiService apiService) { this.personService = personService; this.tracer = tracer; + this.apiService = apiService; } @GetMapping("{id}") @@ -34,6 +36,7 @@ Person person(@PathVariable Long id) { ISpan currentSpan = Sentry.getSpan(); ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi"); try { + apiService.apiRequest(id.toString()); LOGGER.error("Trying person with id={}", id, new RuntimeException("error while loading")); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } finally { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 662d87c2650..55421bcad63 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -46,6 +46,8 @@ public final class io/sentry/Baggage { public fun getPublicKey ()Ljava/lang/String; public fun getRelease ()Ljava/lang/String; public fun getReplayId ()Ljava/lang/String; + public fun getSampleRand ()Ljava/lang/String; + public fun getSampleRandDouble ()Ljava/lang/Double; public fun getSampleRate ()Ljava/lang/String; public fun getSampleRateDouble ()Ljava/lang/Double; public fun getSampled ()Ljava/lang/String; @@ -60,6 +62,8 @@ public final class io/sentry/Baggage { public fun setPublicKey (Ljava/lang/String;)V public fun setRelease (Ljava/lang/String;)V public fun setReplayId (Ljava/lang/String;)V + public fun setSampleRand (Ljava/lang/String;)V + public fun setSampleRandDouble (Ljava/lang/Double;)V public fun setSampleRate (Ljava/lang/String;)V public fun setSampled (Ljava/lang/String;)V public fun setTraceId (Ljava/lang/String;)V @@ -78,6 +82,7 @@ public final class io/sentry/Baggage$DSCKeys { public static final field RELEASE Ljava/lang/String; public static final field REPLAY_ID Ljava/lang/String; public static final field SAMPLED Ljava/lang/String; + public static final field SAMPLE_RAND Ljava/lang/String; public static final field SAMPLE_RATE Ljava/lang/String; public static final field TRACE_ID Ljava/lang/String; public static final field TRANSACTION Ljava/lang/String; @@ -1941,12 +1946,13 @@ public final class io/sentry/ProfilingTransactionData$JsonKeys { public final class io/sentry/PropagationContext { public fun ()V public fun (Lio/sentry/PropagationContext;)V - public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V + public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;)V public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext; public fun getBaggage ()Lio/sentry/Baggage; public fun getParentSpanId ()Lio/sentry/SpanId; + public fun getSampleRand ()Ljava/lang/Double; public fun getSpanId ()Lio/sentry/SpanId; public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun isSampled ()Ljava/lang/Boolean; @@ -2007,7 +2013,9 @@ public final class io/sentry/RequestDetails { public final class io/sentry/SamplingContext { public fun (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;)V + public fun (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Ljava/lang/Double;)V public fun getCustomSamplingContext ()Lio/sentry/CustomSamplingContext; + public fun getSampleRand ()Ljava/lang/Double; public fun getTransactionContext ()Lio/sentry/TransactionContext; } diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 05a59d7053f..44243fd6220 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -335,6 +335,21 @@ public void setSampleRate(final @Nullable String sampleRate) { set(DSCKeys.SAMPLE_RATE, sampleRate); } + @ApiStatus.Internal + public @Nullable String getSampleRand() { + return get(DSCKeys.SAMPLE_RAND); + } + + @ApiStatus.Internal + public void setSampleRand(final @Nullable String sampleRand) { + set(DSCKeys.SAMPLE_RAND, sampleRand); + } + + @ApiStatus.Internal + public void setSampleRandDouble(final @Nullable Double sampleRand) { + setSampleRand(sampleRateToString(sampleRand)); + } + @ApiStatus.Internal public void setSampled(final @Nullable String sampled) { set(DSCKeys.SAMPLED, sampled); @@ -459,6 +474,22 @@ private static boolean isHighQualityTransactionName( return null; } + @ApiStatus.Internal + public @Nullable Double getSampleRandDouble() { + final String sampleRandString = getSampleRand(); + if (sampleRandString != null) { + try { + double sampleRand = Double.parseDouble(sampleRandString); + if (SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { + return sampleRand; + } + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + @ApiStatus.Internal @Nullable public TraceContext toTraceContext() { @@ -494,6 +525,7 @@ public static final class DSCKeys { public static final String ENVIRONMENT = "sentry-environment"; public static final String TRANSACTION = "sentry-transaction"; public static final String SAMPLE_RATE = "sentry-sample_rate"; + public static final String SAMPLE_RAND = "sentry-sample_rand"; public static final String SAMPLED = "sentry-sampled"; public static final String REPLAY_ID = "sentry-replay_id"; @@ -506,6 +538,7 @@ public static final class DSCKeys { ENVIRONMENT, TRANSACTION, SAMPLE_RATE, + SAMPLE_RAND, SAMPLED, REPLAY_ID); } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index b0debc2a9d1..703826488f0 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -2,6 +2,7 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; +import io.sentry.util.SentryRandom; import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -47,7 +48,8 @@ public static PropagationContext fromHeaders( spanIdToUse, sentryTraceHeader.getSpanId(), baggage, - sentryTraceHeader.isSampled()); + sentryTraceHeader.isSampled(), + null); } private @NotNull SentryId traceId; @@ -55,11 +57,12 @@ public static PropagationContext fromHeaders( private @Nullable SpanId parentSpanId; private @Nullable Boolean sampled; + private @NotNull Double sampleRand; private @Nullable Baggage baggage; public PropagationContext() { - this(new SentryId(), new SpanId(), null, null, null); + this(new SentryId(), new SpanId(), null, null, null, null); } public PropagationContext(final @NotNull PropagationContext propagationContext) { @@ -68,7 +71,8 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.getSpanId(), propagationContext.getParentSpanId(), cloneBaggage(propagationContext.getBaggage()), - propagationContext.isSampled()); + propagationContext.isSampled(), + propagationContext.getSampleRand()); } private static @Nullable Baggage cloneBaggage(final @Nullable Baggage baggage) { @@ -84,12 +88,31 @@ public PropagationContext( final @NotNull SpanId spanId, final @Nullable SpanId parentSpanId, final @Nullable Baggage baggage, - final @Nullable Boolean sampled) { + final @Nullable Boolean sampled, + final @Nullable Double sampleRand) { this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; this.baggage = baggage; this.sampled = sampled; + if (sampleRand != null) { + this.sampleRand = sampleRand; + } else if (baggage != null && baggage.getSampleRandDouble() != null) { + this.sampleRand = baggage.getSampleRandDouble(); + } else { + final @Nullable Double sampleRate = baggage == null ? null : baggage.getSampleRateDouble(); + final @NotNull Double sampleRandToUse = SentryRandom.current().nextDouble(); + + if (sampled != null && sampleRate != null) { + if (sampled) { + this.sampleRand = sampleRandToUse * sampleRate; + } else { + this.sampleRand = sampleRate + (sampleRandToUse * (1 - sampleRate)); + } + } else { + this.sampleRand = sampleRandToUse; + } + } } public @NotNull SentryId getTraceId() { @@ -145,4 +168,8 @@ public void setSampled(final @Nullable Boolean sampled) { spanContext.setOrigin("auto"); return spanContext; } + + public @NotNull Double getSampleRand() { + return sampleRand; + } } diff --git a/sentry/src/main/java/io/sentry/SamplingContext.java b/sentry/src/main/java/io/sentry/SamplingContext.java index 60944fbd433..711c03e21c5 100644 --- a/sentry/src/main/java/io/sentry/SamplingContext.java +++ b/sentry/src/main/java/io/sentry/SamplingContext.java @@ -1,6 +1,8 @@ package io.sentry; import io.sentry.util.Objects; +import io.sentry.util.SentryRandom; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,13 +13,28 @@ public final class SamplingContext { private final @NotNull TransactionContext transactionContext; private final @Nullable CustomSamplingContext customSamplingContext; + private final @NotNull Double sampleRand; + @Deprecated + @SuppressWarnings("InlineMeSuggester") + /** + * @deprecated creating a SamplingContext is something only the SDK should do + */ public SamplingContext( final @NotNull TransactionContext transactionContext, final @Nullable CustomSamplingContext customSamplingContext) { + this(transactionContext, customSamplingContext, SentryRandom.current().nextDouble()); + } + + @ApiStatus.Internal + public SamplingContext( + final @NotNull TransactionContext transactionContext, + final @Nullable CustomSamplingContext customSamplingContext, + final @NotNull Double sampleRand) { this.transactionContext = Objects.requireNonNull(transactionContext, "transactionContexts is required"); this.customSamplingContext = customSamplingContext; + this.sampleRand = sampleRand; } public @Nullable CustomSamplingContext getCustomSamplingContext() { @@ -27,4 +44,8 @@ public SamplingContext( public @NotNull TransactionContext getTransactionContext() { return transactionContext; } + + public @NotNull Double getSampleRand() { + return sampleRand; + } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 2b0d5103686..4b4f8f8a877 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -857,8 +857,11 @@ public void flush(long timeoutMillis) { SentryLevel.INFO, "Tracing is disabled and this 'startTransaction' returns a no-op."); transaction = NoOpTransaction.getInstance(); } else { + final @NotNull Double sampleRand = + getCombinedScopeView().getPropagationContext().getSampleRand(); final SamplingContext samplingContext = - new SamplingContext(transactionContext, transactionOptions.getCustomSamplingContext()); + new SamplingContext( + transactionContext, transactionOptions.getCustomSamplingContext(), sampleRand); final @NotNull TracesSampler tracesSampler = getOptions().getInternalTracesSampler(); @NotNull TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext); transactionContext.setSamplingDecision(samplingDecision); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 94007e42c56..bd5f296b7c2 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -22,6 +22,7 @@ import io.sentry.util.InitUtil; import io.sentry.util.LoadClass; import io.sentry.util.Platform; +import io.sentry.util.SentryRandom; import io.sentry.util.thread.IThreadChecker; import io.sentry.util.thread.NoOpThreadChecker; import io.sentry.util.thread.ThreadChecker; @@ -458,7 +459,8 @@ private static void handleAppStartProfilingConfig( final @NotNull SentryOptions options) { TransactionContext appStartTransactionContext = new TransactionContext("app.launch", "profile"); appStartTransactionContext.setForNextAppStart(true); - SamplingContext appStartSamplingContext = new SamplingContext(appStartTransactionContext, null); + SamplingContext appStartSamplingContext = + new SamplingContext(appStartTransactionContext, null, SentryRandom.current().nextDouble()); return options.getInternalTracesSampler().sample(appStartSamplingContext); } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 65901a2e1ab..ca4f4d67ee2 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -657,6 +657,7 @@ private void updateBaggageValues() { scope -> { replayId.set(scope.getReplayId()); }); + // TODO sampleRand? baggage.setValuesFromTransaction( getSpanContext().getTraceId(), replayId.get(), @@ -674,7 +675,15 @@ private void updateBaggageValues() { if (scopes.getOptions().isTraceSampling()) { updateBaggageValues(); - return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + BaggageHeader baggageHeader = + BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + if (baggageHeader != null) { + System.out.println(baggageHeader.getName()); + System.out.println(baggageHeader.getValue()); + } else { + System.out.println("baggage header null in SentryTracer"); + } + return baggageHeader; } else { return null; } diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 3ce28ab7451..1d1ad6470ac 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -2,7 +2,6 @@ import io.sentry.util.Objects; import io.sentry.util.Random; -import io.sentry.util.SentryRandom; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,7 +10,6 @@ @ApiStatus.Internal public final class TracesSampler { private final @NotNull SentryOptions options; - private final @Nullable Random random; public TracesSampler(final @NotNull SentryOptions options) { this(Objects.requireNonNull(options, "options are required"), null); @@ -20,12 +18,12 @@ public TracesSampler(final @NotNull SentryOptions options) { @TestOnly TracesSampler(final @NotNull SentryOptions options, final @Nullable Random random) { this.options = options; - this.random = random; } @SuppressWarnings("deprecation") @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { + final @NotNull Double sampleRand = samplingContext.getSampleRand(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { @@ -45,7 +43,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont if (profilesSampleRate == null) { profilesSampleRate = options.getProfilesSampleRate(); } - Boolean profilesSampled = profilesSampleRate != null && sample(profilesSampleRate); + Boolean profilesSampled = profilesSampleRate != null && sample(profilesSampleRate, sampleRand); if (options.getTracesSampler() != null) { Double samplerResult = null; @@ -58,7 +56,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont } if (samplerResult != null) { return new TracesSamplingDecision( - sample(samplerResult), samplerResult, profilesSampled, profilesSampleRate); + sample(samplerResult, sampleRand), samplerResult, profilesSampled, profilesSampleRate); } } @@ -76,7 +74,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont if (downsampledTracesSampleRate != null) { return new TracesSamplingDecision( - sample(downsampledTracesSampleRate), + sample(downsampledTracesSampleRate, sampleRand), downsampledTracesSampleRate, profilesSampled, profilesSampleRate); @@ -85,14 +83,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont return new TracesSamplingDecision(false, null, false, null); } - private boolean sample(final @NotNull Double aDouble) { - return !(aDouble < getRandom().nextDouble()); - } - - private Random getRandom() { - if (random == null) { - return SentryRandom.current(); - } - return random; + private boolean sample(final @NotNull Double sampleRate, final @NotNull Double sampleRand) { + return !(sampleRate < sampleRand); } } diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index 16655be634c..be7a7355d37 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -85,6 +85,9 @@ public static void startNewTrace(final @NotNull IScopes scopes) { baggage = new Baggage(sentryOptions.getLogger()); propagationContext.setBaggage(baggage); } + if (baggage.getSampleRand() != null) { + baggage.setSampleRandDouble(propagationContext.getSampleRand()); + } if (baggage.isMutable()) { baggage.setValuesFromScope(scope, sentryOptions); baggage.freeze(); diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index b8025735e8a..0d13f0d21ae 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -828,7 +828,7 @@ class ScopeTest { } val scope = Scope(options) - scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null) + scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null, null) verify(observer).setTrace( argThat { traceId.toString() == "64cf554cc8d74c6eafa3e08b7c984f6d" }, eq(scope) From 56060a4306de261db96f12073fdcaf29ce9f96a0 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 6 Feb 2025 06:12:42 +0100 Subject: [PATCH 02/29] wip2 --- .../api/sentry-opentelemetry-bootstrap.api | 1 + .../InternalSemanticAttributes.java | 2 + .../sentry/opentelemetry/OtelSpanFactory.java | 2 + .../opentelemetry/OtelSamplingUtil.java | 7 +- .../opentelemetry/OtelSentryPropagator.java | 14 ++- .../OtelSentrySpanProcessor.java | 14 +-- .../sentry/opentelemetry/SentrySampler.java | 21 +++- .../opentelemetry/SentrySamplingResult.java | 1 + .../boot/jakarta/SentryDemoApplication.java | 65 +++++----- sentry/api/sentry.api | 7 +- sentry/src/main/java/io/sentry/Baggage.java | 114 +++++++++++++++++- .../src/main/java/io/sentry/OutboxSender.java | 8 ++ .../java/io/sentry/PropagationContext.java | 54 +++++---- .../src/main/java/io/sentry/SentryTracer.java | 2 +- .../src/main/java/io/sentry/TraceContext.java | 41 ++++++- .../main/java/io/sentry/TracesSampler.java | 8 +- .../io/sentry/TracesSamplingDecision.java | 24 +++- .../java/io/sentry/TransactionContext.java | 33 ++--- .../java/io/sentry/util/SampleRateUtils.java | 20 +++ .../java/io/sentry/util/TracingUtils.java | 17 +-- 20 files changed, 343 insertions(+), 112 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api index df7db47c652..adb976adc05 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api @@ -20,6 +20,7 @@ public final class io/sentry/opentelemetry/InternalSemanticAttributes { public static final field PROFILE_SAMPLED Lio/opentelemetry/api/common/AttributeKey; public static final field PROFILE_SAMPLE_RATE Lio/opentelemetry/api/common/AttributeKey; public static final field SAMPLED Lio/opentelemetry/api/common/AttributeKey; + public static final field SAMPLE_RAND Lio/opentelemetry/api/common/AttributeKey; public static final field SAMPLE_RATE Lio/opentelemetry/api/common/AttributeKey; public fun ()V } diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java index cb64d7bfffd..4795401266f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java @@ -8,6 +8,8 @@ public final class InternalSemanticAttributes { public static final AttributeKey SAMPLED = AttributeKey.booleanKey("sentry.sampled"); public static final AttributeKey SAMPLE_RATE = AttributeKey.doubleKey("sentry.sample_rate"); + public static final AttributeKey SAMPLE_RAND = + AttributeKey.doubleKey("sentry.sample_rand"); public static final AttributeKey PARENT_SAMPLED = AttributeKey.booleanKey("sentry.parent_sampled"); public static final AttributeKey PROFILE_SAMPLED = diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java index 547463dcdca..7a51c3f337d 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java @@ -136,6 +136,8 @@ public OtelSpanFactory() { spanBuilder.setAttribute(InternalSemanticAttributes.SAMPLED, samplingDecision.getSampled()); spanBuilder.setAttribute( InternalSemanticAttributes.SAMPLE_RATE, samplingDecision.getSampleRate()); + spanBuilder.setAttribute( + InternalSemanticAttributes.SAMPLE_RAND, samplingDecision.getSampleRand()); spanBuilder.setAttribute( InternalSemanticAttributes.PROFILE_SAMPLED, samplingDecision.getProfileSampled()); spanBuilder.setAttribute( diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java index 01f414f9022..324f06b5399 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java @@ -14,13 +14,18 @@ public final class OtelSamplingUtil { final @Nullable Boolean sampled = attributes.get(InternalSemanticAttributes.SAMPLED); if (sampled != null) { final @Nullable Double sampleRate = attributes.get(InternalSemanticAttributes.SAMPLE_RATE); + final @Nullable Double sampleRand = attributes.get(InternalSemanticAttributes.SAMPLE_RAND); final @Nullable Boolean profileSampled = attributes.get(InternalSemanticAttributes.PROFILE_SAMPLED); final @Nullable Double profileSampleRate = attributes.get(InternalSemanticAttributes.PROFILE_SAMPLE_RATE); return new TracesSamplingDecision( - sampled, sampleRate, profileSampled == null ? false : profileSampled, profileSampleRate); + sampled, + sampleRate, + sampleRand, + profileSampled == null ? false : profileSampled, + profileSampleRate); } else { return null; } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index 580b1e99c33..ddcead442d7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -19,6 +19,8 @@ import io.sentry.SentryLevel; import io.sentry.SentryTraceHeader; import io.sentry.exception.InvalidSentryTraceHeaderException; +import io.sentry.util.TracingUtils; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -73,12 +75,15 @@ public void inject(final Context context, final C carrier, final TextMapSett return; } + // TODO can we use traceIfAllowed? do we have the URL here? + TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); + final @NotNull SentryTraceHeader sentryTraceHeader = sentrySpan.toSentryTrace(); setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); final @Nullable BaggageHeader baggageHeader = sentrySpan.toBaggageHeader(Collections.emptyList()); if (baggageHeader != null) { - System.out.println("outgoing baggage:"); + System.out.println("outgoing baggage: "); System.out.println(baggageHeader.getValue()); setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); } @@ -129,10 +134,9 @@ public Context extract( .getLogger() .log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId()); - final @NotNull PropagationContext propagationContext = - PropagationContext.fromHeaders( - scopes.getOptions().getLogger(), sentryTraceString, baggageString); - scopesToUse.getIsolationScope().setPropagationContext(propagationContext); +// final @NotNull PropagationContext propagationContext = +// PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); +// scopesToUse.getIsolationScope().setPropagationContext(propagationContext); return modifiedContext; } catch (InvalidSentryTraceHeaderException e) { diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index 90e430db6fc..ef329414d6b 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -70,21 +70,19 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri baggage = baggageFromContext; } - final @Nullable Boolean baggageMutable = - otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE_MUTABLE); +// final @Nullable Boolean baggageMutable = +// otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE_MUTABLE); final @Nullable String baggageString = otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE); if (baggageString != null) { baggage = Baggage.fromHeader(baggageString); - if (baggageMutable == true) { - baggage.freeze(); - } +// if (baggageMutable == false) { // TODO was this a bug? +// baggage.freeze(); +// } } final @Nullable Boolean sampled = isSampled(otelSpan, samplingDecision); - // TODO do not access isolation scope directly - final @Nullable Double sampleRand = - scopes.getIsolationScope().getPropagationContext().getSampleRand(); + final @Nullable Double sampleRand = samplingDecision == null ? null : samplingDecision.getSampleRand(); final @NotNull PropagationContext propagationContext = new PropagationContext( diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java index 06c5f141ab5..f83b69f5571 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java @@ -23,6 +23,7 @@ import io.sentry.TransactionContext; import io.sentry.clientreport.DiscardReason; import io.sentry.protocol.SentryId; +import io.sentry.util.SampleRateUtils; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -82,12 +83,25 @@ public SamplingResult shouldSample( baggage = baggageFromContext; } + final @Nullable Boolean sampledFromHeaders = + sentryTraceHeader == null ? null : sentryTraceHeader.isSampled(); + final @Nullable Double sampleRandFromHeaders = + baggage == null ? null : baggage.getSampleRandDouble(); + final @Nullable Double sampleRateFromHeaders = + baggage == null ? null : baggage.getSampleRateDouble(); + final @NotNull Double sampleRand = + SampleRateUtils.backfilledSampleRand( + sampleRandFromHeaders, sampleRateFromHeaders, sampledFromHeaders); + // there's no way to get the span id here, so we just use a random id for sampling SpanId randomSpanId = new SpanId(); + final @Nullable Baggage baggageToUse = baggage == null ? null : baggage.toReadOnly(); + // TODO this freezes baggage but should not final @NotNull PropagationContext propagationContext = sentryTraceHeader == null - ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null, null) - : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId); + ? new PropagationContext( + new SentryId(traceId), randomSpanId, null, baggageToUse, null, sampleRand) + : PropagationContext.fromHeaders(sentryTraceHeader, baggageToUse, randomSpanId); final @NotNull TransactionContext transactionContext = TransactionContext.fromPropagationContext(propagationContext); @@ -95,8 +109,7 @@ public SamplingResult shouldSample( scopes .getOptions() .getInternalTracesSampler() - .sample( - new SamplingContext(transactionContext, null, propagationContext.getSampleRand())); + .sample(new SamplingContext(transactionContext, null, sampleRand)); if (!sentryDecision.getSampled()) { scopes diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java index 69acf521346..e29601faf5f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java @@ -29,6 +29,7 @@ public Attributes getAttributes() { return Attributes.builder() .put(InternalSemanticAttributes.SAMPLED, sentryDecision.getSampled()) .put(InternalSemanticAttributes.SAMPLE_RATE, sentryDecision.getSampleRate()) + .put(InternalSemanticAttributes.SAMPLE_RAND, sentryDecision.getSampleRand()) .put(InternalSemanticAttributes.PROFILE_SAMPLED, sentryDecision.getProfileSampled()) .put(InternalSemanticAttributes.PROFILE_SAMPLE_RATE, sentryDecision.getProfileSampleRate()) .build(); diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java index a6eb46f4c74..4c61bf79278 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java @@ -1,21 +1,12 @@ package io.sentry.samples.spring.boot.jakarta; -import static io.sentry.quartz.SentryJobListener.SENTRY_SLUG_KEY; - import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Tracer; -import io.sentry.samples.spring.boot.jakarta.quartz.SampleJob; -import java.util.Collections; -import org.quartz.JobDetail; -import org.quartz.SimpleTrigger; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.quartz.CronTriggerFactoryBean; -import org.springframework.scheduling.quartz.JobDetailFactoryBean; -import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; @@ -42,34 +33,34 @@ RestClient restClient(RestClient.Builder builder) { return builder.build(); } - @Bean - public JobDetailFactoryBean jobDetail() { - JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); - jobDetailFactory.setJobClass(SampleJob.class); - jobDetailFactory.setDurability(true); - jobDetailFactory.setJobDataAsMap( - Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); - return jobDetailFactory; - } - - @Bean - public SimpleTriggerFactoryBean trigger(JobDetail job) { - SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); - trigger.setJobDetail(job); - trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes - trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); - trigger.setJobDataAsMap( - Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); - return trigger; - } - - @Bean - public CronTriggerFactoryBean cronTrigger(JobDetail job) { - CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); - trigger.setJobDetail(job); - trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes - return trigger; - } + // @Bean + // public JobDetailFactoryBean jobDetail() { + // JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); + // jobDetailFactory.setJobClass(SampleJob.class); + // jobDetailFactory.setDurability(true); + // jobDetailFactory.setJobDataAsMap( + // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); + // return jobDetailFactory; + // } + // + // @Bean + // public SimpleTriggerFactoryBean trigger(JobDetail job) { + // SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); + // trigger.setJobDetail(job); + // trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes + // trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); + // trigger.setJobDataAsMap( + // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); + // return trigger; + // } + // + // @Bean + // public CronTriggerFactoryBean cronTrigger(JobDetail job) { + // CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); + // trigger.setJobDetail(job); + // trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes + // return trigger; + // } @Bean public Tracer tracer() { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 55421bcad63..3ab35b9dd5f 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1956,7 +1956,6 @@ public final class io/sentry/PropagationContext { public fun getSpanId ()Lio/sentry/SpanId; public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun isSampled ()Ljava/lang/Boolean; - public fun setBaggage (Lio/sentry/Baggage;)V public fun setParentSpanId (Lio/sentry/SpanId;)V public fun setSampled (Ljava/lang/Boolean;)V public fun setSpanId (Lio/sentry/SpanId;)V @@ -3642,6 +3641,7 @@ public final class io/sentry/TraceContext : io/sentry/JsonSerializable, io/sentr public fun getPublicKey ()Ljava/lang/String; public fun getRelease ()Ljava/lang/String; public fun getReplayId ()Lio/sentry/protocol/SentryId; + public fun getSampleRand ()Ljava/lang/String; public fun getSampleRate ()Ljava/lang/String; public fun getSampled ()Ljava/lang/String; public fun getTraceId ()Lio/sentry/protocol/SentryId; @@ -3664,6 +3664,7 @@ public final class io/sentry/TraceContext$JsonKeys { public static final field RELEASE Ljava/lang/String; public static final field REPLAY_ID Ljava/lang/String; public static final field SAMPLED Ljava/lang/String; + public static final field SAMPLE_RAND Ljava/lang/String; public static final field SAMPLE_RATE Ljava/lang/String; public static final field TRACE_ID Ljava/lang/String; public static final field TRANSACTION Ljava/lang/String; @@ -3680,8 +3681,11 @@ public final class io/sentry/TracesSamplingDecision { public fun (Ljava/lang/Boolean;)V public fun (Ljava/lang/Boolean;Ljava/lang/Double;)V public fun (Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Boolean;Ljava/lang/Double;)V + public fun (Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;)V + public fun (Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Boolean;Ljava/lang/Double;)V public fun getProfileSampleRate ()Ljava/lang/Double; public fun getProfileSampled ()Ljava/lang/Boolean; + public fun getSampleRand ()Ljava/lang/Double; public fun getSampleRate ()Ljava/lang/Double; public fun getSampled ()Ljava/lang/Boolean; } @@ -6283,6 +6287,7 @@ public final class io/sentry/util/Random : java/io/Serializable { public final class io/sentry/util/SampleRateUtils { public fun ()V + public static fun backfilledSampleRand (Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Boolean;)Ljava/lang/Double; public static fun isValidProfilesSampleRate (Ljava/lang/Double;)Z public static fun isValidSampleRate (Ljava/lang/Double;)Z public static fun isValidTracesSampleRate (Ljava/lang/Double;)Z diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 44243fd6220..7fbf1f2c7dd 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -2,6 +2,8 @@ import static io.sentry.protocol.Contexts.REPLAY_ID; +import com.jakewharton.nopen.annotation.Open; + import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.SampleRateUtils; @@ -25,7 +27,8 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental -public final class Baggage { +@Open +public class Baggage { static final @NotNull String CHARSET = "UTF-8"; static final @NotNull Integer MAX_BAGGAGE_STRING_LENGTH = 8192; @@ -103,7 +106,7 @@ public static Baggage fromHeader( final String valueDecoded = decode(value); keyValues.put(keyDecoded, valueDecoded); - mutable = false; +// mutable = false; } catch (Throwable e) { logger.log( SentryLevel.ERROR, @@ -140,6 +143,7 @@ public static Baggage fromEvent( // we don't persist sample rate baggage.setSampleRate(null); baggage.setSampled(null); + baggage.setSampleRand(null); // TODO do we need this? final @Nullable Object replayId = event.getContexts().get(REPLAY_ID); if (replayId != null && !replayId.toString().equals(SentryId.EMPTY_ID.toString())) { baggage.setReplayId(replayId.toString()); @@ -161,6 +165,7 @@ public Baggage(final @NotNull Baggage baggage) { } @ApiStatus.Internal + @SuppressWarnings("ObjectToString") public Baggage( final @NotNull Map keyValues, final @Nullable String thirdPartyHeader, @@ -170,10 +175,13 @@ public Baggage( this.logger = logger; this.mutable = isMutable; this.thirdPartyHeader = thirdPartyHeader; + new RuntimeException("creating new baggage" + this).printStackTrace(); } + @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { + new RuntimeException("freezing baggage " + this).printStackTrace(); this.mutable = false; } @@ -187,6 +195,7 @@ public String getThirdPartyHeader() { return thirdPartyHeader; } + @SuppressWarnings("ObjectToString") public @NotNull String toHeaderString(@Nullable String thirdPartyBaggageHeaderString) { final StringBuilder sb = new StringBuilder(); String separator = ""; @@ -240,6 +249,8 @@ public String getThirdPartyHeader() { } } + sb.append(",sbg=" + this); + return sb.toString(); } @@ -490,6 +501,10 @@ private static boolean isHighQualityTransactionName( return null; } + public @NotNull Baggage toReadOnly() { + return new ReadOnlyBaggage(this); + } + @ApiStatus.Internal @Nullable public TraceContext toTraceContext() { @@ -508,7 +523,8 @@ public TraceContext toTraceContext() { getTransaction(), getSampleRate(), getSampled(), - replayIdString == null ? null : new SentryId(replayIdString)); + replayIdString == null ? null : new SentryId(replayIdString), + getSampleRand()); traceContext.setUnknown(getUnknown()); return traceContext; } else { @@ -542,4 +558,96 @@ public static final class DSCKeys { SAMPLED, REPLAY_ID); } + + private static final class ReadOnlyBaggage extends Baggage { + + public ReadOnlyBaggage(@NotNull Baggage baggage) { + super(baggage); + } + + @Override + public void freeze() { + // do nothing + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public void setTraceId(@Nullable String traceId) { + // do nothing + } + + @Override + public void setPublicKey(@Nullable String publicKey) { + // do nothing + } + + @Override + public void setEnvironment(@Nullable String environment) { + // do nothing + } + + @Override + public void setRelease(@Nullable String release) { + // do nothing + } + + @Override + public void setUserId(@Nullable String userId) { + // do nothing + } + + @Override + public void setTransaction(@Nullable String transaction) { + // do nothing + } + + @Override + public void setSampleRate(@Nullable String sampleRate) { + // do nothing + } + + @Override + public void setSampleRand(@Nullable String sampleRand) { + // do nothing + } + + @Override + public void setSampleRandDouble(@Nullable Double sampleRand) { + // do nothing + } + + @Override + public void setSampled(@Nullable String sampled) { + // do nothing + } + + @Override + public void setReplayId(@Nullable String replayId) { + // do nothing + } + + @Override + public void set(@NotNull String key, @Nullable String value) { + // do nothing + } + + @Override + public void setValuesFromTransaction(@NotNull SentryId traceId, @Nullable SentryId replayId, @NotNull SentryOptions sentryOptions, @Nullable TracesSamplingDecision samplingDecision, @Nullable String transactionName, @Nullable TransactionNameSource transactionNameSource) { + // do nothing + } + + @Override + public void setValuesFromScope(@NotNull IScope scope, @NotNull SentryOptions options) { + // do nothing + } + + @Override + public @NotNull Baggage toReadOnly() { + return this; + } + } } diff --git a/sentry/src/main/java/io/sentry/OutboxSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java index 4e223da03d2..f5c3813414d 100644 --- a/sentry/src/main/java/io/sentry/OutboxSender.java +++ b/sentry/src/main/java/io/sentry/OutboxSender.java @@ -244,6 +244,14 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN "Invalid sample rate parsed from TraceContext: %s", sampleRateString); } else { + final @Nullable String sampleRandString = traceContext.getSampleRate(); + if (sampleRandString != null) { + final Double sampleRand = Double.parseDouble(sampleRandString); + if (!SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { + return new TracesSamplingDecision(true, sampleRate, sampleRand); + } + } + return new TracesSamplingDecision(true, sampleRate); } } catch (Exception e) { diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 703826488f0..2b88a5ed457 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -2,7 +2,7 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; -import io.sentry.util.SentryRandom; +import io.sentry.util.SampleRateUtils; import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -59,7 +59,7 @@ public static PropagationContext fromHeaders( private @Nullable Boolean sampled; private @NotNull Double sampleRand; - private @Nullable Baggage baggage; + private @NotNull Baggage baggage; public PropagationContext() { this(new SentryId(), new SpanId(), null, null, null, null); @@ -70,11 +70,12 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.getTraceId(), propagationContext.getSpanId(), propagationContext.getParentSpanId(), - cloneBaggage(propagationContext.getBaggage()), + propagationContext.getBaggage(), propagationContext.isSampled(), propagationContext.getSampleRand()); } + @SuppressWarnings("UnusedMethod") private static @Nullable Baggage cloneBaggage(final @Nullable Baggage baggage) { if (baggage != null) { return new Baggage(baggage); @@ -83,6 +84,7 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) return null; } + @SuppressWarnings("ObjectToString") public PropagationContext( final @NotNull SentryId traceId, final @NotNull SpanId spanId, @@ -93,26 +95,40 @@ public PropagationContext( this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; - this.baggage = baggage; + boolean shouldFreezeBaggage = false; + if (baggage != null) { + this.baggage = baggage; + shouldFreezeBaggage = true; + } else { + this.baggage = new Baggage(ScopesAdapter.getInstance().getOptions().getLogger()); + } this.sampled = sampled; + StringBuilder sb = new StringBuilder("sample rand"); if (sampleRand != null) { + sb.append(" [passed in as param]"); this.sampleRand = sampleRand; - } else if (baggage != null && baggage.getSampleRandDouble() != null) { - this.sampleRand = baggage.getSampleRandDouble(); } else { - final @Nullable Double sampleRate = baggage == null ? null : baggage.getSampleRateDouble(); - final @NotNull Double sampleRandToUse = SentryRandom.current().nextDouble(); - - if (sampled != null && sampleRate != null) { - if (sampled) { - this.sampleRand = sampleRandToUse * sampleRate; - } else { - this.sampleRand = sampleRate + (sampleRandToUse * (1 - sampleRate)); - } + sb.append(" [maybe baggage maybe backfill]"); + final @Nullable Double sampleRandMaybe = this.baggage.getSampleRandDouble(); + sb.append(" [baggage " + sampleRandMaybe + "]"); + final @Nullable Double sampleRateMaybe = this.baggage.getSampleRateDouble(); + this.sampleRand = + SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, sampled); + } + if (this.baggage.getSampleRand() == null) { + sb.append(" [setting sample rand on baggage " + this.baggage + "]"); + this.baggage.setSampleRandDouble(this.sampleRand); + } + if (shouldFreezeBaggage) { + if (this.baggage.isMutable()) { + sb.append(" [freezing baggage]"); + this.baggage.freeze(); } else { - this.sampleRand = sampleRandToUse; + sb.append(" [baggage already frozen]"); } } + sb.append(" {" + this.sampleRand + "}"); + new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); } public @NotNull SentryId getTraceId() { @@ -139,14 +155,10 @@ public void setParentSpanId(final @Nullable SpanId parentSpanId) { this.parentSpanId = parentSpanId; } - public @Nullable Baggage getBaggage() { + public @NotNull Baggage getBaggage() { return baggage; } - public void setBaggage(final @Nullable Baggage baggage) { - this.baggage = baggage; - } - public @Nullable Boolean isSampled() { return sampled; } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index ca4f4d67ee2..c442755ae4c 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -84,6 +84,7 @@ public SentryTracer( if (context.getBaggage() != null) { this.baggage = context.getBaggage(); } else { + System.out.println("---- new baggage created in SentryTracer"); this.baggage = new Baggage(scopes.getOptions().getLogger()); } @@ -657,7 +658,6 @@ private void updateBaggageValues() { scope -> { replayId.set(scope.getReplayId()); }); - // TODO sampleRand? baggage.setValuesFromTransaction( getSpanContext().getTraceId(), replayId.get(), diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index bb32022f605..2f5afe807b5 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -19,6 +19,7 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { private final @Nullable String userId; private final @Nullable String transaction; private final @Nullable String sampleRate; + private final @Nullable String sampleRand; private final @Nullable String sampled; private final @Nullable SentryId replayId; @@ -39,6 +40,30 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { @Nullable String sampleRate, @Nullable String sampled, @Nullable SentryId replayId) { + this( + traceId, + publicKey, + release, + environment, + userId, + transaction, + sampleRate, + sampled, + replayId, + null); + } + + TraceContext( + @NotNull SentryId traceId, + @NotNull String publicKey, + @Nullable String release, + @Nullable String environment, + @Nullable String userId, + @Nullable String transaction, + @Nullable String sampleRate, + @Nullable String sampled, + @Nullable SentryId replayId, + @Nullable String sampleRand) { this.traceId = traceId; this.publicKey = publicKey; this.release = release; @@ -48,6 +73,7 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { this.sampleRate = sampleRate; this.sampled = sampled; this.replayId = replayId; + this.sampleRand = sampleRand; } @SuppressWarnings("UnusedMethod") @@ -88,6 +114,10 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { return sampleRate; } + public @Nullable String getSampleRand() { + return sampleRand; + } + public @Nullable String getSampled() { return sampled; } @@ -117,6 +147,7 @@ public static final class JsonKeys { public static final String USER_ID = "user_id"; public static final String TRANSACTION = "transaction"; public static final String SAMPLE_RATE = "sample_rate"; + public static final String SAMPLE_RAND = "sample_rand"; public static final String SAMPLED = "sampled"; public static final String REPLAY_ID = "replay_id"; } @@ -142,6 +173,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (sampleRate != null) { writer.name(TraceContext.JsonKeys.SAMPLE_RATE).value(sampleRate); } + if (sampleRate != null) { + writer.name(TraceContext.JsonKeys.SAMPLE_RAND).value(sampleRand); + } if (sampled != null) { writer.name(TraceContext.JsonKeys.SAMPLED).value(sampled); } @@ -171,6 +205,7 @@ public static final class Deserializer implements JsonDeserializer String userId = null; String transaction = null; String sampleRate = null; + String sampleRand = null; String sampled = null; SentryId replayId = null; @@ -199,6 +234,9 @@ public static final class Deserializer implements JsonDeserializer case TraceContext.JsonKeys.SAMPLE_RATE: sampleRate = reader.nextStringOrNull(); break; + case TraceContext.JsonKeys.SAMPLE_RAND: + sampleRand = reader.nextStringOrNull(); + break; case TraceContext.JsonKeys.SAMPLED: sampled = reader.nextStringOrNull(); break; @@ -229,7 +267,8 @@ public static final class Deserializer implements JsonDeserializer transaction, sampleRate, sampled, - replayId); + replayId, + sampleRand); traceContext.setUnknown(unknown); reader.endObject(); return traceContext; diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 1d1ad6470ac..12453b169b6 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -24,6 +24,7 @@ public TracesSampler(final @NotNull SentryOptions options) { @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); + System.out.println("sample rand used in TracesSampler " + sampleRand); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { @@ -56,7 +57,11 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont } if (samplerResult != null) { return new TracesSamplingDecision( - sample(samplerResult, sampleRand), samplerResult, profilesSampled, profilesSampleRate); + sample(samplerResult, sampleRand), + samplerResult, + sampleRand, + profilesSampled, + profilesSampleRate); } } @@ -76,6 +81,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont return new TracesSamplingDecision( sample(downsampledTracesSampleRate, sampleRand), downsampledTracesSampleRate, + sampleRand, profilesSampled, profilesSampleRate); } diff --git a/sentry/src/main/java/io/sentry/TracesSamplingDecision.java b/sentry/src/main/java/io/sentry/TracesSamplingDecision.java index 8010537d5c4..e9e9a7a490e 100644 --- a/sentry/src/main/java/io/sentry/TracesSamplingDecision.java +++ b/sentry/src/main/java/io/sentry/TracesSamplingDecision.java @@ -7,6 +7,7 @@ public final class TracesSamplingDecision { private final @NotNull Boolean sampled; private final @Nullable Double sampleRate; + private final @Nullable Double sampleRand; private final @NotNull Boolean profileSampled; private final @Nullable Double profileSampleRate; @@ -15,16 +16,33 @@ public TracesSamplingDecision(final @NotNull Boolean sampled) { } public TracesSamplingDecision(final @NotNull Boolean sampled, final @Nullable Double sampleRate) { - this(sampled, sampleRate, false, null); + this(sampled, sampleRate, null, false, null); } public TracesSamplingDecision( final @NotNull Boolean sampled, final @Nullable Double sampleRate, + final @Nullable Double sampleRand) { + this(sampled, sampleRate, sampleRand, false, null); + } + + public TracesSamplingDecision( + final @NotNull Boolean sampled, + final @Nullable Double sampleRate, + final @NotNull Boolean profileSampled, + final @Nullable Double profileSampleRate) { + this(sampled, sampleRate, null, profileSampled, profileSampleRate); + } + + public TracesSamplingDecision( + final @NotNull Boolean sampled, + final @Nullable Double sampleRate, + final @Nullable Double sampleRand, final @NotNull Boolean profileSampled, final @Nullable Double profileSampleRate) { this.sampled = sampled; this.sampleRate = sampleRate; + this.sampleRand = sampleRand; // A profile can be sampled only if the transaction is sampled this.profileSampled = sampled && profileSampled; this.profileSampleRate = profileSampleRate; @@ -38,6 +56,10 @@ public TracesSamplingDecision( return sampleRate; } + public @Nullable Double getSampleRand() { + return sampleRand; + } + public @NotNull Boolean getProfileSampled() { return profileSampled; } diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index aec0b8927de..233953c4941 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -22,20 +22,25 @@ public static TransactionContext fromPropagationContext( final @NotNull PropagationContext propagationContext) { @Nullable Boolean parentSampled = propagationContext.isSampled(); TracesSamplingDecision samplingDecision = - parentSampled == null ? null : new TracesSamplingDecision(parentSampled); - - @Nullable Baggage baggage = propagationContext.getBaggage(); - - if (baggage != null) { - baggage.freeze(); - - Double sampleRate = baggage.getSampleRateDouble(); - if (parentSampled != null) { - if (sampleRate != null) { - samplingDecision = new TracesSamplingDecision(parentSampled.booleanValue(), sampleRate); - } else { - samplingDecision = new TracesSamplingDecision(parentSampled.booleanValue()); - } + parentSampled == null + ? null + : new TracesSamplingDecision(parentSampled, null, propagationContext.getSampleRand()); + + @NotNull Baggage baggage = propagationContext.getBaggage(); + + // TODO is this ok? +// baggage.freeze(); + + Double sampleRate = baggage.getSampleRateDouble(); + if (parentSampled != null) { + if (sampleRate != null) { + samplingDecision = + new TracesSamplingDecision( + parentSampled.booleanValue(), sampleRate, propagationContext.getSampleRand()); + } else { + samplingDecision = + new TracesSamplingDecision( + parentSampled.booleanValue(), null, propagationContext.getSampleRand()); } } diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index ed011ff842b..77392a15720 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -23,6 +23,26 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return isValidRate(profilesSampleRate, true); } + public static Double backfilledSampleRand( + final @Nullable Double sampleRand, + final @Nullable Double sampleRate, + final @Nullable Boolean sampled) { + if (sampleRand != null) { + return sampleRand; + } + + double newSampleRand = SentryRandom.current().nextDouble(); + if (sampleRate != null && sampled != null) { + if (sampled) { + return newSampleRand * sampleRate; + } else { + return sampleRate + (newSampleRand * (1 - sampleRate)); + } + } + + return newSampleRand; + } + private static boolean isValidRate(final @Nullable Double rate, final boolean allowNull) { if (rate == null) { return allowNull; diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index be7a7355d37..a8093c89b85 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -57,12 +57,8 @@ public static void startNewTrace(final @NotNull IScopes scopes) { if (returnValue.propagationContext != null) { final @NotNull PropagationContext propagationContext = returnValue.propagationContext; - final @Nullable Baggage baggage = propagationContext.getBaggage(); - @Nullable BaggageHeader baggageHeader = null; - if (baggage != null) { - baggageHeader = - BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); - } + final @NotNull Baggage baggage = propagationContext.getBaggage(); + final @NotNull BaggageHeader baggageHeader = BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); return new TracingHeaders( new SentryTraceHeader( @@ -80,14 +76,7 @@ public static void startNewTrace(final @NotNull IScopes scopes) { final @NotNull IScope scope, final @NotNull SentryOptions sentryOptions) { return scope.withPropagationContext( propagationContext -> { - @Nullable Baggage baggage = propagationContext.getBaggage(); - if (baggage == null) { - baggage = new Baggage(sentryOptions.getLogger()); - propagationContext.setBaggage(baggage); - } - if (baggage.getSampleRand() != null) { - baggage.setSampleRandDouble(propagationContext.getSampleRand()); - } + @NotNull Baggage baggage = propagationContext.getBaggage(); if (baggage.isMutable()) { baggage.setValuesFromScope(scope, sentryOptions); baggage.freeze(); From 94e04c2ce87ce4ef2c9e6a2fb7742bfc3bcf2643 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 7 Feb 2025 15:54:53 +0100 Subject: [PATCH 03/29] wip3 --- .../opentelemetry/OtelSentryPropagator.java | 36 +++-- .../OtelSentrySpanProcessor.java | 15 +- .../sentry/opentelemetry/OtelSpanWrapper.java | 24 ++- .../sentry/opentelemetry/SentrySampler.java | 21 +-- .../opentelemetry/SentrySpanExporter.java | 1 + sentry/api/sentry.api | 16 +- sentry/src/main/java/io/sentry/Baggage.java | 149 +++++------------- .../src/main/java/io/sentry/HubAdapter.java | 5 + .../main/java/io/sentry/HubScopesWrapper.java | 5 + sentry/src/main/java/io/sentry/IScopes.java | 4 + sentry/src/main/java/io/sentry/NoOpHub.java | 5 + .../src/main/java/io/sentry/NoOpScopes.java | 5 + .../java/io/sentry/PropagationContext.java | 64 ++------ sentry/src/main/java/io/sentry/Scopes.java | 25 ++- .../main/java/io/sentry/ScopesAdapter.java | 5 + .../src/main/java/io/sentry/SentryTracer.java | 44 +++--- sentry/src/main/java/io/sentry/Span.java | 5 +- .../src/main/java/io/sentry/TraceContext.java | 5 + .../main/java/io/sentry/TracesSampler.java | 19 +-- .../java/io/sentry/TransactionContext.java | 8 +- .../java/io/sentry/util/SampleRateUtils.java | 22 ++- .../java/io/sentry/util/TracingUtils.java | 73 ++++++++- sentry/src/test/java/io/sentry/BaggageTest.kt | 37 ++++- .../test/java/io/sentry/JsonSerializerTest.kt | 8 +- .../java/io/sentry/PropagationContextTest.kt | 43 +++++ sentry/src/test/java/io/sentry/ScopeTest.kt | 2 +- .../sentry/TraceContextSerializationTest.kt | 3 +- .../test/java/io/sentry/TracesSamplerTest.kt | 117 ++++++++------ .../java/io/sentry/util/SampleRateUtilTest.kt | 61 +++++++ .../java/io/sentry/util/TracingUtilsTest.kt | 148 +++++++++++++++-- .../json/sentry_envelope_header.json | 1 + .../src/test/resources/json/trace_state.json | 1 + 32 files changed, 647 insertions(+), 330 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/PropagationContextTest.kt diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index ddcead442d7..c57fdcbc8c8 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -13,14 +13,12 @@ import io.sentry.Baggage; import io.sentry.BaggageHeader; import io.sentry.IScopes; -import io.sentry.PropagationContext; import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryTraceHeader; import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.util.TracingUtils; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -75,17 +73,21 @@ public void inject(final Context context, final C carrier, final TextMapSett return; } - // TODO can we use traceIfAllowed? do we have the URL here? - TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); - - final @NotNull SentryTraceHeader sentryTraceHeader = sentrySpan.toSentryTrace(); - setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); - final @Nullable BaggageHeader baggageHeader = - sentrySpan.toBaggageHeader(Collections.emptyList()); - if (baggageHeader != null) { - System.out.println("outgoing baggage: "); - System.out.println(baggageHeader.getValue()); - setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); + // TODO can we use traceIfAllowed? do we have the URL here? need to access span attrs + final @Nullable TracingUtils.TracingHeaders tracingHeaders = + TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); + + if (tracingHeaders != null) { + final @NotNull SentryTraceHeader sentryTraceHeader = tracingHeaders.getSentryTraceHeader(); + setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); + final @Nullable BaggageHeader baggageHeader = tracingHeaders.getBaggageHeader(); + if (baggageHeader != null) { + System.out.println("outgoing baggage: "); + System.out.println(baggageHeader.getValue()); + setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); + } + } else { + System.out.println("not tracing headers found"); } } @@ -108,6 +110,8 @@ public Context extract( SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(sentryTraceString); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); + System.out.println("incoming sentry-trace:"); + System.out.println(sentryTraceString); System.out.println("incoming baggage:"); System.out.println(baggageString); final Baggage baggage = Baggage.fromHeader(baggageString); @@ -134,9 +138,9 @@ public Context extract( .getLogger() .log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId()); -// final @NotNull PropagationContext propagationContext = -// PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); -// scopesToUse.getIsolationScope().setPropagationContext(propagationContext); + // final @NotNull PropagationContext propagationContext = + // PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); + // scopesToUse.getIsolationScope().setPropagationContext(propagationContext); return modifiedContext; } catch (InvalidSentryTraceHeaderException e) { diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index ef329414d6b..37293569aee 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -70,28 +70,19 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri baggage = baggageFromContext; } -// final @Nullable Boolean baggageMutable = -// otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE_MUTABLE); final @Nullable String baggageString = otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE); if (baggageString != null) { baggage = Baggage.fromHeader(baggageString); -// if (baggageMutable == false) { // TODO was this a bug? -// baggage.freeze(); -// } } final @Nullable Boolean sampled = isSampled(otelSpan, samplingDecision); - final @Nullable Double sampleRand = samplingDecision == null ? null : samplingDecision.getSampleRand(); final @NotNull PropagationContext propagationContext = new PropagationContext( - new SentryId(traceId), - sentrySpanId, - sentryParentSpanId, - baggage, - sampled, - sampleRand); + new SentryId(traceId), sentrySpanId, sentryParentSpanId, baggage, sampled); + + baggage = propagationContext.getBaggage(); updatePropagationContext(scopes, propagationContext); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java index 090d317485b..34f2d2a4d7c 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java @@ -64,7 +64,6 @@ public final class OtelSpanWrapper implements IOtelSpanWrapper { private final @NotNull Contexts contexts = new Contexts(); private @Nullable String transactionName; private @Nullable TransactionNameSource transactionNameSource; - private final @Nullable Baggage baggage; private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); private final @NotNull Map data = new ConcurrentHashMap<>(); @@ -86,17 +85,12 @@ public OtelSpanWrapper( this.scopes = Objects.requireNonNull(scopes, "scopes are required"); this.span = new WeakReference<>(span); this.startTimestamp = startTimestamp; - - if (parentSpan != null) { - this.baggage = parentSpan.getSpanContext().getBaggage(); - } else if (baggage != null) { - this.baggage = baggage; - } else { - this.baggage = null; - } - + final @Nullable Baggage baggageToUse = + baggage != null + ? baggage + : (parentSpan != null ? parentSpan.getSpanContext().getBaggage() : null); this.context = - new OtelSpanContext(span, samplingDecision, parentSpan, parentSpanId, this.baggage); + new OtelSpanContext(span, samplingDecision, parentSpan, parentSpanId, baggageToUse); } @Override @@ -207,15 +201,16 @@ public OtelSpanWrapper( @Override public @Nullable TraceContext traceContext() { if (scopes.getOptions().isTraceSampling()) { + final @Nullable Baggage baggage = context.getBaggage(); if (baggage != null) { - updateBaggageValues(); + updateBaggageValues(baggage); return baggage.toTraceContext(); } } return null; } - private void updateBaggageValues() { + private void updateBaggageValues(final @NotNull Baggage baggage) { try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (baggage != null && baggage.isMutable()) { final AtomicReference replayIdAtomicReference = new AtomicReference<>(); @@ -238,8 +233,9 @@ private void updateBaggageValues() { @Override public @Nullable BaggageHeader toBaggageHeader(@Nullable List thirdPartyBaggageHeaders) { if (scopes.getOptions().isTraceSampling()) { + final @Nullable Baggage baggage = context.getBaggage(); if (baggage != null) { - updateBaggageValues(); + updateBaggageValues(baggage); return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java index f83b69f5571..89499321293 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java @@ -23,7 +23,6 @@ import io.sentry.TransactionContext; import io.sentry.clientreport.DiscardReason; import io.sentry.protocol.SentryId; -import io.sentry.util.SampleRateUtils; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -83,25 +82,12 @@ public SamplingResult shouldSample( baggage = baggageFromContext; } - final @Nullable Boolean sampledFromHeaders = - sentryTraceHeader == null ? null : sentryTraceHeader.isSampled(); - final @Nullable Double sampleRandFromHeaders = - baggage == null ? null : baggage.getSampleRandDouble(); - final @Nullable Double sampleRateFromHeaders = - baggage == null ? null : baggage.getSampleRateDouble(); - final @NotNull Double sampleRand = - SampleRateUtils.backfilledSampleRand( - sampleRandFromHeaders, sampleRateFromHeaders, sampledFromHeaders); - // there's no way to get the span id here, so we just use a random id for sampling SpanId randomSpanId = new SpanId(); - final @Nullable Baggage baggageToUse = baggage == null ? null : baggage.toReadOnly(); - // TODO this freezes baggage but should not final @NotNull PropagationContext propagationContext = sentryTraceHeader == null - ? new PropagationContext( - new SentryId(traceId), randomSpanId, null, baggageToUse, null, sampleRand) - : PropagationContext.fromHeaders(sentryTraceHeader, baggageToUse, randomSpanId); + ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null) + : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId); final @NotNull TransactionContext transactionContext = TransactionContext.fromPropagationContext(propagationContext); @@ -109,7 +95,8 @@ public SamplingResult shouldSample( scopes .getOptions() .getInternalTracesSampler() - .sample(new SamplingContext(transactionContext, null, sampleRand)); + .sample( + new SamplingContext(transactionContext, null, propagationContext.getSampleRand())); if (!sentryDecision.getSampled()) { scopes diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java index 4d2e7545c64..693b94fe38d 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java @@ -62,6 +62,7 @@ public final class SentrySpanExporter implements SpanExporter { InternalSemanticAttributes.BAGGAGE_MUTABLE.getKey(), InternalSemanticAttributes.SAMPLED.getKey(), InternalSemanticAttributes.SAMPLE_RATE.getKey(), + InternalSemanticAttributes.SAMPLE_RAND.getKey(), InternalSemanticAttributes.PROFILE_SAMPLED.getKey(), InternalSemanticAttributes.PROFILE_SAMPLE_RATE.getKey(), InternalSemanticAttributes.PARENT_SAMPLED.getKey(), diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 3ab35b9dd5f..62f62571149 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -29,10 +29,11 @@ public final class io/sentry/Attachment { public abstract interface class io/sentry/BackfillingEventProcessor : io/sentry/EventProcessor { } -public final class io/sentry/Baggage { +public class io/sentry/Baggage { + public static final field NOOP Lio/sentry/Baggage; public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V - public fun (Ljava/util/Map;Ljava/lang/String;ZLio/sentry/ILogger;)V + public fun (Ljava/util/Map;Ljava/lang/String;ZZLio/sentry/ILogger;)V public fun freeze ()V public static fun fromEvent (Lio/sentry/SentryEvent;Lio/sentry/SentryOptions;)Lio/sentry/Baggage; public static fun fromHeader (Ljava/lang/String;)Lio/sentry/Baggage; @@ -57,6 +58,7 @@ public final class io/sentry/Baggage { public fun getUnknown ()Ljava/util/Map; public fun getUserId ()Ljava/lang/String; public fun isMutable ()Z + public fun isShouldFreeze ()Z public fun set (Ljava/lang/String;Ljava/lang/String;)V public fun setEnvironment (Ljava/lang/String;)V public fun setPublicKey (Ljava/lang/String;)V @@ -598,6 +600,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -662,6 +665,7 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -888,6 +892,7 @@ public abstract interface class io/sentry/IScopes { public abstract fun getLastEventId ()Lio/sentry/protocol/SentryId; public abstract fun getOptions ()Lio/sentry/SentryOptions; public abstract fun getParentScopes ()Lio/sentry/IScopes; + public abstract fun getPropagationContext ()Lio/sentry/PropagationContext; public abstract fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public abstract fun getScope ()Lio/sentry/IScope; public abstract fun getSpan ()Lio/sentry/ISpan; @@ -1410,6 +1415,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -1569,6 +1575,7 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -1944,9 +1951,10 @@ public final class io/sentry/ProfilingTransactionData$JsonKeys { } public final class io/sentry/PropagationContext { + public static field NOOP Lio/sentry/PropagationContext; public fun ()V public fun (Lio/sentry/PropagationContext;)V - public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;)V + public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext; @@ -2170,6 +2178,7 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -2234,6 +2243,7 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 7fbf1f2c7dd..10a5d7bf68b 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -3,7 +3,6 @@ import static io.sentry.protocol.Contexts.REPLAY_ID; import com.jakewharton.nopen.annotation.Open; - import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.SampleRateUtils; @@ -30,6 +29,8 @@ @Open public class Baggage { + public static final @NotNull Baggage NOOP = + new Baggage(new HashMap<>(), null, false, true, NoOpLogger.getInstance()); static final @NotNull String CHARSET = "UTF-8"; static final @NotNull Integer MAX_BAGGAGE_STRING_LENGTH = 8192; static final @NotNull Integer MAX_BAGGAGE_LIST_MEMBER_COUNT = 64; @@ -38,6 +39,7 @@ public class Baggage { final @NotNull Map keyValues; final @Nullable String thirdPartyHeader; private boolean mutable; + private boolean shouldFreeze; final @NotNull ILogger logger; @NotNull @@ -88,7 +90,7 @@ public static Baggage fromHeader( final @NotNull ILogger logger) { final @NotNull Map keyValues = new HashMap<>(); final @NotNull List thirdPartyKeyValueStrings = new ArrayList<>(); - boolean mutable = true; + boolean shouldFreeze = false; if (headerValue != null) { try { @@ -106,7 +108,9 @@ public static Baggage fromHeader( final String valueDecoded = decode(value); keyValues.put(keyDecoded, valueDecoded); -// mutable = false; + if (!DSCKeys.SAMPLE_RAND.equalsIgnoreCase(key)) { + shouldFreeze = true; + } } catch (Throwable e) { logger.log( SentryLevel.ERROR, @@ -126,7 +130,12 @@ public static Baggage fromHeader( thirdPartyKeyValueStrings.isEmpty() ? null : StringUtils.join(",", thirdPartyKeyValueStrings); - return new Baggage(keyValues, thirdPartyHeader, mutable, logger); + /* + can't freeze Baggage right away as we might have to backfill sampleRand + also we don't receive sentry-trace header here or in ctor so we can't + backfill then freeze here unless we pass sentry-trace header. + */ + return new Baggage(keyValues, thirdPartyHeader, true, shouldFreeze, logger); } @ApiStatus.Internal @@ -156,12 +165,17 @@ public static Baggage fromEvent( @ApiStatus.Internal public Baggage(final @NotNull ILogger logger) { - this(new HashMap<>(), null, true, logger); + this(new HashMap<>(), null, true, false, logger); } @ApiStatus.Internal public Baggage(final @NotNull Baggage baggage) { - this(baggage.keyValues, baggage.thirdPartyHeader, baggage.mutable, baggage.logger); + this( + baggage.keyValues, + baggage.thirdPartyHeader, + baggage.mutable, + baggage.shouldFreeze, + baggage.logger); } @ApiStatus.Internal @@ -170,18 +184,32 @@ public Baggage( final @NotNull Map keyValues, final @Nullable String thirdPartyHeader, boolean isMutable, + boolean shouldFreeze, final @NotNull ILogger logger) { this.keyValues = keyValues; this.logger = logger; - this.mutable = isMutable; this.thirdPartyHeader = thirdPartyHeader; - new RuntimeException("creating new baggage" + this).printStackTrace(); + this.mutable = isMutable; + this.shouldFreeze = shouldFreeze; + // new RuntimeException( + // "creating new baggage " + // + this + // + " mutable " + // + mutable + // + " shouldFreeze " + // + shouldFreeze) + // .printStackTrace(); } @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { - new RuntimeException("freezing baggage " + this).printStackTrace(); + // if (mutable) { + // new RuntimeException("freezing baggage " + this).printStackTrace(); + // } else { + // new RuntimeException("freezing baggage that is already frozen " + + // this).printStackTrace(); + // } this.mutable = false; } @@ -190,6 +218,11 @@ public boolean isMutable() { return mutable; } + @ApiStatus.Internal + public boolean isShouldFreeze() { + return shouldFreeze; + } + @Nullable public String getThirdPartyHeader() { return thirdPartyHeader; @@ -249,7 +282,7 @@ public String getThirdPartyHeader() { } } - sb.append(",sbg=" + this); + // sb.append(",sbg=" + this); return sb.toString(); } @@ -501,10 +534,6 @@ private static boolean isHighQualityTransactionName( return null; } - public @NotNull Baggage toReadOnly() { - return new ReadOnlyBaggage(this); - } - @ApiStatus.Internal @Nullable public TraceContext toTraceContext() { @@ -558,96 +587,4 @@ public static final class DSCKeys { SAMPLED, REPLAY_ID); } - - private static final class ReadOnlyBaggage extends Baggage { - - public ReadOnlyBaggage(@NotNull Baggage baggage) { - super(baggage); - } - - @Override - public void freeze() { - // do nothing - } - - @Override - public boolean isMutable() { - return false; - } - - @Override - public void setTraceId(@Nullable String traceId) { - // do nothing - } - - @Override - public void setPublicKey(@Nullable String publicKey) { - // do nothing - } - - @Override - public void setEnvironment(@Nullable String environment) { - // do nothing - } - - @Override - public void setRelease(@Nullable String release) { - // do nothing - } - - @Override - public void setUserId(@Nullable String userId) { - // do nothing - } - - @Override - public void setTransaction(@Nullable String transaction) { - // do nothing - } - - @Override - public void setSampleRate(@Nullable String sampleRate) { - // do nothing - } - - @Override - public void setSampleRand(@Nullable String sampleRand) { - // do nothing - } - - @Override - public void setSampleRandDouble(@Nullable Double sampleRand) { - // do nothing - } - - @Override - public void setSampled(@Nullable String sampled) { - // do nothing - } - - @Override - public void setReplayId(@Nullable String replayId) { - // do nothing - } - - @Override - public void set(@NotNull String key, @Nullable String value) { - // do nothing - } - - @Override - public void setValuesFromTransaction(@NotNull SentryId traceId, @Nullable SentryId replayId, @NotNull SentryOptions sentryOptions, @Nullable TracesSamplingDecision samplingDecision, @Nullable String transactionName, @Nullable TransactionNameSource transactionNameSource) { - // do nothing - } - - @Override - public void setValuesFromScope(@NotNull IScope scope, @NotNull SentryOptions options) { - // do nothing - } - - @Override - public @NotNull Baggage toReadOnly() { - return this; - } - } } diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index fc2f9c15dcd..50dd34adb1b 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -344,6 +344,11 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + @Override + public @NotNull PropagationContext getPropagationContext() { + return Sentry.getCurrentScopes().getPropagationContext(); + } + @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 591852a9adf..5b13970f13d 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -342,4 +342,9 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return scopes.captureReplay(replay, hint); } + + @Override + public @NotNull PropagationContext getPropagationContext() { + return scopes.getPropagationContext(); + } } diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index e07de9c327c..2ceb5c4f52c 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -689,4 +689,8 @@ default boolean isNoOp() { @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint); + + @ApiStatus.Internal + @NotNull + PropagationContext getPropagationContext(); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index d3e0b010c39..8f3741e15d7 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -297,6 +297,11 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } + @Override + public @NotNull PropagationContext getPropagationContext() { + return PropagationContext.NOOP; + } + @Override public @Nullable RateLimiter getRateLimiter() { return null; diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 8255569387d..0ccab9bbdc5 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -301,4 +301,9 @@ public boolean isNoOp() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return SentryId.EMPTY_ID; } + + @Override + public @NotNull PropagationContext getPropagationContext() { + return PropagationContext.NOOP; + } } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 2b88a5ed457..694f62052d4 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -3,6 +3,8 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; import io.sentry.util.SampleRateUtils; +import io.sentry.util.TracingUtils; + import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -12,6 +14,9 @@ @ApiStatus.Internal public final class PropagationContext { + public static @NotNull PropagationContext NOOP = + new PropagationContext(SentryId.EMPTY_ID, SpanId.EMPTY_ID, null, Baggage.NOOP, false); + public static PropagationContext fromHeaders( final @NotNull ILogger logger, final @Nullable String sentryTraceHeader, @@ -48,8 +53,7 @@ public static PropagationContext fromHeaders( spanIdToUse, sentryTraceHeader.getSpanId(), baggage, - sentryTraceHeader.isSampled(), - null); + sentryTraceHeader.isSampled()); } private @NotNull SentryId traceId; @@ -57,12 +61,11 @@ public static PropagationContext fromHeaders( private @Nullable SpanId parentSpanId; private @Nullable Boolean sampled; - private @NotNull Double sampleRand; private @NotNull Baggage baggage; public PropagationContext() { - this(new SentryId(), new SpanId(), null, null, null, null); + this(new SentryId(), new SpanId(), null, null, null); } public PropagationContext(final @NotNull PropagationContext propagationContext) { @@ -71,17 +74,7 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.getSpanId(), propagationContext.getParentSpanId(), propagationContext.getBaggage(), - propagationContext.isSampled(), - propagationContext.getSampleRand()); - } - - @SuppressWarnings("UnusedMethod") - private static @Nullable Baggage cloneBaggage(final @Nullable Baggage baggage) { - if (baggage != null) { - return new Baggage(baggage); - } - - return null; + propagationContext.isSampled()); } @SuppressWarnings("ObjectToString") @@ -90,45 +83,12 @@ public PropagationContext( final @NotNull SpanId spanId, final @Nullable SpanId parentSpanId, final @Nullable Baggage baggage, - final @Nullable Boolean sampled, - final @Nullable Double sampleRand) { + final @Nullable Boolean sampled) { this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; - boolean shouldFreezeBaggage = false; - if (baggage != null) { - this.baggage = baggage; - shouldFreezeBaggage = true; - } else { - this.baggage = new Baggage(ScopesAdapter.getInstance().getOptions().getLogger()); - } + this.baggage = TracingUtils.ensureBaggage(baggage, sampled, null, null); this.sampled = sampled; - StringBuilder sb = new StringBuilder("sample rand"); - if (sampleRand != null) { - sb.append(" [passed in as param]"); - this.sampleRand = sampleRand; - } else { - sb.append(" [maybe baggage maybe backfill]"); - final @Nullable Double sampleRandMaybe = this.baggage.getSampleRandDouble(); - sb.append(" [baggage " + sampleRandMaybe + "]"); - final @Nullable Double sampleRateMaybe = this.baggage.getSampleRateDouble(); - this.sampleRand = - SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, sampled); - } - if (this.baggage.getSampleRand() == null) { - sb.append(" [setting sample rand on baggage " + this.baggage + "]"); - this.baggage.setSampleRandDouble(this.sampleRand); - } - if (shouldFreezeBaggage) { - if (this.baggage.isMutable()) { - sb.append(" [freezing baggage]"); - this.baggage.freeze(); - } else { - sb.append(" [baggage already frozen]"); - } - } - sb.append(" {" + this.sampleRand + "}"); - new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); } public @NotNull SentryId getTraceId() { @@ -182,6 +142,8 @@ public void setSampled(final @Nullable Boolean sampled) { } public @NotNull Double getSampleRand() { - return sampleRand; + final @Nullable Double sampleRand = baggage.getSampleRandDouble(); + // should never be null since we ensure it in ctor + return sampleRand == null ? 0.0 : sampleRand; } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 4b4f8f8a877..08febded426 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -857,8 +857,7 @@ public void flush(long timeoutMillis) { SentryLevel.INFO, "Tracing is disabled and this 'startTransaction' returns a no-op."); transaction = NoOpTransaction.getInstance(); } else { - final @NotNull Double sampleRand = - getCombinedScopeView().getPropagationContext().getSampleRand(); + final Double sampleRand = getSampleRand(transactionContext); final SamplingContext samplingContext = new SamplingContext( transactionContext, transactionOptions.getCustomSamplingContext(), sampleRand); @@ -897,6 +896,18 @@ public void flush(long timeoutMillis) { return transaction; } + private @NotNull Double getSampleRand(final @NotNull TransactionContext transactionContext) { + final @Nullable Baggage baggage = transactionContext.getBaggage(); + if (baggage != null) { + final @Nullable Double sampleRandFromBaggageMaybe = baggage.getSampleRandDouble(); + if (sampleRandFromBaggageMaybe != null) { + return sampleRandFromBaggageMaybe; + } + } + + return getCombinedScopeView().getPropagationContext().getSampleRand(); + } + @Override @ApiStatus.Internal public void setSpanContext( @@ -966,7 +977,10 @@ public void reportFullyDisplayed() { PropagationContext.fromHeaders(getOptions().getLogger(), sentryTrace, baggageHeaders); configureScope( (scope) -> { - scope.setPropagationContext(propagationContext); + scope.withPropagationContext( + oldPropagationContext -> { + scope.setPropagationContext(propagationContext); + }); }); if (getOptions().isTracingEnabled()) { return TransactionContext.fromPropagationContext(propagationContext); @@ -1054,6 +1068,11 @@ public void reportFullyDisplayed() { return sentryId; } + @Override + public @NotNull PropagationContext getPropagationContext() { + return getCombinedScopeView().getPropagationContext(); + } + @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 6df6deee3d4..45629babad5 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -347,4 +347,9 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + + @Override + public @NotNull PropagationContext getPropagationContext() { + return Sentry.getCurrentScopes().getPropagationContext(); + } } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index c442755ae4c..5c73e128257 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -46,7 +46,6 @@ public final class SentryTracer implements ITransaction { private final @NotNull AtomicBoolean isIdleFinishTimerRunning = new AtomicBoolean(false); private final @NotNull AtomicBoolean isDeadlineTimerRunning = new AtomicBoolean(false); - private final @NotNull Baggage baggage; private @NotNull TransactionNameSource transactionNameSource; private final @NotNull Instrumenter instrumenter; private final @NotNull Contexts contexts = new Contexts(); @@ -81,13 +80,6 @@ public SentryTracer( this.transactionNameSource = context.getTransactionNameSource(); this.transactionOptions = transactionOptions; - if (context.getBaggage() != null) { - this.baggage = context.getBaggage(); - } else { - System.out.println("---- new baggage created in SentryTracer"); - this.baggage = new Baggage(scopes.getOptions().getLogger()); - } - // We are currently sending the performance data only in profiles, but we are always sending // performance measurements. if (transactionPerformanceCollector != null) { @@ -643,14 +635,16 @@ public void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) @Override public @Nullable TraceContext traceContext() { if (scopes.getOptions().isTraceSampling()) { - updateBaggageValues(); - return baggage.toTraceContext(); - } else { - return null; + final @Nullable Baggage baggage = getSpanContext().getBaggage(); + if (baggage != null) { + updateBaggageValues(baggage); + return baggage.toTraceContext(); + } } + return null; } - private void updateBaggageValues() { + private void updateBaggageValues(final @NotNull Baggage baggage) { try (final @NotNull ISentryLifecycleToken ignored = tracerLock.acquire()) { if (baggage.isMutable()) { final AtomicReference replayId = new AtomicReference<>(); @@ -673,20 +667,24 @@ private void updateBaggageValues() { @Override public @Nullable BaggageHeader toBaggageHeader(@Nullable List thirdPartyBaggageHeaders) { if (scopes.getOptions().isTraceSampling()) { - updateBaggageValues(); + final @Nullable Baggage baggage = getSpanContext().getBaggage(); + if (baggage != null) { + updateBaggageValues(baggage); + BaggageHeader baggageHeader = + BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + if (baggageHeader != null) { + System.out.println("outgoing baggage in SentryTracer:"); + System.out.println(baggageHeader.getValue()); + } else { + System.out.println("baggage header null in SentryTracer"); + } - BaggageHeader baggageHeader = - BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); - if (baggageHeader != null) { - System.out.println(baggageHeader.getName()); - System.out.println(baggageHeader.getValue()); + return baggageHeader; } else { - System.out.println("baggage header null in SentryTracer"); + System.out.println("baggage null in SentryTracer"); } - return baggageHeader; - } else { - return null; } + return null; } private boolean hasAllChildrenFinished() { diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index 3f08cca2a58..da8cf4765e5 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -154,7 +154,10 @@ public Span( @Override public @NotNull SentryTraceHeader toSentryTrace() { - return new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); + SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); + System.out.println("outgoing sentry-trace:"); + System.out.println(sentryTraceHeader.getValue()); + return sentryTraceHeader; } @Override diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index 2f5afe807b5..cdce7a9b0fe 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -30,6 +30,11 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { this(traceId, publicKey, null, null, null, null, null, null, null); } + @SuppressWarnings("InlineMeSuggester") + /** + * @deprecated please use the constructor than also takes sampleRand + */ + @Deprecated TraceContext( @NotNull SentryId traceId, @NotNull String publicKey, diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 12453b169b6..741440369c4 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -2,6 +2,8 @@ import io.sentry.util.Objects; import io.sentry.util.Random; +import io.sentry.util.SampleRateUtils; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,23 +14,18 @@ public final class TracesSampler { private final @NotNull SentryOptions options; public TracesSampler(final @NotNull SentryOptions options) { - this(Objects.requireNonNull(options, "options are required"), null); - } - - @TestOnly - TracesSampler(final @NotNull SentryOptions options, final @Nullable Random random) { - this.options = options; + this.options = Objects.requireNonNull(options, "options are required"); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "ObjectToString"}) @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); - System.out.println("sample rand used in TracesSampler " + sampleRand); +// new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { - return samplingContextSamplingDecision; + return SampleRateUtils.backfilledSampleRand(samplingContextSamplingDecision); } Double profilesSampleRate = null; @@ -68,7 +65,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont final TracesSamplingDecision parentSamplingDecision = samplingContext.getTransactionContext().getParentSamplingDecision(); if (parentSamplingDecision != null) { - return parentSamplingDecision; + return SampleRateUtils.backfilledSampleRand(parentSamplingDecision); } final @Nullable Double tracesSampleRateFromOptions = options.getTracesSampleRate(); @@ -86,7 +83,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont profilesSampleRate); } - return new TracesSamplingDecision(false, null, false, null); + return new TracesSamplingDecision(false, null, sampleRand, false, null); } private boolean sample(final @NotNull Double sampleRate, final @NotNull Double sampleRand) { diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index 233953c4941..fcb4684fbfc 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -3,6 +3,8 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.Objects; +import io.sentry.util.TracingUtils; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,9 +30,6 @@ public static TransactionContext fromPropagationContext( @NotNull Baggage baggage = propagationContext.getBaggage(); - // TODO is this ok? -// baggage.freeze(); - Double sampleRate = baggage.getSampleRateDouble(); if (parentSampled != null) { if (sampleRate != null) { @@ -95,6 +94,7 @@ public TransactionContext( this.name = Objects.requireNonNull(name, "name is required"); this.transactionNameSource = transactionNameSource; this.setSamplingDecision(samplingDecision); + this.baggage = TracingUtils.ensureBaggage(null, samplingDecision); } @ApiStatus.Internal @@ -108,7 +108,7 @@ public TransactionContext( this.name = DEFAULT_TRANSACTION_NAME; this.parentSamplingDecision = parentSamplingDecision; this.transactionNameSource = DEFAULT_NAME_SOURCE; - this.baggage = baggage; + this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); } public @NotNull String getName() { diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index 77392a15720..25cd46d0fdc 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -1,8 +1,11 @@ package io.sentry.util; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import io.sentry.TracesSamplingDecision; + @ApiStatus.Internal public final class SampleRateUtils { @@ -23,7 +26,9 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return isValidRate(profilesSampleRate, true); } - public static Double backfilledSampleRand( + // TODO test + @SuppressWarnings("ObjectToString") + public static @NotNull Double backfilledSampleRand( final @Nullable Double sampleRand, final @Nullable Double sampleRate, final @Nullable Boolean sampled) { @@ -31,6 +36,10 @@ public static Double backfilledSampleRand( return sampleRand; } + new RuntimeException( + "backfilling sample rand " + sampleRand + " rate " + sampleRate + " sampled " + sampled) + .printStackTrace(); + double newSampleRand = SentryRandom.current().nextDouble(); if (sampleRate != null && sampled != null) { if (sampled) { @@ -43,6 +52,17 @@ public static Double backfilledSampleRand( return newSampleRand; } + // TODO test + @SuppressWarnings("ObjectToString") + public static @NotNull TracesSamplingDecision backfilledSampleRand(final @NotNull TracesSamplingDecision samplingDecision) { + if (samplingDecision.getSampleRand() != null) { + return samplingDecision; + } + + final @NotNull Double sampleRand = backfilledSampleRand(null, samplingDecision.getSampleRate(), samplingDecision.getSampled()); + return new TracesSamplingDecision(samplingDecision.getSampled(), samplingDecision.getSampleRate(), sampleRand, samplingDecision.getProfileSampled(), samplingDecision.getProfileSampleRate()); + } + private static boolean isValidRate(final @Nullable Double rate, final boolean allowNull) { if (rate == null) { return allowNull; diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index a8093c89b85..b43fc960608 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -6,9 +6,12 @@ import io.sentry.IScope; import io.sentry.IScopes; import io.sentry.ISpan; +import io.sentry.NoOpLogger; import io.sentry.PropagationContext; import io.sentry.SentryOptions; import io.sentry.SentryTraceHeader; +import io.sentry.TracesSamplingDecision; + import java.util.List; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -58,7 +61,8 @@ public static void startNewTrace(final @NotNull IScopes scopes) { if (returnValue.propagationContext != null) { final @NotNull PropagationContext propagationContext = returnValue.propagationContext; final @NotNull Baggage baggage = propagationContext.getBaggage(); - final @NotNull BaggageHeader baggageHeader = BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + final @NotNull BaggageHeader baggageHeader = + BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); return new TracingHeaders( new SentryTraceHeader( @@ -144,4 +148,71 @@ public static boolean isIgnored( return false; } + + /** + * Ensures a non null baggage instance is present by creating a new Baggage instance if null + * is passed in. + * + * Also ensures there is a sampleRand value present on the baggage if it is still mutable. + * If the baggage should be frozen, it also takes care of freezing it. + * + * @param incomingBaggage a nullable baggage instance, if null a new one will be created + * @param decision a TracesSamplingDecision for potentially backfilling sampleRand to match that decision + * @return previous baggage instance or a new one + */ + @ApiStatus.Internal + public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable TracesSamplingDecision decision) { + final @Nullable Boolean decisionSampled = decision == null ? null : decision.getSampled(); + final @Nullable Double decisionSampleRate = decision == null ? null : decision.getSampleRate(); + final @Nullable Double decisionSampleRand = decision == null ? null : decision.getSampleRand(); + + return ensureBaggage(incomingBaggage, decisionSampled, decisionSampleRate, decisionSampleRand); + } + + /** + * Ensures a non null baggage instance is present by creating a new Baggage instance if null + * is passed in. + * + * Also ensures there is a sampleRand value present on the baggage if it is still mutable. + * If the baggage should be frozen, it also takes care of freezing it. + * + * @param incomingBaggage a nullable baggage instance, if null a new one will be created + * @param decisionSampled sampled decision for potential backfilling + * @param decisionSampleRate sampleRate for potential backfilling + * @param decisionSampleRand sampleRand to be used if none in baggage + * @return previous baggage instance or a new one + */ + @ApiStatus.Internal + public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable Boolean decisionSampled, final @Nullable Double decisionSampleRate, final @Nullable Double decisionSampleRand) { + final @NotNull Baggage baggage = incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; + + StringBuilder sb = new StringBuilder("sample rand"); + + if (baggage.getSampleRand() == null) { + final @Nullable Double baggageSampleRate = baggage.getSampleRateDouble(); + final @Nullable Double baggageSampleRand = baggage.getSampleRandDouble(); + + final @Nullable Double sampleRandMaybe = baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; + sb.append(" [baggage " + sampleRandMaybe + "]"); + final @Nullable Double sampleRateMaybe = baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; + final @NotNull Double sampleRand = + SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); + sb.append(" [setting sample rand on baggage " + baggage + "]"); + baggage.setSampleRandDouble(sampleRand); + sb.append(" {" + sampleRand + "}"); + } + if (baggage.isMutable()) { + sb.append(" [baggage mutable]"); + // cannot freeze on scope fork + if (baggage.isShouldFreeze()) { + sb.append(" [freezing baggage]"); + baggage.freeze(); + } + } else { + sb.append(" [baggage already frozen]"); + } +// new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); + + return baggage; + } } diff --git a/sentry/src/test/java/io/sentry/BaggageTest.kt b/sentry/src/test/java/io/sentry/BaggageTest.kt index 8beae33668b..242d10dff2e 100644 --- a/sentry/src/test/java/io/sentry/BaggageTest.kt +++ b/sentry/src/test/java/io/sentry/BaggageTest.kt @@ -8,6 +8,7 @@ import java.util.UUID import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -340,8 +341,9 @@ class BaggageTest { } @Test - fun `setting values if header contains sentry values has no effect`() { + fun `setting values on frozen baggage has no effect`() { val baggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction", logger) + baggage.freeze() baggage.traceId = "b" baggage.traceId = "c" @@ -352,6 +354,18 @@ class BaggageTest { assertEquals("sentry-trace_id=a,sentry-transaction=sentryTransaction", baggage.toHeaderString(null)) } + @Test + fun `if header contains sentry values baggage is marked as shouldFreeze`() { + val baggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction", logger) + assertTrue(baggage.isShouldFreeze) + } + + @Test + fun `if header does not contain sentry values baggage is not marked as shouldFreeze`() { + val baggage = Baggage.fromHeader("a=b", logger) + assertFalse(baggage.isShouldFreeze) + } + @Test fun `value may contain = sign`() { val baggage = Baggage(logger) @@ -535,6 +549,27 @@ class BaggageTest { assertEquals("abc", traceContext.unknown!!["anewkey"]) } + @Test + fun `header with sentry values is marked for freezing`() { + val baggage = + Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction") + assertTrue(baggage.isShouldFreeze) + } + + @Test + fun `header with sentry sample rand only is not marked for freezing`() { + val baggage = + Baggage.fromHeader("sentry-sample_rand=0.3") + assertFalse(baggage.isShouldFreeze) + } + + @Test + fun `header without sentry values is not marked for freezing`() { + val baggage = + Baggage.fromHeader("a=b,c=d") + assertFalse(baggage.isShouldFreeze) + } + /** * token = 1*tchar * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index e9d12ff4b17..8e3140faa11 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -456,16 +456,16 @@ class JsonSerializerTest { @Test fun `serializes trace context`() { - val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", "userId", "transaction", "0.5", "true", SentryId("3367f5196c494acaae85bbbd535379aa"))) - val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","user_id":"userId","transaction":"transaction","sample_rate":"0.5","sampled":"true","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" + val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", "userId", "transaction", "0.5", "true", SentryId("3367f5196c494acaae85bbbd535379aa"), "0.25")) + val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","user_id":"userId","transaction":"transaction","sample_rate":"0.5","sample_rand":"0.25","sampled":"true","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" val json = serializeToString(traceContext) assertEquals(expected, json) } @Test fun `serializes trace context with user having null id`() { - val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", null, "transaction", "0.6", "false", SentryId("3367f5196c494acaae85bbbd535379aa"))) - val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","transaction":"transaction","sample_rate":"0.6","sampled":"false","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" + val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", null, "transaction", "0.6", "false", SentryId("3367f5196c494acaae85bbbd535379aa"), "0.3")) + val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","transaction":"transaction","sample_rate":"0.6","sample_rand":"0.3","sampled":"false","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" val json = serializeToString(traceContext) assertEquals(expected, json) } diff --git a/sentry/src/test/java/io/sentry/PropagationContextTest.kt b/sentry/src/test/java/io/sentry/PropagationContextTest.kt new file mode 100644 index 00000000000..39fffe9f89a --- /dev/null +++ b/sentry/src/test/java/io/sentry/PropagationContextTest.kt @@ -0,0 +1,43 @@ +package io.sentry + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class PropagationContextTest { + + @Test + fun `freezes baggage with sentry values`() { + val propagationContext = PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + "2722d9f6ec019ade60c776169d9a8904-cedf5b7571cb4972-1", + "sentry-trace_id=a,sentry-transaction=sentryTransaction" + ) + assertFalse(propagationContext.baggage.isMutable) + assertTrue(propagationContext.baggage.isShouldFreeze) + } + + @Test + fun `does not freeze baggage without sentry values`() { + val propagationContext = PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + "2722d9f6ec019ade60c776169d9a8904-cedf5b7571cb4972-1", + "a=b" + ) + assertTrue(propagationContext.baggage.isMutable) + assertFalse(propagationContext.baggage.isShouldFreeze) + } + + @Test + fun `creates new baggage if none passed`() { + val propagationContext = PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + "2722d9f6ec019ade60c776169d9a8904-cedf5b7571cb4972-1", + null as? String? + ) + assertNotNull(propagationContext.baggage) + assertTrue(propagationContext.baggage.isMutable) + assertFalse(propagationContext.baggage.isShouldFreeze) + } +} diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 0d13f0d21ae..b8025735e8a 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -828,7 +828,7 @@ class ScopeTest { } val scope = Scope(options) - scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null, null) + scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null) verify(observer).setTrace( argThat { traceId.toString() == "64cf554cc8d74c6eafa3e08b7c984f6d" }, eq(scope) diff --git a/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt b/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt index 8b00df543d6..ce0e4a4ae40 100644 --- a/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt @@ -24,7 +24,8 @@ class TraceContextSerializationTest { "0252ec25-cd0a-4230-bd2f-936a4585637e", "0.00000021", "true", - SentryId("3367f5196c494acaae85bbbd535379aa") + SentryId("3367f5196c494acaae85bbbd535379aa"), + "0.00000012" ) } private val fixture = Fixture() diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index 06eb60aece2..a192aed6da0 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -9,23 +9,19 @@ import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue class TracesSamplerTest { class Fixture { internal fun getSut( - randomResult: Double? = null, tracesSampleRate: Double? = null, profilesSampleRate: Double? = null, tracesSamplerCallback: SentryOptions.TracesSamplerCallback? = null, profilesSamplerCallback: SentryOptions.ProfilesSamplerCallback? = null, logger: ILogger? = null ): TracesSampler { - val random = mock() - if (randomResult != null) { - whenever(random.nextDouble()).thenReturn(randomResult) - } val options = SentryOptions() if (tracesSampleRate != null) { options.tracesSampleRate = tracesSampleRate @@ -43,7 +39,7 @@ class TracesSamplerTest { options.isDebug = true options.setLogger(logger) } - return TracesSampler(options, random) + return TracesSampler(options) } } @@ -51,103 +47,115 @@ class TracesSamplerTest { @Test fun `when tracesSampleRate is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSampleRate = 0.2, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 0.2, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.9)) assertFalse(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test fun `when tracesSampleRate is set and random returns lower number returns true`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 0.2, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 0.2, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.1)) assertTrue(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSampleRate = 1.0, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.9)) assertTrue(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is set and random returns lower number returns true`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.1)) assertTrue(samplingDecision.sampled) assertTrue(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when trace is not sampled, profile is not sampled`() { - val sampler = fixture.getSut(randomResult = 0.3, tracesSampleRate = 0.0, profilesSampleRate = 1.0) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 0.0, profilesSampleRate = 1.0) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.3)) assertFalse(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertEquals(1.0, samplingDecision.profileSampleRate) + assertEquals(0.3, samplingDecision.sampleRand) } @Test fun `when tracesSampleRate is not set, tracesSampler is set and random returns lower number returns true`() { val sampler = fixture.getSut( - randomResult = 0.1, tracesSamplerCallback = { 0.2 }, profilesSamplerCallback = { 0.2 } ) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is not set, profilesSampler is set and random returns lower number returns true`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertTrue(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when tracesSampleRate is not set, tracesSampler is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSamplerCallback = { 0.2 }) + val sampler = fixture.getSut(tracesSamplerCallback = { 0.2 }) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.9 ) ) assertFalse(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is not set, profilesSampler is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.9 ) ) assertTrue(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test @@ -158,11 +166,13 @@ class TracesSamplerTest { val samplingDecision = sampler.sample( SamplingContext( transactionContextParentSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertNotNull(samplingDecision.sampleRand) } @Test @@ -173,34 +183,39 @@ class TracesSamplerTest { val samplingDecision = sampler.sample( SamplingContext( transactionContextParentSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertTrue(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) + assertNotNull(samplingDecision.sampleRand) } @Test fun `when tracesSampler returns null and tracesSampleRate is set sampler uses it as a sampling decision`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 0.2, tracesSamplerCallback = null) + val sampler = fixture.getSut(tracesSampleRate = 0.2, tracesSamplerCallback = null) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampler returns null and profilesSampleRate is set sampler uses it as a sampling decision`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0, profilesSampleRate = 0.2, profilesSamplerCallback = null) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSampleRate = 0.2, profilesSamplerCallback = null) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) @@ -210,29 +225,33 @@ class TracesSamplerTest { @Test fun `when tracesSampleRate is not set, and tracesSampler is not set returns false`() { - val sampler = fixture.getSut(randomResult = 0.1) + val sampler = fixture.getSut() val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertFalse(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is not set, and profilesSampler is not set returns false`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0) + val sampler = fixture.getSut(tracesSampleRate = 1.0) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test @@ -243,11 +262,13 @@ class TracesSamplerTest { val samplingDecision = sampler.sample( SamplingContext( transactionContextParentNotSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertFalse(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertNotNull(samplingDecision.sampleRand) assertFalse(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) @@ -256,11 +277,13 @@ class TracesSamplerTest { val samplingDecisionParentSampled = sampler.sample( SamplingContext( transactionContextParentSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecisionParentSampled.sampled) assertNull(samplingDecisionParentSampled.sampleRate) + assertNotNull(samplingDecisionParentSampled.sampleRand) assertTrue(samplingDecisionParentSampled.profileSampled) assertNull(samplingDecisionParentSampled.profileSampleRate) } @@ -288,27 +311,30 @@ class TracesSamplerTest { val transactionContextNotSampled = TransactionContext("name", "op") transactionContextNotSampled.sampled = false val samplingDecision = - sampler.sample(SamplingContext(transactionContextNotSampled, CustomSamplingContext())) + sampler.sample(SamplingContext(transactionContextNotSampled, CustomSamplingContext(), 0.1)) assertFalse(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertNotNull(samplingDecision.sampleRand) assertFalse(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) val transactionContextSampled = TransactionContext("name", "op") transactionContextSampled.setSampled(true, true) val samplingDecisionContextSampled = - sampler.sample(SamplingContext(transactionContextSampled, CustomSamplingContext())) + sampler.sample(SamplingContext(transactionContextSampled, CustomSamplingContext(), 0.1)) assertTrue(samplingDecisionContextSampled.sampled) assertNull(samplingDecisionContextSampled.sampleRate) + assertNotNull(samplingDecisionContextSampled.sampleRand) assertTrue(samplingDecisionContextSampled.profileSampled) assertNull(samplingDecisionContextSampled.profileSampleRate) val transactionContextUnsampledWithProfile = TransactionContext("name", "op") transactionContextUnsampledWithProfile.setSampled(false, true) val samplingDecisionContextUnsampledWithProfile = - sampler.sample(SamplingContext(transactionContextUnsampledWithProfile, CustomSamplingContext())) + sampler.sample(SamplingContext(transactionContextUnsampledWithProfile, CustomSamplingContext(), 0.1)) assertFalse(samplingDecisionContextUnsampledWithProfile.sampled) assertNull(samplingDecisionContextUnsampledWithProfile.sampleRate) + assertNotNull(samplingDecisionContextUnsampledWithProfile.sampleRand) assertFalse(samplingDecisionContextUnsampledWithProfile.profileSampled) assertNull(samplingDecisionContextUnsampledWithProfile.profileSampleRate) } @@ -326,7 +352,7 @@ class TracesSamplerTest { logger = logger ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.1) ) assertFalse(decision.profileSampled) verify(logger).log(eq(SentryLevel.ERROR), any(), eq(exception)) @@ -336,7 +362,6 @@ class TracesSamplerTest { fun `when a profilingRate and a ProfilesSamplerCallback is set but the callback throws an exception then profiling should still be enabled`() { val exception = Exception("faulty ProfilesSamplerCallback") val sampler = fixture.getSut( - randomResult = 0.0, tracesSampleRate = 1.0, profilesSampleRate = 1.0, profilesSamplerCallback = { @@ -344,9 +369,10 @@ class TracesSamplerTest { } ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.0) ) assertTrue(decision.profileSampled) + assertEquals(0.0, decision.sampleRand) } @Test @@ -361,9 +387,10 @@ class TracesSamplerTest { logger = logger ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.1) ) assertFalse(decision.sampled) + assertEquals(0.1, decision.sampleRand) verify(logger).log(eq(SentryLevel.ERROR), any(), eq(exception)) } @@ -371,15 +398,15 @@ class TracesSamplerTest { fun `when a tracesSampleRate and a TracesSamplerCallback is set but the callback throws an exception then tracing should still be enabled`() { val exception = Exception("faulty TracesSamplerCallback") val sampler = fixture.getSut( - randomResult = 0.0, tracesSampleRate = 1.0, tracesSamplerCallback = { throw exception } ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.0) ) assertTrue(decision.sampled) + assertEquals(0.0, decision.sampleRand) } } diff --git a/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt b/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt index e5c81bc70e4..5b1117aa6cf 100644 --- a/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt +++ b/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt @@ -1,7 +1,10 @@ package io.sentry.util +import io.sentry.TracesSamplingDecision import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue class SampleRateUtilTest { @@ -130,4 +133,62 @@ class SampleRateUtilTest { fun `accepts null profiles sample rate`() { assertTrue(SampleRateUtils.isValidProfilesSampleRate(null)) } + + @Test + fun `fills sample rand on decision if missing`() { + val decision = SampleRateUtils.backfilledSampleRand(TracesSamplingDecision(true)) + assertNotNull(decision.sampleRand) + } + + @Test + fun `keeps sample rand on decision if present`() { + val decision = SampleRateUtils.backfilledSampleRand(TracesSamplingDecision(true, 0.1, 0.5)) + assertEquals(0.5, decision.sampleRand) + } + + @Test + fun `uses sampleRand and does not backfill`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(0.3, null, null) + assertEquals(0.3, sampleRand) + } + + @Test + fun `backfills sampleRand if missing`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, null, null) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 1) + } + + @Test + fun `backfills sampleRand if missing with sampled true`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, null, true) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 1) + } + + @Test + fun `backfills sampleRand if missing with sampled false`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, null, false) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 1) + } + + @Test + fun `backfills sampleRand if missing with sampled true below samlpe rate`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, 0.0001, true) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 0.0001) + } + + @Test + fun `backfills sampleRand if missing with sampled false above sample rate`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, 0.9999, false) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0.9999) + assertTrue(sampleRand < 1) + } } diff --git a/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt b/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt index e5403f54d99..ed378ee9606 100644 --- a/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt +++ b/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt @@ -2,15 +2,19 @@ package io.sentry.util import io.sentry.Baggage import io.sentry.IScopes +import io.sentry.NoOpLogger import io.sentry.NoOpSpan +import io.sentry.PropagationContext import io.sentry.Scope import io.sentry.ScopeCallback import io.sentry.SentryOptions import io.sentry.SentryTracer import io.sentry.Span +import io.sentry.SpanId import io.sentry.SpanOptions import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext +import io.sentry.protocol.SentryId import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer @@ -21,6 +25,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertSame import kotlin.test.assertTrue class TracingUtilsTest { @@ -129,7 +134,7 @@ class TracingUtilsTest { @Test fun `returns headers if allowed from scope without span leaving frozen baggage alone`() { - fixture.scope.propagationContext.baggage = Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET").also { it.freeze() } + fixture.scope.propagationContext = PropagationContext(SentryId(), SpanId(), null, Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET").also { it.freeze() }, true) fixture.setup() val headers = TracingUtils.traceIfAllowed(fixture.scopes, "https://sentry.io/hello", fixture.preExistingBaggage, null) @@ -208,23 +213,11 @@ class TracingUtilsTest { assertNotEquals(propagationContextBefore.spanId, fixture.scope.propagationContext.spanId) } - @Test - fun `creates new baggage if none present`() { - fixture.setup() - assertNull(fixture.scope.propagationContext.baggage) - - TracingUtils.maybeUpdateBaggage(fixture.scope, fixture.options) - - assertNotNull(fixture.scope.propagationContext.baggage) - assertEquals(fixture.scope.propagationContext.traceId.toString(), fixture.scope.propagationContext.baggage!!.traceId) - assertFalse(fixture.scope.propagationContext.baggage!!.isMutable) - } - @Test fun `updates mutable baggage`() { fixture.setup() // not frozen because it doesn't contain sentry-* keys - fixture.scope.propagationContext.baggage = Baggage.fromHeader(fixture.preExistingBaggage) + fixture.scope.propagationContext = PropagationContext(SentryId(), SpanId(), null, Baggage.fromHeader(fixture.preExistingBaggage), true) TracingUtils.maybeUpdateBaggage(fixture.scope, fixture.options) @@ -236,11 +229,136 @@ class TracingUtilsTest { fun `does not change frozen baggage`() { fixture.setup() // frozen automatically because it contains sentry-* keys - fixture.scope.propagationContext.baggage = Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET") + fixture.scope.propagationContext = PropagationContext(SentryId(), SpanId(), null, Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET"), true) TracingUtils.maybeUpdateBaggage(fixture.scope, fixture.options) assertEquals("2722d9f6ec019ade60c776169d9a8904", fixture.scope.propagationContext.baggage!!.traceId) assertFalse(fixture.scope.propagationContext.baggage!!.isMutable) } + + @Test + fun `returns baggage if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + } + + @Test + fun `crates new baggage if null passed in that has sampleRand set and is mutable`() { + val baggage = TracingUtils.ensureBaggage( + null, + null as? TracesSamplingDecision? + ) + assertNotNull(baggage) + assertNotNull(baggage.sampleRand) + assertTrue(baggage.isMutable) + assertFalse(baggage.isShouldFreeze) + } + + @Test + fun `backfills sampleRand on passed in baggage if missing`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNotNull(baggage.sampleRand) + assertTrue(baggage.isMutable) + } + + @Test + fun `keeps sampleRand on passed in baggage if present`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + incomingBaggage.sampleRand = "0.3" + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertEquals("0.3", baggage.sampleRand) + assertTrue(baggage.isMutable) + } + + @Test + fun `does not backfill sampleRand on passed in baggage if frozen`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + incomingBaggage.freeze() + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNull(baggage.sampleRand) + assertFalse(baggage.isMutable) + } + + @Test + fun `freezes passed in baggage if should be frozen`() { + // markes as shouldFreeze=true due to sentry values being present in header + val incomingBaggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction") + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNotNull(baggage.sampleRand) + assertFalse(baggage.isMutable) + } + + @Test + fun `does not freeze passed in baggage if should not be frozen`() { + // markes as shouldFreeze=false due to no sentry values being present in header + val incomingBaggage = Baggage.fromHeader("a=b,c=d") + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNotNull(baggage.sampleRand) + assertTrue(baggage.isMutable) + } + + @Test + fun `uses sample rand if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + TracesSamplingDecision(true, null, 0.123) + ) + assertSame(incomingBaggage, baggage) + assertEquals("0.123", baggage.sampleRand) + } + + @Test + fun `uses sample rate and sampled flag true if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + TracesSamplingDecision(true, 0.0001, null) + ) + assertSame(incomingBaggage, baggage) + val sampleRand = baggage.sampleRandDouble + assertNotNull(sampleRand) + assertTrue(sampleRand < 0.0001) + assertTrue(sampleRand >= 0.0) + } + + @Test + fun `uses sample rate and sampled flag false if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + TracesSamplingDecision(false, 0.9999, null) + ) + assertSame(incomingBaggage, baggage) + val sampleRand = baggage.sampleRandDouble + assertNotNull(sampleRand) + assertTrue(sampleRand < 1.0) + assertTrue(sampleRand >= 0.9999) + } } diff --git a/sentry/src/test/resources/json/sentry_envelope_header.json b/sentry/src/test/resources/json/sentry_envelope_header.json index 626e9cbbc23..23580aab660 100644 --- a/sentry/src/test/resources/json/sentry_envelope_header.json +++ b/sentry/src/test/resources/json/sentry_envelope_header.json @@ -26,6 +26,7 @@ "user_id": "c052c566-6619-45f5-a61f-172802afa39a", "transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e", "sample_rate": "0.00000021", + "sample_rand": "0.00000012", "sampled": "true", "replay_id": "3367f5196c494acaae85bbbd535379aa" }, diff --git a/sentry/src/test/resources/json/trace_state.json b/sentry/src/test/resources/json/trace_state.json index db745e52136..a5eabb35834 100644 --- a/sentry/src/test/resources/json/trace_state.json +++ b/sentry/src/test/resources/json/trace_state.json @@ -6,6 +6,7 @@ "user_id": "c052c566-6619-45f5-a61f-172802afa39a", "transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e", "sample_rate": "0.00000021", + "sample_rand": "0.00000012", "sampled": "true", "replay_id": "3367f5196c494acaae85bbbd535379aa" } From 68a104892e689b8f4226a79b4ac93fbf88f68025 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 06:51:10 +0100 Subject: [PATCH 04/29] format + api --- sentry/api/sentry.api | 3 ++ .../java/io/sentry/PropagationContext.java | 2 - sentry/src/main/java/io/sentry/Span.java | 3 +- .../main/java/io/sentry/TracesSampler.java | 5 +-- .../java/io/sentry/TransactionContext.java | 3 +- .../java/io/sentry/util/SampleRateUtils.java | 16 ++++--- .../java/io/sentry/util/TracingUtils.java | 42 +++++++++++-------- .../test/java/io/sentry/TracesSamplerTest.kt | 2 - 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 62f62571149..ddea3df1386 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -6297,6 +6297,7 @@ public final class io/sentry/util/Random : java/io/Serializable { public final class io/sentry/util/SampleRateUtils { public fun ()V + public static fun backfilledSampleRand (Lio/sentry/TracesSamplingDecision;)Lio/sentry/TracesSamplingDecision; public static fun backfilledSampleRand (Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Boolean;)Ljava/lang/Double; public static fun isValidProfilesSampleRate (Ljava/lang/Double;)Z public static fun isValidSampleRate (Ljava/lang/Double;)Z @@ -6333,6 +6334,8 @@ public final class io/sentry/util/StringUtils { public final class io/sentry/util/TracingUtils { public fun ()V + public static fun ensureBaggage (Lio/sentry/Baggage;Lio/sentry/TracesSamplingDecision;)Lio/sentry/Baggage; + public static fun ensureBaggage (Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;)Lio/sentry/Baggage; public static fun isIgnored (Ljava/util/List;Ljava/lang/String;)Z public static fun maybeUpdateBaggage (Lio/sentry/IScope;Lio/sentry/SentryOptions;)Lio/sentry/PropagationContext; public static fun startNewTrace (Lio/sentry/IScopes;)V diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 694f62052d4..c126f19a5d3 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -2,9 +2,7 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; -import io.sentry.util.SampleRateUtils; import io.sentry.util.TracingUtils; - import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index da8cf4765e5..5b5cb6eadc8 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -154,7 +154,8 @@ public Span( @Override public @NotNull SentryTraceHeader toSentryTrace() { - SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); + SentryTraceHeader sentryTraceHeader = + new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); System.out.println("outgoing sentry-trace:"); System.out.println(sentryTraceHeader.getValue()); return sentryTraceHeader; diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 741440369c4..300f6cb6672 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -1,13 +1,10 @@ package io.sentry; import io.sentry.util.Objects; -import io.sentry.util.Random; import io.sentry.util.SampleRateUtils; - import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; @ApiStatus.Internal public final class TracesSampler { @@ -21,7 +18,7 @@ public TracesSampler(final @NotNull SentryOptions options) { @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); -// new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); + // new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index fcb4684fbfc..63f6eba9a6d 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -4,7 +4,6 @@ import io.sentry.protocol.TransactionNameSource; import io.sentry.util.Objects; import io.sentry.util.TracingUtils; - import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -108,7 +107,7 @@ public TransactionContext( this.name = DEFAULT_TRANSACTION_NAME; this.parentSamplingDecision = parentSamplingDecision; this.transactionNameSource = DEFAULT_NAME_SOURCE; - this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); + this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); // todo test } public @NotNull String getName() { diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index 25cd46d0fdc..3d7f247dfe2 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -1,11 +1,10 @@ package io.sentry.util; +import io.sentry.TracesSamplingDecision; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import io.sentry.TracesSamplingDecision; - @ApiStatus.Internal public final class SampleRateUtils { @@ -54,13 +53,20 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR // TODO test @SuppressWarnings("ObjectToString") - public static @NotNull TracesSamplingDecision backfilledSampleRand(final @NotNull TracesSamplingDecision samplingDecision) { + public static @NotNull TracesSamplingDecision backfilledSampleRand( + final @NotNull TracesSamplingDecision samplingDecision) { if (samplingDecision.getSampleRand() != null) { return samplingDecision; } - final @NotNull Double sampleRand = backfilledSampleRand(null, samplingDecision.getSampleRate(), samplingDecision.getSampled()); - return new TracesSamplingDecision(samplingDecision.getSampled(), samplingDecision.getSampleRate(), sampleRand, samplingDecision.getProfileSampled(), samplingDecision.getProfileSampleRate()); + final @NotNull Double sampleRand = + backfilledSampleRand(null, samplingDecision.getSampleRate(), samplingDecision.getSampled()); + return new TracesSamplingDecision( + samplingDecision.getSampled(), + samplingDecision.getSampleRate(), + sampleRand, + samplingDecision.getProfileSampled(), + samplingDecision.getProfileSampleRate()); } private static boolean isValidRate(final @Nullable Double rate, final boolean allowNull) { diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index b43fc960608..f1e22e04955 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -11,7 +11,6 @@ import io.sentry.SentryOptions; import io.sentry.SentryTraceHeader; import io.sentry.TracesSamplingDecision; - import java.util.List; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -150,18 +149,20 @@ public static boolean isIgnored( } /** - * Ensures a non null baggage instance is present by creating a new Baggage instance if null - * is passed in. + * Ensures a non null baggage instance is present by creating a new Baggage instance if null is + * passed in. * - * Also ensures there is a sampleRand value present on the baggage if it is still mutable. - * If the baggage should be frozen, it also takes care of freezing it. + *

Also ensures there is a sampleRand value present on the baggage if it is still mutable. If + * the baggage should be frozen, it also takes care of freezing it. * * @param incomingBaggage a nullable baggage instance, if null a new one will be created - * @param decision a TracesSamplingDecision for potentially backfilling sampleRand to match that decision + * @param decision a TracesSamplingDecision for potentially backfilling sampleRand to match that + * decision * @return previous baggage instance or a new one */ @ApiStatus.Internal - public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable TracesSamplingDecision decision) { + public static @NotNull Baggage ensureBaggage( + final @Nullable Baggage incomingBaggage, final @Nullable TracesSamplingDecision decision) { final @Nullable Boolean decisionSampled = decision == null ? null : decision.getSampled(); final @Nullable Double decisionSampleRate = decision == null ? null : decision.getSampleRate(); final @Nullable Double decisionSampleRand = decision == null ? null : decision.getSampleRand(); @@ -170,11 +171,11 @@ public static boolean isIgnored( } /** - * Ensures a non null baggage instance is present by creating a new Baggage instance if null - * is passed in. + * Ensures a non null baggage instance is present by creating a new Baggage instance if null is + * passed in. * - * Also ensures there is a sampleRand value present on the baggage if it is still mutable. - * If the baggage should be frozen, it also takes care of freezing it. + *

Also ensures there is a sampleRand value present on the baggage if it is still mutable. If + * the baggage should be frozen, it also takes care of freezing it. * * @param incomingBaggage a nullable baggage instance, if null a new one will be created * @param decisionSampled sampled decision for potential backfilling @@ -183,8 +184,13 @@ public static boolean isIgnored( * @return previous baggage instance or a new one */ @ApiStatus.Internal - public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable Boolean decisionSampled, final @Nullable Double decisionSampleRate, final @Nullable Double decisionSampleRand) { - final @NotNull Baggage baggage = incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; + public static @NotNull Baggage ensureBaggage( + final @Nullable Baggage incomingBaggage, + final @Nullable Boolean decisionSampled, + final @Nullable Double decisionSampleRate, + final @Nullable Double decisionSampleRand) { + final @NotNull Baggage baggage = + incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; StringBuilder sb = new StringBuilder("sample rand"); @@ -192,11 +198,13 @@ public static boolean isIgnored( final @Nullable Double baggageSampleRate = baggage.getSampleRateDouble(); final @Nullable Double baggageSampleRand = baggage.getSampleRandDouble(); - final @Nullable Double sampleRandMaybe = baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; + final @Nullable Double sampleRandMaybe = + baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; sb.append(" [baggage " + sampleRandMaybe + "]"); - final @Nullable Double sampleRateMaybe = baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; + final @Nullable Double sampleRateMaybe = + baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; final @NotNull Double sampleRand = - SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); + SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); sb.append(" [setting sample rand on baggage " + baggage + "]"); baggage.setSampleRandDouble(sampleRand); sb.append(" {" + sampleRand + "}"); @@ -211,7 +219,7 @@ public static boolean isIgnored( } else { sb.append(" [baggage already frozen]"); } -// new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); + // new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); return baggage; } diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index a192aed6da0..0fbc8e2f679 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -1,11 +1,9 @@ package io.sentry -import io.sentry.util.Random import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse From 0c0b4d57f8d99755e6a91711c7e1b3e672d56642 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:23:04 +0100 Subject: [PATCH 05/29] cleanup --- .../opentelemetry/OtelSentryPropagator.java | 12 -------- .../spring/boot/jakarta/ApiService.java | 23 -------------- .../spring/boot/jakarta/PersonController.java | 5 +--- sentry/src/main/java/io/sentry/Baggage.java | 18 +---------- .../java/io/sentry/CombinedScopeView.java | 1 + .../src/main/java/io/sentry/HubAdapter.java | 1 + .../main/java/io/sentry/HubScopesWrapper.java | 1 + sentry/src/main/java/io/sentry/NoOpHub.java | 1 + .../src/main/java/io/sentry/NoOpScopes.java | 1 + .../src/main/java/io/sentry/OutboxSender.java | 3 +- .../java/io/sentry/PropagationContext.java | 2 +- sentry/src/main/java/io/sentry/Scopes.java | 1 + .../main/java/io/sentry/ScopesAdapter.java | 1 + .../src/main/java/io/sentry/SentryTracer.java | 13 +------- sentry/src/main/java/io/sentry/Span.java | 6 +--- .../main/java/io/sentry/TracesSampler.java | 1 - .../java/io/sentry/TransactionContext.java | 2 +- .../java/io/sentry/util/SampleRateUtils.java | 8 ----- .../java/io/sentry/util/TracingUtils.java | 10 ------- .../java/io/sentry/TransactionContextTest.kt | 30 +++++++++++++++++++ 20 files changed, 45 insertions(+), 95 deletions(-) delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index c57fdcbc8c8..fc2e3d426b7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -82,12 +82,8 @@ public void inject(final Context context, final C carrier, final TextMapSett setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); final @Nullable BaggageHeader baggageHeader = tracingHeaders.getBaggageHeader(); if (baggageHeader != null) { - System.out.println("outgoing baggage: "); - System.out.println(baggageHeader.getValue()); setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); } - } else { - System.out.println("not tracing headers found"); } } @@ -110,10 +106,6 @@ public Context extract( SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(sentryTraceString); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); - System.out.println("incoming sentry-trace:"); - System.out.println(sentryTraceString); - System.out.println("incoming baggage:"); - System.out.println(baggageString); final Baggage baggage = Baggage.fromHeader(baggageString); final @NotNull TraceState traceState = TraceState.getDefault(); @@ -138,10 +130,6 @@ public Context extract( .getLogger() .log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId()); - // final @NotNull PropagationContext propagationContext = - // PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); - // scopesToUse.getIsolationScope().setPropagationContext(propagationContext); - return modifiedContext; } catch (InvalidSentryTraceHeaderException e) { scopes diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java deleted file mode 100644 index c895b8b979c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.sentry.samples.spring.boot.jakarta; - -import io.sentry.spring.jakarta.tracing.SentrySpan; -import org.jetbrains.annotations.NotNull; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; - -@Service -public class ApiService { - - private final RestClient restClient; - - public ApiService(RestClient restClient) { - this.restClient = restClient; - } - - @SentrySpan("annotation-span") - void apiRequest(final @NotNull String name) { - // restClient.get().uri("http://localhost:8000?q={name}", - // name).retrieve().body(String.class); - restClient.get().uri("http://localhost:8081/articles").retrieve().body(String.class); - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java index fe79122f5ff..5ce11d0a528 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java @@ -20,13 +20,11 @@ public class PersonController { private final PersonService personService; private final Tracer tracer; - private final ApiService apiService; private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); - public PersonController(PersonService personService, Tracer tracer, ApiService apiService) { + public PersonController(PersonService personService, Tracer tracer) { this.personService = personService; this.tracer = tracer; - this.apiService = apiService; } @GetMapping("{id}") @@ -36,7 +34,6 @@ Person person(@PathVariable Long id) { ISpan currentSpan = Sentry.getSpan(); ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi"); try { - apiService.apiRequest(id.toString()); LOGGER.error("Trying person with id={}", id, new RuntimeException("error while loading")); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } finally { diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 10a5d7bf68b..53b18e48a9e 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -152,7 +152,7 @@ public static Baggage fromEvent( // we don't persist sample rate baggage.setSampleRate(null); baggage.setSampled(null); - baggage.setSampleRand(null); // TODO do we need this? + baggage.setSampleRand(null); final @Nullable Object replayId = event.getContexts().get(REPLAY_ID); if (replayId != null && !replayId.toString().equals(SentryId.EMPTY_ID.toString())) { baggage.setReplayId(replayId.toString()); @@ -191,25 +191,11 @@ public Baggage( this.thirdPartyHeader = thirdPartyHeader; this.mutable = isMutable; this.shouldFreeze = shouldFreeze; - // new RuntimeException( - // "creating new baggage " - // + this - // + " mutable " - // + mutable - // + " shouldFreeze " - // + shouldFreeze) - // .printStackTrace(); } @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { - // if (mutable) { - // new RuntimeException("freezing baggage " + this).printStackTrace(); - // } else { - // new RuntimeException("freezing baggage that is already frozen " + - // this).printStackTrace(); - // } this.mutable = false; } @@ -282,8 +268,6 @@ public String getThirdPartyHeader() { } } - // sb.append(",sbg=" + this); - return sb.toString(); } diff --git a/sentry/src/main/java/io/sentry/CombinedScopeView.java b/sentry/src/main/java/io/sentry/CombinedScopeView.java index 3523afa4d3d..129066450f3 100644 --- a/sentry/src/main/java/io/sentry/CombinedScopeView.java +++ b/sentry/src/main/java/io/sentry/CombinedScopeView.java @@ -426,6 +426,7 @@ public void setPropagationContext(@NotNull PropagationContext propagationContext getDefaultWriteScope().setPropagationContext(propagationContext); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return getDefaultWriteScope().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 50dd34adb1b..8dbcff4ef6e 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -344,6 +344,7 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return Sentry.getCurrentScopes().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 5b13970f13d..4427f33c4bc 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -343,6 +343,7 @@ public void reportFullyDisplayed() { return scopes.captureReplay(replay, hint); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return scopes.getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index 8f3741e15d7..7ef1a52cdec 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -297,6 +297,7 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return PropagationContext.NOOP; diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 0ccab9bbdc5..779a67024a9 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -302,6 +302,7 @@ public boolean isNoOp() { return SentryId.EMPTY_ID; } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return PropagationContext.NOOP; diff --git a/sentry/src/main/java/io/sentry/OutboxSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java index f5c3813414d..b878606c00b 100644 --- a/sentry/src/main/java/io/sentry/OutboxSender.java +++ b/sentry/src/main/java/io/sentry/OutboxSender.java @@ -252,7 +252,8 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN } } - return new TracesSamplingDecision(true, sampleRate); + return SampleRateUtils.backfilledSampleRand( + new TracesSamplingDecision(true, sampleRate)); } } catch (Exception e) { logger.log( diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index c126f19a5d3..9b1de29371c 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -60,7 +60,7 @@ public static PropagationContext fromHeaders( private @Nullable Boolean sampled; - private @NotNull Baggage baggage; + private final @NotNull Baggage baggage; public PropagationContext() { this(new SentryId(), new SpanId(), null, null, null); diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 08febded426..773fdc0e04d 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1068,6 +1068,7 @@ public void reportFullyDisplayed() { return sentryId; } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return getCombinedScopeView().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 45629babad5..794449305e1 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -348,6 +348,7 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return Sentry.getCurrentScopes().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 5c73e128257..36329db7a71 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -670,18 +670,7 @@ private void updateBaggageValues(final @NotNull Baggage baggage) { final @Nullable Baggage baggage = getSpanContext().getBaggage(); if (baggage != null) { updateBaggageValues(baggage); - BaggageHeader baggageHeader = - BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); - if (baggageHeader != null) { - System.out.println("outgoing baggage in SentryTracer:"); - System.out.println(baggageHeader.getValue()); - } else { - System.out.println("baggage header null in SentryTracer"); - } - - return baggageHeader; - } else { - System.out.println("baggage null in SentryTracer"); + return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); } } return null; diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index 5b5cb6eadc8..3f08cca2a58 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -154,11 +154,7 @@ public Span( @Override public @NotNull SentryTraceHeader toSentryTrace() { - SentryTraceHeader sentryTraceHeader = - new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); - System.out.println("outgoing sentry-trace:"); - System.out.println(sentryTraceHeader.getValue()); - return sentryTraceHeader; + return new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); } @Override diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 300f6cb6672..e89065d3af9 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -18,7 +18,6 @@ public TracesSampler(final @NotNull SentryOptions options) { @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); - // new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index 63f6eba9a6d..64b60d4c3b8 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -107,7 +107,7 @@ public TransactionContext( this.name = DEFAULT_TRANSACTION_NAME; this.parentSamplingDecision = parentSamplingDecision; this.transactionNameSource = DEFAULT_NAME_SOURCE; - this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); // todo test + this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); } public @NotNull String getName() { diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index 3d7f247dfe2..225ce58a3b5 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -25,8 +25,6 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return isValidRate(profilesSampleRate, true); } - // TODO test - @SuppressWarnings("ObjectToString") public static @NotNull Double backfilledSampleRand( final @Nullable Double sampleRand, final @Nullable Double sampleRate, @@ -35,10 +33,6 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return sampleRand; } - new RuntimeException( - "backfilling sample rand " + sampleRand + " rate " + sampleRate + " sampled " + sampled) - .printStackTrace(); - double newSampleRand = SentryRandom.current().nextDouble(); if (sampleRate != null && sampled != null) { if (sampled) { @@ -51,8 +45,6 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return newSampleRand; } - // TODO test - @SuppressWarnings("ObjectToString") public static @NotNull TracesSamplingDecision backfilledSampleRand( final @NotNull TracesSamplingDecision samplingDecision) { if (samplingDecision.getSampleRand() != null) { diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index f1e22e04955..7a6d053f138 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -192,34 +192,24 @@ public static boolean isIgnored( final @NotNull Baggage baggage = incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; - StringBuilder sb = new StringBuilder("sample rand"); - if (baggage.getSampleRand() == null) { final @Nullable Double baggageSampleRate = baggage.getSampleRateDouble(); final @Nullable Double baggageSampleRand = baggage.getSampleRandDouble(); final @Nullable Double sampleRandMaybe = baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; - sb.append(" [baggage " + sampleRandMaybe + "]"); final @Nullable Double sampleRateMaybe = baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; final @NotNull Double sampleRand = SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); - sb.append(" [setting sample rand on baggage " + baggage + "]"); baggage.setSampleRandDouble(sampleRand); - sb.append(" {" + sampleRand + "}"); } if (baggage.isMutable()) { - sb.append(" [baggage mutable]"); // cannot freeze on scope fork if (baggage.isShouldFreeze()) { - sb.append(" [freezing baggage]"); baggage.freeze(); } - } else { - sb.append(" [baggage already frozen]"); } - // new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); return baggage; } diff --git a/sentry/src/test/java/io/sentry/TransactionContextTest.kt b/sentry/src/test/java/io/sentry/TransactionContextTest.kt index d6b715bd841..8a66870bc26 100644 --- a/sentry/src/test/java/io/sentry/TransactionContextTest.kt +++ b/sentry/src/test/java/io/sentry/TransactionContextTest.kt @@ -1,10 +1,12 @@ package io.sentry import io.sentry.protocol.SentryId +import io.sentry.protocol.TransactionNameSource import org.mockito.kotlin.mock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -91,4 +93,32 @@ class TransactionContextTest { context.isForNextAppStart = true assertTrue(context.isForNextAppStart) } + + @Test + fun `when passing null baggage creates a new one`() { + val context = TransactionContext(SentryId(), SpanId(), null, null, null) + assertNotNull(context.baggage) + assertNotNull(context.baggage?.sampleRand) + } + + @Test + fun `when passing null baggage creates a new one and uses parent sampling decision`() { + val context = TransactionContext(SentryId(), SpanId(), null, TracesSamplingDecision(true, 0.1, 0.2), null) + assertNotNull(context.baggage) + assertEquals("0.2", context.baggage?.sampleRand) + } + + @Test + fun `when using few param ctor creates a new baggage`() { + val context = TransactionContext("name", "op") + assertNotNull(context.baggage) + assertNotNull(context.baggage?.sampleRand) + } + + @Test + fun `when using few param ctor creates a new baggage and uses sampling decision`() { + val context = TransactionContext("name", TransactionNameSource.CUSTOM, "op", TracesSamplingDecision(true, 0.1, 0.2)) + assertNotNull(context.baggage) + assertEquals("0.2", context.baggage?.sampleRand) + } } From ee67f428a5bf87bfc6233b9d86072620a78754f4 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:39:11 +0100 Subject: [PATCH 06/29] revert change to demo --- .../boot/jakarta/SentryDemoApplication.java | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java index 4c61bf79278..a6eb46f4c74 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java @@ -1,12 +1,21 @@ package io.sentry.samples.spring.boot.jakarta; +import static io.sentry.quartz.SentryJobListener.SENTRY_SLUG_KEY; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Tracer; +import io.sentry.samples.spring.boot.jakarta.quartz.SampleJob; +import java.util.Collections; +import org.quartz.JobDetail; +import org.quartz.SimpleTrigger; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.quartz.CronTriggerFactoryBean; +import org.springframework.scheduling.quartz.JobDetailFactoryBean; +import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; @@ -33,34 +42,34 @@ RestClient restClient(RestClient.Builder builder) { return builder.build(); } - // @Bean - // public JobDetailFactoryBean jobDetail() { - // JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); - // jobDetailFactory.setJobClass(SampleJob.class); - // jobDetailFactory.setDurability(true); - // jobDetailFactory.setJobDataAsMap( - // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); - // return jobDetailFactory; - // } - // - // @Bean - // public SimpleTriggerFactoryBean trigger(JobDetail job) { - // SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); - // trigger.setJobDetail(job); - // trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes - // trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); - // trigger.setJobDataAsMap( - // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); - // return trigger; - // } - // - // @Bean - // public CronTriggerFactoryBean cronTrigger(JobDetail job) { - // CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); - // trigger.setJobDetail(job); - // trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes - // return trigger; - // } + @Bean + public JobDetailFactoryBean jobDetail() { + JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); + jobDetailFactory.setJobClass(SampleJob.class); + jobDetailFactory.setDurability(true); + jobDetailFactory.setJobDataAsMap( + Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); + return jobDetailFactory; + } + + @Bean + public SimpleTriggerFactoryBean trigger(JobDetail job) { + SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); + trigger.setJobDetail(job); + trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes + trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); + trigger.setJobDataAsMap( + Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); + return trigger; + } + + @Bean + public CronTriggerFactoryBean cronTrigger(JobDetail job) { + CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); + trigger.setJobDetail(job); + trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes + return trigger; + } @Bean public Tracer tracer() { From 591d91eb090f9b9dacb2d0f177707cebfe72cb95 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:40:46 +0100 Subject: [PATCH 07/29] make baggage final again --- sentry/src/main/java/io/sentry/Baggage.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 53b18e48a9e..5cf7d264202 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -2,7 +2,6 @@ import static io.sentry.protocol.Contexts.REPLAY_ID; -import com.jakewharton.nopen.annotation.Open; import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.SampleRateUtils; @@ -26,8 +25,7 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental -@Open -public class Baggage { +public final class Baggage { public static final @NotNull Baggage NOOP = new Baggage(new HashMap<>(), null, false, true, NoOpLogger.getInstance()); From 1c6cee7df34ac20120e5f5fd125d5a1f7167df34 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:45:48 +0100 Subject: [PATCH 08/29] remove ObjectToString suppressing --- sentry/src/main/java/io/sentry/Baggage.java | 3 --- sentry/src/main/java/io/sentry/PropagationContext.java | 1 - sentry/src/main/java/io/sentry/TracesSampler.java | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 5cf7d264202..acd5fdb6e0a 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -177,7 +177,6 @@ public Baggage(final @NotNull Baggage baggage) { } @ApiStatus.Internal - @SuppressWarnings("ObjectToString") public Baggage( final @NotNull Map keyValues, final @Nullable String thirdPartyHeader, @@ -191,7 +190,6 @@ public Baggage( this.shouldFreeze = shouldFreeze; } - @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { this.mutable = false; @@ -212,7 +210,6 @@ public String getThirdPartyHeader() { return thirdPartyHeader; } - @SuppressWarnings("ObjectToString") public @NotNull String toHeaderString(@Nullable String thirdPartyBaggageHeaderString) { final StringBuilder sb = new StringBuilder(); String separator = ""; diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 9b1de29371c..d863955bdb0 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -75,7 +75,6 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.isSampled()); } - @SuppressWarnings("ObjectToString") public PropagationContext( final @NotNull SentryId traceId, final @NotNull SpanId spanId, diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index e89065d3af9..b3da8d63ccf 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -14,7 +14,7 @@ public TracesSampler(final @NotNull SentryOptions options) { this.options = Objects.requireNonNull(options, "options are required"); } - @SuppressWarnings({"deprecation", "ObjectToString"}) + @SuppressWarnings("deprecation") @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); From 4df3c8fbcce3fe9bc8f8f46b6c21e5d6f348823f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:54:19 +0100 Subject: [PATCH 09/29] remove outdated comment --- sentry/src/main/java/io/sentry/util/TracingUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index 7a6d053f138..075bfcccd01 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -205,7 +205,6 @@ public static boolean isIgnored( baggage.setSampleRandDouble(sampleRand); } if (baggage.isMutable()) { - // cannot freeze on scope fork if (baggage.isShouldFreeze()) { baggage.freeze(); } From a86229651bdf917b22a79490de574c3b8ad0c3e1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 11:33:31 +0100 Subject: [PATCH 10/29] remove getPropagationContext from Scopes again --- sentry/api/sentry.api | 9 +-------- sentry/src/main/java/io/sentry/HubAdapter.java | 6 ------ sentry/src/main/java/io/sentry/HubScopesWrapper.java | 6 ------ sentry/src/main/java/io/sentry/IScopes.java | 4 ---- sentry/src/main/java/io/sentry/NoOpHub.java | 6 ------ sentry/src/main/java/io/sentry/NoOpScopes.java | 6 ------ sentry/src/main/java/io/sentry/Scopes.java | 6 ------ sentry/src/main/java/io/sentry/ScopesAdapter.java | 6 ------ 8 files changed, 1 insertion(+), 48 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ddea3df1386..165c52873d7 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -29,7 +29,7 @@ public final class io/sentry/Attachment { public abstract interface class io/sentry/BackfillingEventProcessor : io/sentry/EventProcessor { } -public class io/sentry/Baggage { +public final class io/sentry/Baggage { public static final field NOOP Lio/sentry/Baggage; public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V @@ -600,7 +600,6 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -665,7 +664,6 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -892,7 +890,6 @@ public abstract interface class io/sentry/IScopes { public abstract fun getLastEventId ()Lio/sentry/protocol/SentryId; public abstract fun getOptions ()Lio/sentry/SentryOptions; public abstract fun getParentScopes ()Lio/sentry/IScopes; - public abstract fun getPropagationContext ()Lio/sentry/PropagationContext; public abstract fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public abstract fun getScope ()Lio/sentry/IScope; public abstract fun getSpan ()Lio/sentry/ISpan; @@ -1415,7 +1412,6 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -1575,7 +1571,6 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -2178,7 +2173,6 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -2243,7 +2237,6 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 8dbcff4ef6e..fc2f9c15dcd 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -344,12 +344,6 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return Sentry.getCurrentScopes().getPropagationContext(); - } - @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 4427f33c4bc..591852a9adf 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -342,10 +342,4 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return scopes.captureReplay(replay, hint); } - - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return scopes.getPropagationContext(); - } } diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index 2ceb5c4f52c..e07de9c327c 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -689,8 +689,4 @@ default boolean isNoOp() { @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint); - - @ApiStatus.Internal - @NotNull - PropagationContext getPropagationContext(); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index 7ef1a52cdec..d3e0b010c39 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -297,12 +297,6 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return PropagationContext.NOOP; - } - @Override public @Nullable RateLimiter getRateLimiter() { return null; diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 779a67024a9..8255569387d 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -301,10 +301,4 @@ public boolean isNoOp() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return SentryId.EMPTY_ID; } - - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return PropagationContext.NOOP; - } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 773fdc0e04d..14025a1d774 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1068,12 +1068,6 @@ public void reportFullyDisplayed() { return sentryId; } - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return getCombinedScopeView().getPropagationContext(); - } - @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 794449305e1..6df6deee3d4 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -347,10 +347,4 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return Sentry.getCurrentScopes().captureReplay(replay, hint); } - - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return Sentry.getCurrentScopes().getPropagationContext(); - } } From f13e331f327549f3b2fc33b5bbb52e6e57a03da1 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 11:50:02 +0100 Subject: [PATCH 11/29] remove noop baggage and propagation context again --- sentry/src/main/java/io/sentry/Baggage.java | 2 -- sentry/src/main/java/io/sentry/PropagationContext.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index acd5fdb6e0a..155b455c6db 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -27,8 +27,6 @@ @ApiStatus.Experimental public final class Baggage { - public static final @NotNull Baggage NOOP = - new Baggage(new HashMap<>(), null, false, true, NoOpLogger.getInstance()); static final @NotNull String CHARSET = "UTF-8"; static final @NotNull Integer MAX_BAGGAGE_STRING_LENGTH = 8192; static final @NotNull Integer MAX_BAGGAGE_LIST_MEMBER_COUNT = 64; diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index d863955bdb0..791cb1d3d36 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -12,9 +12,6 @@ @ApiStatus.Internal public final class PropagationContext { - public static @NotNull PropagationContext NOOP = - new PropagationContext(SentryId.EMPTY_ID, SpanId.EMPTY_ID, null, Baggage.NOOP, false); - public static PropagationContext fromHeaders( final @NotNull ILogger logger, final @Nullable String sentryTraceHeader, From 95d331e3f0e0015c809b89391ff806f9df4df0a8 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 11 Feb 2025 06:41:34 +0100 Subject: [PATCH 12/29] fix outbox sender; test; cleanup api file --- sentry/api/sentry.api | 2 - .../src/main/java/io/sentry/OutboxSender.java | 4 +- .../test/java/io/sentry/OutboxSenderTest.kt | 59 +++++++++++++++++++ .../envelope-transaction-with-sample-rand.txt | 3 + 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 sentry/src/test/resources/envelope-transaction-with-sample-rand.txt diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 165c52873d7..eb63ba38c4a 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -30,7 +30,6 @@ public abstract interface class io/sentry/BackfillingEventProcessor : io/sentry/ } public final class io/sentry/Baggage { - public static final field NOOP Lio/sentry/Baggage; public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V public fun (Ljava/util/Map;Ljava/lang/String;ZZLio/sentry/ILogger;)V @@ -1946,7 +1945,6 @@ public final class io/sentry/ProfilingTransactionData$JsonKeys { } public final class io/sentry/PropagationContext { - public static field NOOP Lio/sentry/PropagationContext; public fun ()V public fun (Lio/sentry/PropagationContext;)V public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V diff --git a/sentry/src/main/java/io/sentry/OutboxSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java index b878606c00b..cbe4f6ee007 100644 --- a/sentry/src/main/java/io/sentry/OutboxSender.java +++ b/sentry/src/main/java/io/sentry/OutboxSender.java @@ -244,10 +244,10 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN "Invalid sample rate parsed from TraceContext: %s", sampleRateString); } else { - final @Nullable String sampleRandString = traceContext.getSampleRate(); + final @Nullable String sampleRandString = traceContext.getSampleRand(); if (sampleRandString != null) { final Double sampleRand = Double.parseDouble(sampleRandString); - if (!SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { + if (SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { return new TracesSamplingDecision(true, sampleRate, sampleRand); } } diff --git a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt index 8a1850e7ddc..9136494ddf4 100644 --- a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt +++ b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt @@ -23,6 +23,7 @@ import java.util.Date import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue class OutboxSenderTest { @@ -141,6 +142,63 @@ class OutboxSenderTest { whenever(fixture.scopes.options).thenReturn(fixture.options) whenever(fixture.options.transactionProfiler).thenReturn(NoOpTransactionProfiler.getInstance()) + val transactionContext = TransactionContext("fixture-name", "http") + transactionContext.description = "fixture-request" + transactionContext.status = SpanStatus.OK + transactionContext.setTag("fixture-tag", "fixture-value") + transactionContext.samplingDecision = TracesSamplingDecision(true, 0.00000021, 0.021) + + val sentryTracer = SentryTracer(transactionContext, fixture.scopes) + val span = sentryTracer.startChild("child") + span.finish(SpanStatus.OK) + sentryTracer.finish() + + val sentryTracerSpy = spy(sentryTracer) + whenever(sentryTracerSpy.eventId).thenReturn(SentryId("3367f5196c494acaae85bbbd535379ac")) + + val expected = SentryTransaction(sentryTracerSpy) + whenever(fixture.serializer.deserialize(any(), eq(SentryTransaction::class.java))).thenReturn(expected) + + val sut = fixture.getSut() + val path = getTempEnvelope(fileName = "envelope-transaction-with-sample-rand.txt") + assertTrue(File(path).exists()) + + val hints = HintUtils.createWithTypeCheckHint(mock()) + sut.processEnvelopeFile(path, hints) + + verify(fixture.scopes).captureTransaction( + check { + assertEquals(expected, it) + assertTrue(it.isSampled) + assertEquals(0.00000021, it.samplingDecision?.sampleRate) + assertEquals(0.021, it.samplingDecision?.sampleRand) + assertTrue(it.samplingDecision!!.sampled) + }, + check { + assertEquals("b156a475de54423d9c1571df97ec7eb6", it.traceId.toString()) + assertEquals("key", it.publicKey) + assertEquals("0.00000021", it.sampleRate) + assertEquals("1.0-beta.1", it.release) + assertEquals("prod", it.environment) + assertEquals("usr1", it.userId) + assertEquals("tx1", it.transaction) + }, + any() + ) + assertFalse(File(path).exists()) + + // Additionally make sure we have no errors logged + verify(fixture.logger, never()).log(eq(SentryLevel.ERROR), any(), any()) + verify(fixture.logger, never()).log(eq(SentryLevel.ERROR), any(), any()) + } + + @Test + fun `backfills sampleRand`() { + fixture.envelopeReader = EnvelopeReader(JsonSerializer(fixture.options)) + whenever(fixture.options.maxSpans).thenReturn(1000) + whenever(fixture.scopes.options).thenReturn(fixture.options) + whenever(fixture.options.transactionProfiler).thenReturn(NoOpTransactionProfiler.getInstance()) + val transactionContext = TransactionContext("fixture-name", "http") transactionContext.description = "fixture-request" transactionContext.status = SpanStatus.OK @@ -170,6 +228,7 @@ class OutboxSenderTest { assertEquals(expected, it) assertTrue(it.isSampled) assertEquals(0.00000021, it.samplingDecision?.sampleRate) + assertNotNull(it.samplingDecision?.sampleRand) assertTrue(it.samplingDecision!!.sampled) }, check { diff --git a/sentry/src/test/resources/envelope-transaction-with-sample-rand.txt b/sentry/src/test/resources/envelope-transaction-with-sample-rand.txt new file mode 100644 index 00000000000..1a6b3120b81 --- /dev/null +++ b/sentry/src/test/resources/envelope-transaction-with-sample-rand.txt @@ -0,0 +1,3 @@ +{"event_id":"3367f5196c494acaae85bbbd535379ac","trace":{"trace_id":"b156a475de54423d9c1571df97ec7eb6","public_key":"key","release":"1.0-beta.1","environment":"prod","user_id":"usr1","transaction":"tx1","sample_rate":"0.00000021","sample_rand":"0.021"}} +{"type":"transaction","length":640,"content_type":"application/json"} +{"transaction":"a-transaction","type":"transaction","start_timestamp":"2020-10-23T10:24:01.791Z","timestamp":"2020-10-23T10:24:02.791Z","event_id":"3367f5196c494acaae85bbbd535379ac","contexts":{"trace":{"trace_id":"b156a475de54423d9c1571df97ec7eb6","span_id":"0a53026963414893","op":"http","status":"ok"},"custom":{"some-key":"some-value"}},"spans":[{"start_timestamp":"2021-03-05T08:51:12.838Z","timestamp":"2021-03-05T08:51:12.949Z","trace_id":"2b099185293344a5bfdd7ad89ebf9416","span_id":"5b95c29a5ded4281","parent_span_id":"a3b2d1d58b344b07","op":"PersonService.create","description":"desc","status":"aborted","tags":{"name":"value"}}]} From 86f26c239ee4d2055b89697d9395fc67a69d9e33 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 11 Feb 2025 13:05:24 +0100 Subject: [PATCH 13/29] Update sampleRate in DSC --- .../OtelSentrySpanProcessor.java | 1 + sentry/api/sentry.api | 2 + sentry/src/main/java/io/sentry/Baggage.java | 45 ++++++++++++++++++- .../src/main/java/io/sentry/SpanContext.java | 7 ++- sentry/src/test/java/io/sentry/BaggageTest.kt | 39 ++++++++++++++++ .../test/java/io/sentry/SpanContextTest.kt | 11 +++++ 6 files changed, 102 insertions(+), 3 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index 37293569aee..6469ea92099 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -83,6 +83,7 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri new SentryId(traceId), sentrySpanId, sentryParentSpanId, baggage, sampled); baggage = propagationContext.getBaggage(); + baggage.setValuesFromSamplingDecision(samplingDecision); updatePropagationContext(scopes, propagationContext); } diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index eb63ba38c4a..51ff5b390a0 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -33,6 +33,7 @@ public final class io/sentry/Baggage { public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V public fun (Ljava/util/Map;Ljava/lang/String;ZZLio/sentry/ILogger;)V + public fun forceSetSampleRate (Ljava/lang/String;)V public fun freeze ()V public static fun fromEvent (Lio/sentry/SentryEvent;Lio/sentry/SentryOptions;)Lio/sentry/Baggage; public static fun fromHeader (Ljava/lang/String;)Lio/sentry/Baggage; @@ -70,6 +71,7 @@ public final class io/sentry/Baggage { public fun setTraceId (Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUserId (Ljava/lang/String;)V + public fun setValuesFromSamplingDecision (Lio/sentry/TracesSamplingDecision;)V public fun setValuesFromScope (Lio/sentry/IScope;Lio/sentry/SentryOptions;)V public fun setValuesFromTransaction (Lio/sentry/protocol/SentryId;Lio/sentry/protocol/SentryId;Lio/sentry/SentryOptions;Lio/sentry/TracesSamplingDecision;Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V public fun toHeaderString (Ljava/lang/String;)Ljava/lang/String; diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 155b455c6db..1979ceb77f5 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -356,6 +356,11 @@ public void setSampleRate(final @Nullable String sampleRate) { set(DSCKeys.SAMPLE_RATE, sampleRate); } + @ApiStatus.Internal + public void forceSetSampleRate(final @Nullable String sampleRate) { + set(DSCKeys.SAMPLE_RATE, sampleRate, true); + } + @ApiStatus.Internal public @Nullable String getSampleRand() { return get(DSCKeys.SAMPLE_RAND); @@ -388,7 +393,18 @@ public void setReplayId(final @Nullable String replayId) { @ApiStatus.Internal public void set(final @NotNull String key, final @Nullable String value) { - if (mutable) { + set(key, value, false); + } + + /** + * Sets / updates a value + * + * @param key key + * @param value value to set + * @param force ignores mutability of this baggage and sets the value anyways + */ + private void set(final @NotNull String key, final @Nullable String value, final boolean force) { + if (mutable || force) { this.keyValues.put(key, value); } } @@ -428,6 +444,25 @@ public void setValuesFromTransaction( } setSampleRate(sampleRateToString(sampleRate(samplingDecision))); setSampled(StringUtils.toString(sampled(samplingDecision))); + setSampleRand(sampleRateToString(sampleRand(samplingDecision))); // TODO check + } + + @ApiStatus.Internal + public void setValuesFromSamplingDecision( + final @Nullable TracesSamplingDecision samplingDecision) { + if (samplingDecision == null) { + return; + } + + setSampled(StringUtils.toString(sampled(samplingDecision))); + + if (samplingDecision.getSampleRand() != null) { + setSampleRand(sampleRateToString(sampleRand(samplingDecision))); + } + + if (samplingDecision.getSampleRate() != null) { + forceSetSampleRate(sampleRateToString(sampleRate(samplingDecision))); + } } @ApiStatus.Internal @@ -455,6 +490,14 @@ public void setValuesFromScope( return samplingDecision.getSampleRate(); } + private static @Nullable Double sampleRand(@Nullable TracesSamplingDecision samplingDecision) { + if (samplingDecision == null) { + return null; + } + + return samplingDecision.getSampleRand(); + } + private static @Nullable String sampleRateToString(@Nullable Double sampleRateAsDouble) { if (!SampleRateUtils.isValidTracesSampleRate(sampleRateAsDouble, false)) { return null; diff --git a/sentry/src/main/java/io/sentry/SpanContext.java b/sentry/src/main/java/io/sentry/SpanContext.java index 91e3abd9560..6f1e4e4eaf9 100644 --- a/sentry/src/main/java/io/sentry/SpanContext.java +++ b/sentry/src/main/java/io/sentry/SpanContext.java @@ -91,10 +91,10 @@ public SpanContext( this.spanId = Objects.requireNonNull(spanId, "spanId is required"); this.op = Objects.requireNonNull(operation, "operation is required"); this.parentSpanId = parentSpanId; - this.samplingDecision = samplingDecision; this.description = description; this.status = status; this.origin = origin; + setSamplingDecision(samplingDecision); } /** @@ -106,7 +106,7 @@ public SpanContext(final @NotNull SpanContext spanContext) { this.traceId = spanContext.traceId; this.spanId = spanContext.spanId; this.parentSpanId = spanContext.parentSpanId; - this.samplingDecision = spanContext.samplingDecision; + setSamplingDecision(spanContext.samplingDecision); this.op = spanContext.op; this.description = spanContext.description; this.status = spanContext.status; @@ -209,6 +209,9 @@ public void setSampled(final @Nullable Boolean sampled, final @Nullable Boolean @ApiStatus.Internal public void setSamplingDecision(final @Nullable TracesSamplingDecision samplingDecision) { this.samplingDecision = samplingDecision; + if (this.baggage != null) { + this.baggage.setValuesFromSamplingDecision(this.samplingDecision); + } } public @Nullable String getOrigin() { diff --git a/sentry/src/test/java/io/sentry/BaggageTest.kt b/sentry/src/test/java/io/sentry/BaggageTest.kt index 242d10dff2e..48417f73e77 100644 --- a/sentry/src/test/java/io/sentry/BaggageTest.kt +++ b/sentry/src/test/java/io/sentry/BaggageTest.kt @@ -570,6 +570,45 @@ class BaggageTest { assertFalse(baggage.isShouldFreeze) } + @Test + fun `sets values from traces sampling decision`() { + val baggage = Baggage.fromHeader("a=b,c=d") + baggage.setValuesFromSamplingDecision(TracesSamplingDecision(true, 0.021, 0.025)) + + assertEquals("true", baggage.sampled) + assertEquals("0.021", baggage.sampleRate) + assertEquals("0.025", baggage.sampleRand) + } + + @Test + fun `handles null traces sampling decision`() { + val baggage = Baggage.fromHeader("a=b,c=d") + baggage.setValuesFromSamplingDecision(null) + } + + @Test + fun `sets values from traces sampling decision only if non null`() { + val baggage = Baggage.fromHeader("a=b,c=d") + baggage.setValuesFromSamplingDecision(TracesSamplingDecision(true, 0.021, 0.025)) + baggage.setValuesFromSamplingDecision(TracesSamplingDecision(false, null, null)) + + assertEquals("false", baggage.sampled) + assertEquals("0.021", baggage.sampleRate) + assertEquals("0.025", baggage.sampleRand) + } + + @Test + fun `replaces only sample rate if already frozen`() { + val baggage = Baggage.fromHeader("a=b,c=d") + baggage.setValuesFromSamplingDecision(TracesSamplingDecision(true, 0.021, 0.025)) + baggage.freeze() + baggage.setValuesFromSamplingDecision(TracesSamplingDecision(false, 0.121, 0.125)) + + assertEquals("true", baggage.sampled) + assertEquals("0.121", baggage.sampleRate) + assertEquals("0.025", baggage.sampleRand) + } + /** * token = 1*tchar * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" diff --git a/sentry/src/test/java/io/sentry/SpanContextTest.kt b/sentry/src/test/java/io/sentry/SpanContextTest.kt index 5e7ba9de254..0935c10e1f1 100644 --- a/sentry/src/test/java/io/sentry/SpanContextTest.kt +++ b/sentry/src/test/java/io/sentry/SpanContextTest.kt @@ -19,4 +19,15 @@ class SpanContextTest { trace.setTag("tagName", "tagValue") assertEquals("tagValue", trace.tags["tagName"]) } + + @Test + fun `updates sampling decision on baggage`() { + val trace = SpanContext("op") + trace.baggage = Baggage.fromHeader("a=b") + trace.samplingDecision = TracesSamplingDecision(true, 0.1, 0.2) + + assertEquals("true", trace.baggage?.sampled) + assertEquals("0.1", trace.baggage?.sampleRate) + assertEquals("0.2", trace.baggage?.sampleRand) + } } From b5736554f4b629d561c81f884eb381344d42247b Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 29 Jan 2025 10:32:15 +0100 Subject: [PATCH 14/29] wip --- .../opentelemetry/OtelSentryPropagator.java | 4 +++ .../OtelSentrySpanProcessor.java | 10 +++++- .../sentry/opentelemetry/SentrySampler.java | 5 +-- .../spring/boot/jakarta/ApiService.java | 23 ++++++++++++ .../spring/boot/jakarta/PersonController.java | 5 ++- sentry/api/sentry.api | 10 +++++- sentry/src/main/java/io/sentry/Baggage.java | 33 +++++++++++++++++ .../java/io/sentry/PropagationContext.java | 35 ++++++++++++++++--- .../main/java/io/sentry/SamplingContext.java | 21 +++++++++++ sentry/src/main/java/io/sentry/Scopes.java | 5 ++- sentry/src/main/java/io/sentry/Sentry.java | 4 ++- .../src/main/java/io/sentry/SentryTracer.java | 11 +++++- .../main/java/io/sentry/TracesSampler.java | 21 ++++------- .../java/io/sentry/util/TracingUtils.java | 3 ++ sentry/src/test/java/io/sentry/ScopeTest.kt | 2 +- 15 files changed, 164 insertions(+), 28 deletions(-) create mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index c5802df2454..580b1e99c33 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -78,6 +78,8 @@ public void inject(final Context context, final C carrier, final TextMapSett final @Nullable BaggageHeader baggageHeader = sentrySpan.toBaggageHeader(Collections.emptyList()); if (baggageHeader != null) { + System.out.println("outgoing baggage:"); + System.out.println(baggageHeader.getValue()); setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); } } @@ -101,6 +103,8 @@ public Context extract( SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(sentryTraceString); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); + System.out.println("incoming baggage:"); + System.out.println(baggageString); final Baggage baggage = Baggage.fromHeader(baggageString); final @NotNull TraceState traceState = TraceState.getDefault(); diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index 521bc9020c0..90e430db6fc 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -82,10 +82,18 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri } final @Nullable Boolean sampled = isSampled(otelSpan, samplingDecision); + // TODO do not access isolation scope directly + final @Nullable Double sampleRand = + scopes.getIsolationScope().getPropagationContext().getSampleRand(); final @NotNull PropagationContext propagationContext = new PropagationContext( - new SentryId(traceId), sentrySpanId, sentryParentSpanId, baggage, sampled); + new SentryId(traceId), + sentrySpanId, + sentryParentSpanId, + baggage, + sampled, + sampleRand); updatePropagationContext(scopes, propagationContext); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java index 6f35fcb9c51..06c5f141ab5 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java @@ -86,7 +86,7 @@ public SamplingResult shouldSample( SpanId randomSpanId = new SpanId(); final @NotNull PropagationContext propagationContext = sentryTraceHeader == null - ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null) + ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null, null) : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId); final @NotNull TransactionContext transactionContext = @@ -95,7 +95,8 @@ public SamplingResult shouldSample( scopes .getOptions() .getInternalTracesSampler() - .sample(new SamplingContext(transactionContext, null)); + .sample( + new SamplingContext(transactionContext, null, propagationContext.getSampleRand())); if (!sentryDecision.getSampled()) { scopes diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java new file mode 100644 index 00000000000..c895b8b979c --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java @@ -0,0 +1,23 @@ +package io.sentry.samples.spring.boot.jakarta; + +import io.sentry.spring.jakarta.tracing.SentrySpan; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +@Service +public class ApiService { + + private final RestClient restClient; + + public ApiService(RestClient restClient) { + this.restClient = restClient; + } + + @SentrySpan("annotation-span") + void apiRequest(final @NotNull String name) { + // restClient.get().uri("http://localhost:8000?q={name}", + // name).retrieve().body(String.class); + restClient.get().uri("http://localhost:8081/articles").retrieve().body(String.class); + } +} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java index 5ce11d0a528..fe79122f5ff 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java @@ -20,11 +20,13 @@ public class PersonController { private final PersonService personService; private final Tracer tracer; + private final ApiService apiService; private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); - public PersonController(PersonService personService, Tracer tracer) { + public PersonController(PersonService personService, Tracer tracer, ApiService apiService) { this.personService = personService; this.tracer = tracer; + this.apiService = apiService; } @GetMapping("{id}") @@ -34,6 +36,7 @@ Person person(@PathVariable Long id) { ISpan currentSpan = Sentry.getSpan(); ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi"); try { + apiService.apiRequest(id.toString()); LOGGER.error("Trying person with id={}", id, new RuntimeException("error while loading")); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } finally { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 662d87c2650..55421bcad63 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -46,6 +46,8 @@ public final class io/sentry/Baggage { public fun getPublicKey ()Ljava/lang/String; public fun getRelease ()Ljava/lang/String; public fun getReplayId ()Ljava/lang/String; + public fun getSampleRand ()Ljava/lang/String; + public fun getSampleRandDouble ()Ljava/lang/Double; public fun getSampleRate ()Ljava/lang/String; public fun getSampleRateDouble ()Ljava/lang/Double; public fun getSampled ()Ljava/lang/String; @@ -60,6 +62,8 @@ public final class io/sentry/Baggage { public fun setPublicKey (Ljava/lang/String;)V public fun setRelease (Ljava/lang/String;)V public fun setReplayId (Ljava/lang/String;)V + public fun setSampleRand (Ljava/lang/String;)V + public fun setSampleRandDouble (Ljava/lang/Double;)V public fun setSampleRate (Ljava/lang/String;)V public fun setSampled (Ljava/lang/String;)V public fun setTraceId (Ljava/lang/String;)V @@ -78,6 +82,7 @@ public final class io/sentry/Baggage$DSCKeys { public static final field RELEASE Ljava/lang/String; public static final field REPLAY_ID Ljava/lang/String; public static final field SAMPLED Ljava/lang/String; + public static final field SAMPLE_RAND Ljava/lang/String; public static final field SAMPLE_RATE Ljava/lang/String; public static final field TRACE_ID Ljava/lang/String; public static final field TRANSACTION Ljava/lang/String; @@ -1941,12 +1946,13 @@ public final class io/sentry/ProfilingTransactionData$JsonKeys { public final class io/sentry/PropagationContext { public fun ()V public fun (Lio/sentry/PropagationContext;)V - public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V + public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;)V public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext; public fun getBaggage ()Lio/sentry/Baggage; public fun getParentSpanId ()Lio/sentry/SpanId; + public fun getSampleRand ()Ljava/lang/Double; public fun getSpanId ()Lio/sentry/SpanId; public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun isSampled ()Ljava/lang/Boolean; @@ -2007,7 +2013,9 @@ public final class io/sentry/RequestDetails { public final class io/sentry/SamplingContext { public fun (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;)V + public fun (Lio/sentry/TransactionContext;Lio/sentry/CustomSamplingContext;Ljava/lang/Double;)V public fun getCustomSamplingContext ()Lio/sentry/CustomSamplingContext; + public fun getSampleRand ()Ljava/lang/Double; public fun getTransactionContext ()Lio/sentry/TransactionContext; } diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 05a59d7053f..44243fd6220 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -335,6 +335,21 @@ public void setSampleRate(final @Nullable String sampleRate) { set(DSCKeys.SAMPLE_RATE, sampleRate); } + @ApiStatus.Internal + public @Nullable String getSampleRand() { + return get(DSCKeys.SAMPLE_RAND); + } + + @ApiStatus.Internal + public void setSampleRand(final @Nullable String sampleRand) { + set(DSCKeys.SAMPLE_RAND, sampleRand); + } + + @ApiStatus.Internal + public void setSampleRandDouble(final @Nullable Double sampleRand) { + setSampleRand(sampleRateToString(sampleRand)); + } + @ApiStatus.Internal public void setSampled(final @Nullable String sampled) { set(DSCKeys.SAMPLED, sampled); @@ -459,6 +474,22 @@ private static boolean isHighQualityTransactionName( return null; } + @ApiStatus.Internal + public @Nullable Double getSampleRandDouble() { + final String sampleRandString = getSampleRand(); + if (sampleRandString != null) { + try { + double sampleRand = Double.parseDouble(sampleRandString); + if (SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { + return sampleRand; + } + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + @ApiStatus.Internal @Nullable public TraceContext toTraceContext() { @@ -494,6 +525,7 @@ public static final class DSCKeys { public static final String ENVIRONMENT = "sentry-environment"; public static final String TRANSACTION = "sentry-transaction"; public static final String SAMPLE_RATE = "sentry-sample_rate"; + public static final String SAMPLE_RAND = "sentry-sample_rand"; public static final String SAMPLED = "sentry-sampled"; public static final String REPLAY_ID = "sentry-replay_id"; @@ -506,6 +538,7 @@ public static final class DSCKeys { ENVIRONMENT, TRANSACTION, SAMPLE_RATE, + SAMPLE_RAND, SAMPLED, REPLAY_ID); } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index b0debc2a9d1..703826488f0 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -2,6 +2,7 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; +import io.sentry.util.SentryRandom; import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -47,7 +48,8 @@ public static PropagationContext fromHeaders( spanIdToUse, sentryTraceHeader.getSpanId(), baggage, - sentryTraceHeader.isSampled()); + sentryTraceHeader.isSampled(), + null); } private @NotNull SentryId traceId; @@ -55,11 +57,12 @@ public static PropagationContext fromHeaders( private @Nullable SpanId parentSpanId; private @Nullable Boolean sampled; + private @NotNull Double sampleRand; private @Nullable Baggage baggage; public PropagationContext() { - this(new SentryId(), new SpanId(), null, null, null); + this(new SentryId(), new SpanId(), null, null, null, null); } public PropagationContext(final @NotNull PropagationContext propagationContext) { @@ -68,7 +71,8 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.getSpanId(), propagationContext.getParentSpanId(), cloneBaggage(propagationContext.getBaggage()), - propagationContext.isSampled()); + propagationContext.isSampled(), + propagationContext.getSampleRand()); } private static @Nullable Baggage cloneBaggage(final @Nullable Baggage baggage) { @@ -84,12 +88,31 @@ public PropagationContext( final @NotNull SpanId spanId, final @Nullable SpanId parentSpanId, final @Nullable Baggage baggage, - final @Nullable Boolean sampled) { + final @Nullable Boolean sampled, + final @Nullable Double sampleRand) { this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; this.baggage = baggage; this.sampled = sampled; + if (sampleRand != null) { + this.sampleRand = sampleRand; + } else if (baggage != null && baggage.getSampleRandDouble() != null) { + this.sampleRand = baggage.getSampleRandDouble(); + } else { + final @Nullable Double sampleRate = baggage == null ? null : baggage.getSampleRateDouble(); + final @NotNull Double sampleRandToUse = SentryRandom.current().nextDouble(); + + if (sampled != null && sampleRate != null) { + if (sampled) { + this.sampleRand = sampleRandToUse * sampleRate; + } else { + this.sampleRand = sampleRate + (sampleRandToUse * (1 - sampleRate)); + } + } else { + this.sampleRand = sampleRandToUse; + } + } } public @NotNull SentryId getTraceId() { @@ -145,4 +168,8 @@ public void setSampled(final @Nullable Boolean sampled) { spanContext.setOrigin("auto"); return spanContext; } + + public @NotNull Double getSampleRand() { + return sampleRand; + } } diff --git a/sentry/src/main/java/io/sentry/SamplingContext.java b/sentry/src/main/java/io/sentry/SamplingContext.java index 60944fbd433..711c03e21c5 100644 --- a/sentry/src/main/java/io/sentry/SamplingContext.java +++ b/sentry/src/main/java/io/sentry/SamplingContext.java @@ -1,6 +1,8 @@ package io.sentry; import io.sentry.util.Objects; +import io.sentry.util.SentryRandom; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,13 +13,28 @@ public final class SamplingContext { private final @NotNull TransactionContext transactionContext; private final @Nullable CustomSamplingContext customSamplingContext; + private final @NotNull Double sampleRand; + @Deprecated + @SuppressWarnings("InlineMeSuggester") + /** + * @deprecated creating a SamplingContext is something only the SDK should do + */ public SamplingContext( final @NotNull TransactionContext transactionContext, final @Nullable CustomSamplingContext customSamplingContext) { + this(transactionContext, customSamplingContext, SentryRandom.current().nextDouble()); + } + + @ApiStatus.Internal + public SamplingContext( + final @NotNull TransactionContext transactionContext, + final @Nullable CustomSamplingContext customSamplingContext, + final @NotNull Double sampleRand) { this.transactionContext = Objects.requireNonNull(transactionContext, "transactionContexts is required"); this.customSamplingContext = customSamplingContext; + this.sampleRand = sampleRand; } public @Nullable CustomSamplingContext getCustomSamplingContext() { @@ -27,4 +44,8 @@ public SamplingContext( public @NotNull TransactionContext getTransactionContext() { return transactionContext; } + + public @NotNull Double getSampleRand() { + return sampleRand; + } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 2b0d5103686..4b4f8f8a877 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -857,8 +857,11 @@ public void flush(long timeoutMillis) { SentryLevel.INFO, "Tracing is disabled and this 'startTransaction' returns a no-op."); transaction = NoOpTransaction.getInstance(); } else { + final @NotNull Double sampleRand = + getCombinedScopeView().getPropagationContext().getSampleRand(); final SamplingContext samplingContext = - new SamplingContext(transactionContext, transactionOptions.getCustomSamplingContext()); + new SamplingContext( + transactionContext, transactionOptions.getCustomSamplingContext(), sampleRand); final @NotNull TracesSampler tracesSampler = getOptions().getInternalTracesSampler(); @NotNull TracesSamplingDecision samplingDecision = tracesSampler.sample(samplingContext); transactionContext.setSamplingDecision(samplingDecision); diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 94007e42c56..bd5f296b7c2 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -22,6 +22,7 @@ import io.sentry.util.InitUtil; import io.sentry.util.LoadClass; import io.sentry.util.Platform; +import io.sentry.util.SentryRandom; import io.sentry.util.thread.IThreadChecker; import io.sentry.util.thread.NoOpThreadChecker; import io.sentry.util.thread.ThreadChecker; @@ -458,7 +459,8 @@ private static void handleAppStartProfilingConfig( final @NotNull SentryOptions options) { TransactionContext appStartTransactionContext = new TransactionContext("app.launch", "profile"); appStartTransactionContext.setForNextAppStart(true); - SamplingContext appStartSamplingContext = new SamplingContext(appStartTransactionContext, null); + SamplingContext appStartSamplingContext = + new SamplingContext(appStartTransactionContext, null, SentryRandom.current().nextDouble()); return options.getInternalTracesSampler().sample(appStartSamplingContext); } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 65901a2e1ab..ca4f4d67ee2 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -657,6 +657,7 @@ private void updateBaggageValues() { scope -> { replayId.set(scope.getReplayId()); }); + // TODO sampleRand? baggage.setValuesFromTransaction( getSpanContext().getTraceId(), replayId.get(), @@ -674,7 +675,15 @@ private void updateBaggageValues() { if (scopes.getOptions().isTraceSampling()) { updateBaggageValues(); - return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + BaggageHeader baggageHeader = + BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + if (baggageHeader != null) { + System.out.println(baggageHeader.getName()); + System.out.println(baggageHeader.getValue()); + } else { + System.out.println("baggage header null in SentryTracer"); + } + return baggageHeader; } else { return null; } diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 3ce28ab7451..1d1ad6470ac 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -2,7 +2,6 @@ import io.sentry.util.Objects; import io.sentry.util.Random; -import io.sentry.util.SentryRandom; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,7 +10,6 @@ @ApiStatus.Internal public final class TracesSampler { private final @NotNull SentryOptions options; - private final @Nullable Random random; public TracesSampler(final @NotNull SentryOptions options) { this(Objects.requireNonNull(options, "options are required"), null); @@ -20,12 +18,12 @@ public TracesSampler(final @NotNull SentryOptions options) { @TestOnly TracesSampler(final @NotNull SentryOptions options, final @Nullable Random random) { this.options = options; - this.random = random; } @SuppressWarnings("deprecation") @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { + final @NotNull Double sampleRand = samplingContext.getSampleRand(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { @@ -45,7 +43,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont if (profilesSampleRate == null) { profilesSampleRate = options.getProfilesSampleRate(); } - Boolean profilesSampled = profilesSampleRate != null && sample(profilesSampleRate); + Boolean profilesSampled = profilesSampleRate != null && sample(profilesSampleRate, sampleRand); if (options.getTracesSampler() != null) { Double samplerResult = null; @@ -58,7 +56,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont } if (samplerResult != null) { return new TracesSamplingDecision( - sample(samplerResult), samplerResult, profilesSampled, profilesSampleRate); + sample(samplerResult, sampleRand), samplerResult, profilesSampled, profilesSampleRate); } } @@ -76,7 +74,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont if (downsampledTracesSampleRate != null) { return new TracesSamplingDecision( - sample(downsampledTracesSampleRate), + sample(downsampledTracesSampleRate, sampleRand), downsampledTracesSampleRate, profilesSampled, profilesSampleRate); @@ -85,14 +83,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont return new TracesSamplingDecision(false, null, false, null); } - private boolean sample(final @NotNull Double aDouble) { - return !(aDouble < getRandom().nextDouble()); - } - - private Random getRandom() { - if (random == null) { - return SentryRandom.current(); - } - return random; + private boolean sample(final @NotNull Double sampleRate, final @NotNull Double sampleRand) { + return !(sampleRate < sampleRand); } } diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index 16655be634c..be7a7355d37 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -85,6 +85,9 @@ public static void startNewTrace(final @NotNull IScopes scopes) { baggage = new Baggage(sentryOptions.getLogger()); propagationContext.setBaggage(baggage); } + if (baggage.getSampleRand() != null) { + baggage.setSampleRandDouble(propagationContext.getSampleRand()); + } if (baggage.isMutable()) { baggage.setValuesFromScope(scope, sentryOptions); baggage.freeze(); diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index b8025735e8a..0d13f0d21ae 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -828,7 +828,7 @@ class ScopeTest { } val scope = Scope(options) - scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null) + scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null, null) verify(observer).setTrace( argThat { traceId.toString() == "64cf554cc8d74c6eafa3e08b7c984f6d" }, eq(scope) From 6cb3b5457ad4f123e186813ca6841a4b05000697 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 6 Feb 2025 06:12:42 +0100 Subject: [PATCH 15/29] wip2 --- .../api/sentry-opentelemetry-bootstrap.api | 1 + .../InternalSemanticAttributes.java | 2 + .../sentry/opentelemetry/OtelSpanFactory.java | 2 + .../opentelemetry/OtelSamplingUtil.java | 7 +- .../opentelemetry/OtelSentryPropagator.java | 14 ++- .../OtelSentrySpanProcessor.java | 14 +-- .../sentry/opentelemetry/SentrySampler.java | 21 +++- .../opentelemetry/SentrySamplingResult.java | 1 + .../boot/jakarta/SentryDemoApplication.java | 65 +++++----- sentry/api/sentry.api | 7 +- sentry/src/main/java/io/sentry/Baggage.java | 114 +++++++++++++++++- .../src/main/java/io/sentry/OutboxSender.java | 8 ++ .../java/io/sentry/PropagationContext.java | 54 +++++---- .../src/main/java/io/sentry/SentryTracer.java | 2 +- .../src/main/java/io/sentry/TraceContext.java | 41 ++++++- .../main/java/io/sentry/TracesSampler.java | 8 +- .../io/sentry/TracesSamplingDecision.java | 24 +++- .../java/io/sentry/TransactionContext.java | 33 ++--- .../java/io/sentry/util/SampleRateUtils.java | 20 +++ .../java/io/sentry/util/TracingUtils.java | 17 +-- 20 files changed, 343 insertions(+), 112 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api index df7db47c652..adb976adc05 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api @@ -20,6 +20,7 @@ public final class io/sentry/opentelemetry/InternalSemanticAttributes { public static final field PROFILE_SAMPLED Lio/opentelemetry/api/common/AttributeKey; public static final field PROFILE_SAMPLE_RATE Lio/opentelemetry/api/common/AttributeKey; public static final field SAMPLED Lio/opentelemetry/api/common/AttributeKey; + public static final field SAMPLE_RAND Lio/opentelemetry/api/common/AttributeKey; public static final field SAMPLE_RATE Lio/opentelemetry/api/common/AttributeKey; public fun ()V } diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java index cb64d7bfffd..4795401266f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/InternalSemanticAttributes.java @@ -8,6 +8,8 @@ public final class InternalSemanticAttributes { public static final AttributeKey SAMPLED = AttributeKey.booleanKey("sentry.sampled"); public static final AttributeKey SAMPLE_RATE = AttributeKey.doubleKey("sentry.sample_rate"); + public static final AttributeKey SAMPLE_RAND = + AttributeKey.doubleKey("sentry.sample_rand"); public static final AttributeKey PARENT_SAMPLED = AttributeKey.booleanKey("sentry.parent_sampled"); public static final AttributeKey PROFILE_SAMPLED = diff --git a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java index 547463dcdca..7a51c3f337d 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java +++ b/sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java @@ -136,6 +136,8 @@ public OtelSpanFactory() { spanBuilder.setAttribute(InternalSemanticAttributes.SAMPLED, samplingDecision.getSampled()); spanBuilder.setAttribute( InternalSemanticAttributes.SAMPLE_RATE, samplingDecision.getSampleRate()); + spanBuilder.setAttribute( + InternalSemanticAttributes.SAMPLE_RAND, samplingDecision.getSampleRand()); spanBuilder.setAttribute( InternalSemanticAttributes.PROFILE_SAMPLED, samplingDecision.getProfileSampled()); spanBuilder.setAttribute( diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java index 01f414f9022..324f06b5399 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSamplingUtil.java @@ -14,13 +14,18 @@ public final class OtelSamplingUtil { final @Nullable Boolean sampled = attributes.get(InternalSemanticAttributes.SAMPLED); if (sampled != null) { final @Nullable Double sampleRate = attributes.get(InternalSemanticAttributes.SAMPLE_RATE); + final @Nullable Double sampleRand = attributes.get(InternalSemanticAttributes.SAMPLE_RAND); final @Nullable Boolean profileSampled = attributes.get(InternalSemanticAttributes.PROFILE_SAMPLED); final @Nullable Double profileSampleRate = attributes.get(InternalSemanticAttributes.PROFILE_SAMPLE_RATE); return new TracesSamplingDecision( - sampled, sampleRate, profileSampled == null ? false : profileSampled, profileSampleRate); + sampled, + sampleRate, + sampleRand, + profileSampled == null ? false : profileSampled, + profileSampleRate); } else { return null; } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index 580b1e99c33..ddcead442d7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -19,6 +19,8 @@ import io.sentry.SentryLevel; import io.sentry.SentryTraceHeader; import io.sentry.exception.InvalidSentryTraceHeaderException; +import io.sentry.util.TracingUtils; + import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -73,12 +75,15 @@ public void inject(final Context context, final C carrier, final TextMapSett return; } + // TODO can we use traceIfAllowed? do we have the URL here? + TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); + final @NotNull SentryTraceHeader sentryTraceHeader = sentrySpan.toSentryTrace(); setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); final @Nullable BaggageHeader baggageHeader = sentrySpan.toBaggageHeader(Collections.emptyList()); if (baggageHeader != null) { - System.out.println("outgoing baggage:"); + System.out.println("outgoing baggage: "); System.out.println(baggageHeader.getValue()); setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); } @@ -129,10 +134,9 @@ public Context extract( .getLogger() .log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId()); - final @NotNull PropagationContext propagationContext = - PropagationContext.fromHeaders( - scopes.getOptions().getLogger(), sentryTraceString, baggageString); - scopesToUse.getIsolationScope().setPropagationContext(propagationContext); +// final @NotNull PropagationContext propagationContext = +// PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); +// scopesToUse.getIsolationScope().setPropagationContext(propagationContext); return modifiedContext; } catch (InvalidSentryTraceHeaderException e) { diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index 90e430db6fc..ef329414d6b 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -70,21 +70,19 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri baggage = baggageFromContext; } - final @Nullable Boolean baggageMutable = - otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE_MUTABLE); +// final @Nullable Boolean baggageMutable = +// otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE_MUTABLE); final @Nullable String baggageString = otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE); if (baggageString != null) { baggage = Baggage.fromHeader(baggageString); - if (baggageMutable == true) { - baggage.freeze(); - } +// if (baggageMutable == false) { // TODO was this a bug? +// baggage.freeze(); +// } } final @Nullable Boolean sampled = isSampled(otelSpan, samplingDecision); - // TODO do not access isolation scope directly - final @Nullable Double sampleRand = - scopes.getIsolationScope().getPropagationContext().getSampleRand(); + final @Nullable Double sampleRand = samplingDecision == null ? null : samplingDecision.getSampleRand(); final @NotNull PropagationContext propagationContext = new PropagationContext( diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java index 06c5f141ab5..f83b69f5571 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java @@ -23,6 +23,7 @@ import io.sentry.TransactionContext; import io.sentry.clientreport.DiscardReason; import io.sentry.protocol.SentryId; +import io.sentry.util.SampleRateUtils; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -82,12 +83,25 @@ public SamplingResult shouldSample( baggage = baggageFromContext; } + final @Nullable Boolean sampledFromHeaders = + sentryTraceHeader == null ? null : sentryTraceHeader.isSampled(); + final @Nullable Double sampleRandFromHeaders = + baggage == null ? null : baggage.getSampleRandDouble(); + final @Nullable Double sampleRateFromHeaders = + baggage == null ? null : baggage.getSampleRateDouble(); + final @NotNull Double sampleRand = + SampleRateUtils.backfilledSampleRand( + sampleRandFromHeaders, sampleRateFromHeaders, sampledFromHeaders); + // there's no way to get the span id here, so we just use a random id for sampling SpanId randomSpanId = new SpanId(); + final @Nullable Baggage baggageToUse = baggage == null ? null : baggage.toReadOnly(); + // TODO this freezes baggage but should not final @NotNull PropagationContext propagationContext = sentryTraceHeader == null - ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null, null) - : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId); + ? new PropagationContext( + new SentryId(traceId), randomSpanId, null, baggageToUse, null, sampleRand) + : PropagationContext.fromHeaders(sentryTraceHeader, baggageToUse, randomSpanId); final @NotNull TransactionContext transactionContext = TransactionContext.fromPropagationContext(propagationContext); @@ -95,8 +109,7 @@ public SamplingResult shouldSample( scopes .getOptions() .getInternalTracesSampler() - .sample( - new SamplingContext(transactionContext, null, propagationContext.getSampleRand())); + .sample(new SamplingContext(transactionContext, null, sampleRand)); if (!sentryDecision.getSampled()) { scopes diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java index 69acf521346..e29601faf5f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySamplingResult.java @@ -29,6 +29,7 @@ public Attributes getAttributes() { return Attributes.builder() .put(InternalSemanticAttributes.SAMPLED, sentryDecision.getSampled()) .put(InternalSemanticAttributes.SAMPLE_RATE, sentryDecision.getSampleRate()) + .put(InternalSemanticAttributes.SAMPLE_RAND, sentryDecision.getSampleRand()) .put(InternalSemanticAttributes.PROFILE_SAMPLED, sentryDecision.getProfileSampled()) .put(InternalSemanticAttributes.PROFILE_SAMPLE_RATE, sentryDecision.getProfileSampleRate()) .build(); diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java index a6eb46f4c74..4c61bf79278 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java @@ -1,21 +1,12 @@ package io.sentry.samples.spring.boot.jakarta; -import static io.sentry.quartz.SentryJobListener.SENTRY_SLUG_KEY; - import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Tracer; -import io.sentry.samples.spring.boot.jakarta.quartz.SampleJob; -import java.util.Collections; -import org.quartz.JobDetail; -import org.quartz.SimpleTrigger; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.quartz.CronTriggerFactoryBean; -import org.springframework.scheduling.quartz.JobDetailFactoryBean; -import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; @@ -42,34 +33,34 @@ RestClient restClient(RestClient.Builder builder) { return builder.build(); } - @Bean - public JobDetailFactoryBean jobDetail() { - JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); - jobDetailFactory.setJobClass(SampleJob.class); - jobDetailFactory.setDurability(true); - jobDetailFactory.setJobDataAsMap( - Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); - return jobDetailFactory; - } - - @Bean - public SimpleTriggerFactoryBean trigger(JobDetail job) { - SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); - trigger.setJobDetail(job); - trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes - trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); - trigger.setJobDataAsMap( - Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); - return trigger; - } - - @Bean - public CronTriggerFactoryBean cronTrigger(JobDetail job) { - CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); - trigger.setJobDetail(job); - trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes - return trigger; - } + // @Bean + // public JobDetailFactoryBean jobDetail() { + // JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); + // jobDetailFactory.setJobClass(SampleJob.class); + // jobDetailFactory.setDurability(true); + // jobDetailFactory.setJobDataAsMap( + // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); + // return jobDetailFactory; + // } + // + // @Bean + // public SimpleTriggerFactoryBean trigger(JobDetail job) { + // SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); + // trigger.setJobDetail(job); + // trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes + // trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); + // trigger.setJobDataAsMap( + // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); + // return trigger; + // } + // + // @Bean + // public CronTriggerFactoryBean cronTrigger(JobDetail job) { + // CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); + // trigger.setJobDetail(job); + // trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes + // return trigger; + // } @Bean public Tracer tracer() { diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 55421bcad63..3ab35b9dd5f 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -1956,7 +1956,6 @@ public final class io/sentry/PropagationContext { public fun getSpanId ()Lio/sentry/SpanId; public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun isSampled ()Ljava/lang/Boolean; - public fun setBaggage (Lio/sentry/Baggage;)V public fun setParentSpanId (Lio/sentry/SpanId;)V public fun setSampled (Ljava/lang/Boolean;)V public fun setSpanId (Lio/sentry/SpanId;)V @@ -3642,6 +3641,7 @@ public final class io/sentry/TraceContext : io/sentry/JsonSerializable, io/sentr public fun getPublicKey ()Ljava/lang/String; public fun getRelease ()Ljava/lang/String; public fun getReplayId ()Lio/sentry/protocol/SentryId; + public fun getSampleRand ()Ljava/lang/String; public fun getSampleRate ()Ljava/lang/String; public fun getSampled ()Ljava/lang/String; public fun getTraceId ()Lio/sentry/protocol/SentryId; @@ -3664,6 +3664,7 @@ public final class io/sentry/TraceContext$JsonKeys { public static final field RELEASE Ljava/lang/String; public static final field REPLAY_ID Ljava/lang/String; public static final field SAMPLED Ljava/lang/String; + public static final field SAMPLE_RAND Ljava/lang/String; public static final field SAMPLE_RATE Ljava/lang/String; public static final field TRACE_ID Ljava/lang/String; public static final field TRANSACTION Ljava/lang/String; @@ -3680,8 +3681,11 @@ public final class io/sentry/TracesSamplingDecision { public fun (Ljava/lang/Boolean;)V public fun (Ljava/lang/Boolean;Ljava/lang/Double;)V public fun (Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Boolean;Ljava/lang/Double;)V + public fun (Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;)V + public fun (Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Boolean;Ljava/lang/Double;)V public fun getProfileSampleRate ()Ljava/lang/Double; public fun getProfileSampled ()Ljava/lang/Boolean; + public fun getSampleRand ()Ljava/lang/Double; public fun getSampleRate ()Ljava/lang/Double; public fun getSampled ()Ljava/lang/Boolean; } @@ -6283,6 +6287,7 @@ public final class io/sentry/util/Random : java/io/Serializable { public final class io/sentry/util/SampleRateUtils { public fun ()V + public static fun backfilledSampleRand (Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Boolean;)Ljava/lang/Double; public static fun isValidProfilesSampleRate (Ljava/lang/Double;)Z public static fun isValidSampleRate (Ljava/lang/Double;)Z public static fun isValidTracesSampleRate (Ljava/lang/Double;)Z diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 44243fd6220..7fbf1f2c7dd 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -2,6 +2,8 @@ import static io.sentry.protocol.Contexts.REPLAY_ID; +import com.jakewharton.nopen.annotation.Open; + import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.SampleRateUtils; @@ -25,7 +27,8 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental -public final class Baggage { +@Open +public class Baggage { static final @NotNull String CHARSET = "UTF-8"; static final @NotNull Integer MAX_BAGGAGE_STRING_LENGTH = 8192; @@ -103,7 +106,7 @@ public static Baggage fromHeader( final String valueDecoded = decode(value); keyValues.put(keyDecoded, valueDecoded); - mutable = false; +// mutable = false; } catch (Throwable e) { logger.log( SentryLevel.ERROR, @@ -140,6 +143,7 @@ public static Baggage fromEvent( // we don't persist sample rate baggage.setSampleRate(null); baggage.setSampled(null); + baggage.setSampleRand(null); // TODO do we need this? final @Nullable Object replayId = event.getContexts().get(REPLAY_ID); if (replayId != null && !replayId.toString().equals(SentryId.EMPTY_ID.toString())) { baggage.setReplayId(replayId.toString()); @@ -161,6 +165,7 @@ public Baggage(final @NotNull Baggage baggage) { } @ApiStatus.Internal + @SuppressWarnings("ObjectToString") public Baggage( final @NotNull Map keyValues, final @Nullable String thirdPartyHeader, @@ -170,10 +175,13 @@ public Baggage( this.logger = logger; this.mutable = isMutable; this.thirdPartyHeader = thirdPartyHeader; + new RuntimeException("creating new baggage" + this).printStackTrace(); } + @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { + new RuntimeException("freezing baggage " + this).printStackTrace(); this.mutable = false; } @@ -187,6 +195,7 @@ public String getThirdPartyHeader() { return thirdPartyHeader; } + @SuppressWarnings("ObjectToString") public @NotNull String toHeaderString(@Nullable String thirdPartyBaggageHeaderString) { final StringBuilder sb = new StringBuilder(); String separator = ""; @@ -240,6 +249,8 @@ public String getThirdPartyHeader() { } } + sb.append(",sbg=" + this); + return sb.toString(); } @@ -490,6 +501,10 @@ private static boolean isHighQualityTransactionName( return null; } + public @NotNull Baggage toReadOnly() { + return new ReadOnlyBaggage(this); + } + @ApiStatus.Internal @Nullable public TraceContext toTraceContext() { @@ -508,7 +523,8 @@ public TraceContext toTraceContext() { getTransaction(), getSampleRate(), getSampled(), - replayIdString == null ? null : new SentryId(replayIdString)); + replayIdString == null ? null : new SentryId(replayIdString), + getSampleRand()); traceContext.setUnknown(getUnknown()); return traceContext; } else { @@ -542,4 +558,96 @@ public static final class DSCKeys { SAMPLED, REPLAY_ID); } + + private static final class ReadOnlyBaggage extends Baggage { + + public ReadOnlyBaggage(@NotNull Baggage baggage) { + super(baggage); + } + + @Override + public void freeze() { + // do nothing + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public void setTraceId(@Nullable String traceId) { + // do nothing + } + + @Override + public void setPublicKey(@Nullable String publicKey) { + // do nothing + } + + @Override + public void setEnvironment(@Nullable String environment) { + // do nothing + } + + @Override + public void setRelease(@Nullable String release) { + // do nothing + } + + @Override + public void setUserId(@Nullable String userId) { + // do nothing + } + + @Override + public void setTransaction(@Nullable String transaction) { + // do nothing + } + + @Override + public void setSampleRate(@Nullable String sampleRate) { + // do nothing + } + + @Override + public void setSampleRand(@Nullable String sampleRand) { + // do nothing + } + + @Override + public void setSampleRandDouble(@Nullable Double sampleRand) { + // do nothing + } + + @Override + public void setSampled(@Nullable String sampled) { + // do nothing + } + + @Override + public void setReplayId(@Nullable String replayId) { + // do nothing + } + + @Override + public void set(@NotNull String key, @Nullable String value) { + // do nothing + } + + @Override + public void setValuesFromTransaction(@NotNull SentryId traceId, @Nullable SentryId replayId, @NotNull SentryOptions sentryOptions, @Nullable TracesSamplingDecision samplingDecision, @Nullable String transactionName, @Nullable TransactionNameSource transactionNameSource) { + // do nothing + } + + @Override + public void setValuesFromScope(@NotNull IScope scope, @NotNull SentryOptions options) { + // do nothing + } + + @Override + public @NotNull Baggage toReadOnly() { + return this; + } + } } diff --git a/sentry/src/main/java/io/sentry/OutboxSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java index 4e223da03d2..f5c3813414d 100644 --- a/sentry/src/main/java/io/sentry/OutboxSender.java +++ b/sentry/src/main/java/io/sentry/OutboxSender.java @@ -244,6 +244,14 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN "Invalid sample rate parsed from TraceContext: %s", sampleRateString); } else { + final @Nullable String sampleRandString = traceContext.getSampleRate(); + if (sampleRandString != null) { + final Double sampleRand = Double.parseDouble(sampleRandString); + if (!SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { + return new TracesSamplingDecision(true, sampleRate, sampleRand); + } + } + return new TracesSamplingDecision(true, sampleRate); } } catch (Exception e) { diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 703826488f0..2b88a5ed457 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -2,7 +2,7 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; -import io.sentry.util.SentryRandom; +import io.sentry.util.SampleRateUtils; import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -59,7 +59,7 @@ public static PropagationContext fromHeaders( private @Nullable Boolean sampled; private @NotNull Double sampleRand; - private @Nullable Baggage baggage; + private @NotNull Baggage baggage; public PropagationContext() { this(new SentryId(), new SpanId(), null, null, null, null); @@ -70,11 +70,12 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.getTraceId(), propagationContext.getSpanId(), propagationContext.getParentSpanId(), - cloneBaggage(propagationContext.getBaggage()), + propagationContext.getBaggage(), propagationContext.isSampled(), propagationContext.getSampleRand()); } + @SuppressWarnings("UnusedMethod") private static @Nullable Baggage cloneBaggage(final @Nullable Baggage baggage) { if (baggage != null) { return new Baggage(baggage); @@ -83,6 +84,7 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) return null; } + @SuppressWarnings("ObjectToString") public PropagationContext( final @NotNull SentryId traceId, final @NotNull SpanId spanId, @@ -93,26 +95,40 @@ public PropagationContext( this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; - this.baggage = baggage; + boolean shouldFreezeBaggage = false; + if (baggage != null) { + this.baggage = baggage; + shouldFreezeBaggage = true; + } else { + this.baggage = new Baggage(ScopesAdapter.getInstance().getOptions().getLogger()); + } this.sampled = sampled; + StringBuilder sb = new StringBuilder("sample rand"); if (sampleRand != null) { + sb.append(" [passed in as param]"); this.sampleRand = sampleRand; - } else if (baggage != null && baggage.getSampleRandDouble() != null) { - this.sampleRand = baggage.getSampleRandDouble(); } else { - final @Nullable Double sampleRate = baggage == null ? null : baggage.getSampleRateDouble(); - final @NotNull Double sampleRandToUse = SentryRandom.current().nextDouble(); - - if (sampled != null && sampleRate != null) { - if (sampled) { - this.sampleRand = sampleRandToUse * sampleRate; - } else { - this.sampleRand = sampleRate + (sampleRandToUse * (1 - sampleRate)); - } + sb.append(" [maybe baggage maybe backfill]"); + final @Nullable Double sampleRandMaybe = this.baggage.getSampleRandDouble(); + sb.append(" [baggage " + sampleRandMaybe + "]"); + final @Nullable Double sampleRateMaybe = this.baggage.getSampleRateDouble(); + this.sampleRand = + SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, sampled); + } + if (this.baggage.getSampleRand() == null) { + sb.append(" [setting sample rand on baggage " + this.baggage + "]"); + this.baggage.setSampleRandDouble(this.sampleRand); + } + if (shouldFreezeBaggage) { + if (this.baggage.isMutable()) { + sb.append(" [freezing baggage]"); + this.baggage.freeze(); } else { - this.sampleRand = sampleRandToUse; + sb.append(" [baggage already frozen]"); } } + sb.append(" {" + this.sampleRand + "}"); + new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); } public @NotNull SentryId getTraceId() { @@ -139,14 +155,10 @@ public void setParentSpanId(final @Nullable SpanId parentSpanId) { this.parentSpanId = parentSpanId; } - public @Nullable Baggage getBaggage() { + public @NotNull Baggage getBaggage() { return baggage; } - public void setBaggage(final @Nullable Baggage baggage) { - this.baggage = baggage; - } - public @Nullable Boolean isSampled() { return sampled; } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index ca4f4d67ee2..c442755ae4c 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -84,6 +84,7 @@ public SentryTracer( if (context.getBaggage() != null) { this.baggage = context.getBaggage(); } else { + System.out.println("---- new baggage created in SentryTracer"); this.baggage = new Baggage(scopes.getOptions().getLogger()); } @@ -657,7 +658,6 @@ private void updateBaggageValues() { scope -> { replayId.set(scope.getReplayId()); }); - // TODO sampleRand? baggage.setValuesFromTransaction( getSpanContext().getTraceId(), replayId.get(), diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index bb32022f605..2f5afe807b5 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -19,6 +19,7 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { private final @Nullable String userId; private final @Nullable String transaction; private final @Nullable String sampleRate; + private final @Nullable String sampleRand; private final @Nullable String sampled; private final @Nullable SentryId replayId; @@ -39,6 +40,30 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { @Nullable String sampleRate, @Nullable String sampled, @Nullable SentryId replayId) { + this( + traceId, + publicKey, + release, + environment, + userId, + transaction, + sampleRate, + sampled, + replayId, + null); + } + + TraceContext( + @NotNull SentryId traceId, + @NotNull String publicKey, + @Nullable String release, + @Nullable String environment, + @Nullable String userId, + @Nullable String transaction, + @Nullable String sampleRate, + @Nullable String sampled, + @Nullable SentryId replayId, + @Nullable String sampleRand) { this.traceId = traceId; this.publicKey = publicKey; this.release = release; @@ -48,6 +73,7 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { this.sampleRate = sampleRate; this.sampled = sampled; this.replayId = replayId; + this.sampleRand = sampleRand; } @SuppressWarnings("UnusedMethod") @@ -88,6 +114,10 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { return sampleRate; } + public @Nullable String getSampleRand() { + return sampleRand; + } + public @Nullable String getSampled() { return sampled; } @@ -117,6 +147,7 @@ public static final class JsonKeys { public static final String USER_ID = "user_id"; public static final String TRANSACTION = "transaction"; public static final String SAMPLE_RATE = "sample_rate"; + public static final String SAMPLE_RAND = "sample_rand"; public static final String SAMPLED = "sampled"; public static final String REPLAY_ID = "replay_id"; } @@ -142,6 +173,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (sampleRate != null) { writer.name(TraceContext.JsonKeys.SAMPLE_RATE).value(sampleRate); } + if (sampleRate != null) { + writer.name(TraceContext.JsonKeys.SAMPLE_RAND).value(sampleRand); + } if (sampled != null) { writer.name(TraceContext.JsonKeys.SAMPLED).value(sampled); } @@ -171,6 +205,7 @@ public static final class Deserializer implements JsonDeserializer String userId = null; String transaction = null; String sampleRate = null; + String sampleRand = null; String sampled = null; SentryId replayId = null; @@ -199,6 +234,9 @@ public static final class Deserializer implements JsonDeserializer case TraceContext.JsonKeys.SAMPLE_RATE: sampleRate = reader.nextStringOrNull(); break; + case TraceContext.JsonKeys.SAMPLE_RAND: + sampleRand = reader.nextStringOrNull(); + break; case TraceContext.JsonKeys.SAMPLED: sampled = reader.nextStringOrNull(); break; @@ -229,7 +267,8 @@ public static final class Deserializer implements JsonDeserializer transaction, sampleRate, sampled, - replayId); + replayId, + sampleRand); traceContext.setUnknown(unknown); reader.endObject(); return traceContext; diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 1d1ad6470ac..12453b169b6 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -24,6 +24,7 @@ public TracesSampler(final @NotNull SentryOptions options) { @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); + System.out.println("sample rand used in TracesSampler " + sampleRand); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { @@ -56,7 +57,11 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont } if (samplerResult != null) { return new TracesSamplingDecision( - sample(samplerResult, sampleRand), samplerResult, profilesSampled, profilesSampleRate); + sample(samplerResult, sampleRand), + samplerResult, + sampleRand, + profilesSampled, + profilesSampleRate); } } @@ -76,6 +81,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont return new TracesSamplingDecision( sample(downsampledTracesSampleRate, sampleRand), downsampledTracesSampleRate, + sampleRand, profilesSampled, profilesSampleRate); } diff --git a/sentry/src/main/java/io/sentry/TracesSamplingDecision.java b/sentry/src/main/java/io/sentry/TracesSamplingDecision.java index 8010537d5c4..e9e9a7a490e 100644 --- a/sentry/src/main/java/io/sentry/TracesSamplingDecision.java +++ b/sentry/src/main/java/io/sentry/TracesSamplingDecision.java @@ -7,6 +7,7 @@ public final class TracesSamplingDecision { private final @NotNull Boolean sampled; private final @Nullable Double sampleRate; + private final @Nullable Double sampleRand; private final @NotNull Boolean profileSampled; private final @Nullable Double profileSampleRate; @@ -15,16 +16,33 @@ public TracesSamplingDecision(final @NotNull Boolean sampled) { } public TracesSamplingDecision(final @NotNull Boolean sampled, final @Nullable Double sampleRate) { - this(sampled, sampleRate, false, null); + this(sampled, sampleRate, null, false, null); } public TracesSamplingDecision( final @NotNull Boolean sampled, final @Nullable Double sampleRate, + final @Nullable Double sampleRand) { + this(sampled, sampleRate, sampleRand, false, null); + } + + public TracesSamplingDecision( + final @NotNull Boolean sampled, + final @Nullable Double sampleRate, + final @NotNull Boolean profileSampled, + final @Nullable Double profileSampleRate) { + this(sampled, sampleRate, null, profileSampled, profileSampleRate); + } + + public TracesSamplingDecision( + final @NotNull Boolean sampled, + final @Nullable Double sampleRate, + final @Nullable Double sampleRand, final @NotNull Boolean profileSampled, final @Nullable Double profileSampleRate) { this.sampled = sampled; this.sampleRate = sampleRate; + this.sampleRand = sampleRand; // A profile can be sampled only if the transaction is sampled this.profileSampled = sampled && profileSampled; this.profileSampleRate = profileSampleRate; @@ -38,6 +56,10 @@ public TracesSamplingDecision( return sampleRate; } + public @Nullable Double getSampleRand() { + return sampleRand; + } + public @NotNull Boolean getProfileSampled() { return profileSampled; } diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index aec0b8927de..233953c4941 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -22,20 +22,25 @@ public static TransactionContext fromPropagationContext( final @NotNull PropagationContext propagationContext) { @Nullable Boolean parentSampled = propagationContext.isSampled(); TracesSamplingDecision samplingDecision = - parentSampled == null ? null : new TracesSamplingDecision(parentSampled); - - @Nullable Baggage baggage = propagationContext.getBaggage(); - - if (baggage != null) { - baggage.freeze(); - - Double sampleRate = baggage.getSampleRateDouble(); - if (parentSampled != null) { - if (sampleRate != null) { - samplingDecision = new TracesSamplingDecision(parentSampled.booleanValue(), sampleRate); - } else { - samplingDecision = new TracesSamplingDecision(parentSampled.booleanValue()); - } + parentSampled == null + ? null + : new TracesSamplingDecision(parentSampled, null, propagationContext.getSampleRand()); + + @NotNull Baggage baggage = propagationContext.getBaggage(); + + // TODO is this ok? +// baggage.freeze(); + + Double sampleRate = baggage.getSampleRateDouble(); + if (parentSampled != null) { + if (sampleRate != null) { + samplingDecision = + new TracesSamplingDecision( + parentSampled.booleanValue(), sampleRate, propagationContext.getSampleRand()); + } else { + samplingDecision = + new TracesSamplingDecision( + parentSampled.booleanValue(), null, propagationContext.getSampleRand()); } } diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index ed011ff842b..77392a15720 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -23,6 +23,26 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return isValidRate(profilesSampleRate, true); } + public static Double backfilledSampleRand( + final @Nullable Double sampleRand, + final @Nullable Double sampleRate, + final @Nullable Boolean sampled) { + if (sampleRand != null) { + return sampleRand; + } + + double newSampleRand = SentryRandom.current().nextDouble(); + if (sampleRate != null && sampled != null) { + if (sampled) { + return newSampleRand * sampleRate; + } else { + return sampleRate + (newSampleRand * (1 - sampleRate)); + } + } + + return newSampleRand; + } + private static boolean isValidRate(final @Nullable Double rate, final boolean allowNull) { if (rate == null) { return allowNull; diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index be7a7355d37..a8093c89b85 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -57,12 +57,8 @@ public static void startNewTrace(final @NotNull IScopes scopes) { if (returnValue.propagationContext != null) { final @NotNull PropagationContext propagationContext = returnValue.propagationContext; - final @Nullable Baggage baggage = propagationContext.getBaggage(); - @Nullable BaggageHeader baggageHeader = null; - if (baggage != null) { - baggageHeader = - BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); - } + final @NotNull Baggage baggage = propagationContext.getBaggage(); + final @NotNull BaggageHeader baggageHeader = BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); return new TracingHeaders( new SentryTraceHeader( @@ -80,14 +76,7 @@ public static void startNewTrace(final @NotNull IScopes scopes) { final @NotNull IScope scope, final @NotNull SentryOptions sentryOptions) { return scope.withPropagationContext( propagationContext -> { - @Nullable Baggage baggage = propagationContext.getBaggage(); - if (baggage == null) { - baggage = new Baggage(sentryOptions.getLogger()); - propagationContext.setBaggage(baggage); - } - if (baggage.getSampleRand() != null) { - baggage.setSampleRandDouble(propagationContext.getSampleRand()); - } + @NotNull Baggage baggage = propagationContext.getBaggage(); if (baggage.isMutable()) { baggage.setValuesFromScope(scope, sentryOptions); baggage.freeze(); From 15c9c8ce75c767ecac97bb7743830432b44c161f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Fri, 7 Feb 2025 15:54:53 +0100 Subject: [PATCH 16/29] wip3 --- .../opentelemetry/OtelSentryPropagator.java | 36 +++-- .../OtelSentrySpanProcessor.java | 15 +- .../sentry/opentelemetry/OtelSpanWrapper.java | 24 ++- .../sentry/opentelemetry/SentrySampler.java | 21 +-- .../opentelemetry/SentrySpanExporter.java | 1 + sentry/api/sentry.api | 16 +- sentry/src/main/java/io/sentry/Baggage.java | 149 +++++------------- .../src/main/java/io/sentry/HubAdapter.java | 5 + .../main/java/io/sentry/HubScopesWrapper.java | 5 + sentry/src/main/java/io/sentry/IScopes.java | 4 + sentry/src/main/java/io/sentry/NoOpHub.java | 5 + .../src/main/java/io/sentry/NoOpScopes.java | 5 + .../java/io/sentry/PropagationContext.java | 64 ++------ sentry/src/main/java/io/sentry/Scopes.java | 25 ++- .../main/java/io/sentry/ScopesAdapter.java | 5 + .../src/main/java/io/sentry/SentryTracer.java | 44 +++--- sentry/src/main/java/io/sentry/Span.java | 5 +- .../src/main/java/io/sentry/TraceContext.java | 5 + .../main/java/io/sentry/TracesSampler.java | 19 +-- .../java/io/sentry/TransactionContext.java | 8 +- .../java/io/sentry/util/SampleRateUtils.java | 22 ++- .../java/io/sentry/util/TracingUtils.java | 73 ++++++++- sentry/src/test/java/io/sentry/BaggageTest.kt | 37 ++++- .../test/java/io/sentry/JsonSerializerTest.kt | 8 +- .../java/io/sentry/PropagationContextTest.kt | 43 +++++ sentry/src/test/java/io/sentry/ScopeTest.kt | 2 +- .../sentry/TraceContextSerializationTest.kt | 3 +- .../test/java/io/sentry/TracesSamplerTest.kt | 117 ++++++++------ .../java/io/sentry/util/SampleRateUtilTest.kt | 61 +++++++ .../java/io/sentry/util/TracingUtilsTest.kt | 148 +++++++++++++++-- .../json/sentry_envelope_header.json | 1 + .../src/test/resources/json/trace_state.json | 1 + 32 files changed, 647 insertions(+), 330 deletions(-) create mode 100644 sentry/src/test/java/io/sentry/PropagationContextTest.kt diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index ddcead442d7..c57fdcbc8c8 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -13,14 +13,12 @@ import io.sentry.Baggage; import io.sentry.BaggageHeader; import io.sentry.IScopes; -import io.sentry.PropagationContext; import io.sentry.ScopesAdapter; import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.SentryTraceHeader; import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.util.TracingUtils; - import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -75,17 +73,21 @@ public void inject(final Context context, final C carrier, final TextMapSett return; } - // TODO can we use traceIfAllowed? do we have the URL here? - TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); - - final @NotNull SentryTraceHeader sentryTraceHeader = sentrySpan.toSentryTrace(); - setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); - final @Nullable BaggageHeader baggageHeader = - sentrySpan.toBaggageHeader(Collections.emptyList()); - if (baggageHeader != null) { - System.out.println("outgoing baggage: "); - System.out.println(baggageHeader.getValue()); - setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); + // TODO can we use traceIfAllowed? do we have the URL here? need to access span attrs + final @Nullable TracingUtils.TracingHeaders tracingHeaders = + TracingUtils.trace(scopes, Collections.emptyList(), sentrySpan); + + if (tracingHeaders != null) { + final @NotNull SentryTraceHeader sentryTraceHeader = tracingHeaders.getSentryTraceHeader(); + setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); + final @Nullable BaggageHeader baggageHeader = tracingHeaders.getBaggageHeader(); + if (baggageHeader != null) { + System.out.println("outgoing baggage: "); + System.out.println(baggageHeader.getValue()); + setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); + } + } else { + System.out.println("not tracing headers found"); } } @@ -108,6 +110,8 @@ public Context extract( SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(sentryTraceString); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); + System.out.println("incoming sentry-trace:"); + System.out.println(sentryTraceString); System.out.println("incoming baggage:"); System.out.println(baggageString); final Baggage baggage = Baggage.fromHeader(baggageString); @@ -134,9 +138,9 @@ public Context extract( .getLogger() .log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId()); -// final @NotNull PropagationContext propagationContext = -// PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); -// scopesToUse.getIsolationScope().setPropagationContext(propagationContext); + // final @NotNull PropagationContext propagationContext = + // PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); + // scopesToUse.getIsolationScope().setPropagationContext(propagationContext); return modifiedContext; } catch (InvalidSentryTraceHeaderException e) { diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java index ef329414d6b..37293569aee 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentrySpanProcessor.java @@ -70,28 +70,19 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri baggage = baggageFromContext; } -// final @Nullable Boolean baggageMutable = -// otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE_MUTABLE); final @Nullable String baggageString = otelSpan.getAttribute(InternalSemanticAttributes.BAGGAGE); if (baggageString != null) { baggage = Baggage.fromHeader(baggageString); -// if (baggageMutable == false) { // TODO was this a bug? -// baggage.freeze(); -// } } final @Nullable Boolean sampled = isSampled(otelSpan, samplingDecision); - final @Nullable Double sampleRand = samplingDecision == null ? null : samplingDecision.getSampleRand(); final @NotNull PropagationContext propagationContext = new PropagationContext( - new SentryId(traceId), - sentrySpanId, - sentryParentSpanId, - baggage, - sampled, - sampleRand); + new SentryId(traceId), sentrySpanId, sentryParentSpanId, baggage, sampled); + + baggage = propagationContext.getBaggage(); updatePropagationContext(scopes, propagationContext); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java index 090d317485b..34f2d2a4d7c 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java @@ -64,7 +64,6 @@ public final class OtelSpanWrapper implements IOtelSpanWrapper { private final @NotNull Contexts contexts = new Contexts(); private @Nullable String transactionName; private @Nullable TransactionNameSource transactionNameSource; - private final @Nullable Baggage baggage; private final @NotNull AutoClosableReentrantLock lock = new AutoClosableReentrantLock(); private final @NotNull Map data = new ConcurrentHashMap<>(); @@ -86,17 +85,12 @@ public OtelSpanWrapper( this.scopes = Objects.requireNonNull(scopes, "scopes are required"); this.span = new WeakReference<>(span); this.startTimestamp = startTimestamp; - - if (parentSpan != null) { - this.baggage = parentSpan.getSpanContext().getBaggage(); - } else if (baggage != null) { - this.baggage = baggage; - } else { - this.baggage = null; - } - + final @Nullable Baggage baggageToUse = + baggage != null + ? baggage + : (parentSpan != null ? parentSpan.getSpanContext().getBaggage() : null); this.context = - new OtelSpanContext(span, samplingDecision, parentSpan, parentSpanId, this.baggage); + new OtelSpanContext(span, samplingDecision, parentSpan, parentSpanId, baggageToUse); } @Override @@ -207,15 +201,16 @@ public OtelSpanWrapper( @Override public @Nullable TraceContext traceContext() { if (scopes.getOptions().isTraceSampling()) { + final @Nullable Baggage baggage = context.getBaggage(); if (baggage != null) { - updateBaggageValues(); + updateBaggageValues(baggage); return baggage.toTraceContext(); } } return null; } - private void updateBaggageValues() { + private void updateBaggageValues(final @NotNull Baggage baggage) { try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) { if (baggage != null && baggage.isMutable()) { final AtomicReference replayIdAtomicReference = new AtomicReference<>(); @@ -238,8 +233,9 @@ private void updateBaggageValues() { @Override public @Nullable BaggageHeader toBaggageHeader(@Nullable List thirdPartyBaggageHeaders) { if (scopes.getOptions().isTraceSampling()) { + final @Nullable Baggage baggage = context.getBaggage(); if (baggage != null) { - updateBaggageValues(); + updateBaggageValues(baggage); return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java index f83b69f5571..89499321293 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySampler.java @@ -23,7 +23,6 @@ import io.sentry.TransactionContext; import io.sentry.clientreport.DiscardReason; import io.sentry.protocol.SentryId; -import io.sentry.util.SampleRateUtils; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -83,25 +82,12 @@ public SamplingResult shouldSample( baggage = baggageFromContext; } - final @Nullable Boolean sampledFromHeaders = - sentryTraceHeader == null ? null : sentryTraceHeader.isSampled(); - final @Nullable Double sampleRandFromHeaders = - baggage == null ? null : baggage.getSampleRandDouble(); - final @Nullable Double sampleRateFromHeaders = - baggage == null ? null : baggage.getSampleRateDouble(); - final @NotNull Double sampleRand = - SampleRateUtils.backfilledSampleRand( - sampleRandFromHeaders, sampleRateFromHeaders, sampledFromHeaders); - // there's no way to get the span id here, so we just use a random id for sampling SpanId randomSpanId = new SpanId(); - final @Nullable Baggage baggageToUse = baggage == null ? null : baggage.toReadOnly(); - // TODO this freezes baggage but should not final @NotNull PropagationContext propagationContext = sentryTraceHeader == null - ? new PropagationContext( - new SentryId(traceId), randomSpanId, null, baggageToUse, null, sampleRand) - : PropagationContext.fromHeaders(sentryTraceHeader, baggageToUse, randomSpanId); + ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null) + : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId); final @NotNull TransactionContext transactionContext = TransactionContext.fromPropagationContext(propagationContext); @@ -109,7 +95,8 @@ public SamplingResult shouldSample( scopes .getOptions() .getInternalTracesSampler() - .sample(new SamplingContext(transactionContext, null, sampleRand)); + .sample( + new SamplingContext(transactionContext, null, propagationContext.getSampleRand())); if (!sentryDecision.getSampled()) { scopes diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java index 4d2e7545c64..693b94fe38d 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentrySpanExporter.java @@ -62,6 +62,7 @@ public final class SentrySpanExporter implements SpanExporter { InternalSemanticAttributes.BAGGAGE_MUTABLE.getKey(), InternalSemanticAttributes.SAMPLED.getKey(), InternalSemanticAttributes.SAMPLE_RATE.getKey(), + InternalSemanticAttributes.SAMPLE_RAND.getKey(), InternalSemanticAttributes.PROFILE_SAMPLED.getKey(), InternalSemanticAttributes.PROFILE_SAMPLE_RATE.getKey(), InternalSemanticAttributes.PARENT_SAMPLED.getKey(), diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 3ab35b9dd5f..62f62571149 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -29,10 +29,11 @@ public final class io/sentry/Attachment { public abstract interface class io/sentry/BackfillingEventProcessor : io/sentry/EventProcessor { } -public final class io/sentry/Baggage { +public class io/sentry/Baggage { + public static final field NOOP Lio/sentry/Baggage; public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V - public fun (Ljava/util/Map;Ljava/lang/String;ZLio/sentry/ILogger;)V + public fun (Ljava/util/Map;Ljava/lang/String;ZZLio/sentry/ILogger;)V public fun freeze ()V public static fun fromEvent (Lio/sentry/SentryEvent;Lio/sentry/SentryOptions;)Lio/sentry/Baggage; public static fun fromHeader (Ljava/lang/String;)Lio/sentry/Baggage; @@ -57,6 +58,7 @@ public final class io/sentry/Baggage { public fun getUnknown ()Ljava/util/Map; public fun getUserId ()Ljava/lang/String; public fun isMutable ()Z + public fun isShouldFreeze ()Z public fun set (Ljava/lang/String;Ljava/lang/String;)V public fun setEnvironment (Ljava/lang/String;)V public fun setPublicKey (Ljava/lang/String;)V @@ -598,6 +600,7 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -662,6 +665,7 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -888,6 +892,7 @@ public abstract interface class io/sentry/IScopes { public abstract fun getLastEventId ()Lio/sentry/protocol/SentryId; public abstract fun getOptions ()Lio/sentry/SentryOptions; public abstract fun getParentScopes ()Lio/sentry/IScopes; + public abstract fun getPropagationContext ()Lio/sentry/PropagationContext; public abstract fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public abstract fun getScope ()Lio/sentry/IScope; public abstract fun getSpan ()Lio/sentry/ISpan; @@ -1410,6 +1415,7 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -1569,6 +1575,7 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -1944,9 +1951,10 @@ public final class io/sentry/ProfilingTransactionData$JsonKeys { } public final class io/sentry/PropagationContext { + public static field NOOP Lio/sentry/PropagationContext; public fun ()V public fun (Lio/sentry/PropagationContext;)V - public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;)V + public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/lang/String;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/ILogger;Ljava/lang/String;Ljava/util/List;)Lio/sentry/PropagationContext; public static fun fromHeaders (Lio/sentry/SentryTraceHeader;Lio/sentry/Baggage;Lio/sentry/SpanId;)Lio/sentry/PropagationContext; @@ -2170,6 +2178,7 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -2234,6 +2243,7 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; + public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 7fbf1f2c7dd..10a5d7bf68b 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -3,7 +3,6 @@ import static io.sentry.protocol.Contexts.REPLAY_ID; import com.jakewharton.nopen.annotation.Open; - import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.SampleRateUtils; @@ -30,6 +29,8 @@ @Open public class Baggage { + public static final @NotNull Baggage NOOP = + new Baggage(new HashMap<>(), null, false, true, NoOpLogger.getInstance()); static final @NotNull String CHARSET = "UTF-8"; static final @NotNull Integer MAX_BAGGAGE_STRING_LENGTH = 8192; static final @NotNull Integer MAX_BAGGAGE_LIST_MEMBER_COUNT = 64; @@ -38,6 +39,7 @@ public class Baggage { final @NotNull Map keyValues; final @Nullable String thirdPartyHeader; private boolean mutable; + private boolean shouldFreeze; final @NotNull ILogger logger; @NotNull @@ -88,7 +90,7 @@ public static Baggage fromHeader( final @NotNull ILogger logger) { final @NotNull Map keyValues = new HashMap<>(); final @NotNull List thirdPartyKeyValueStrings = new ArrayList<>(); - boolean mutable = true; + boolean shouldFreeze = false; if (headerValue != null) { try { @@ -106,7 +108,9 @@ public static Baggage fromHeader( final String valueDecoded = decode(value); keyValues.put(keyDecoded, valueDecoded); -// mutable = false; + if (!DSCKeys.SAMPLE_RAND.equalsIgnoreCase(key)) { + shouldFreeze = true; + } } catch (Throwable e) { logger.log( SentryLevel.ERROR, @@ -126,7 +130,12 @@ public static Baggage fromHeader( thirdPartyKeyValueStrings.isEmpty() ? null : StringUtils.join(",", thirdPartyKeyValueStrings); - return new Baggage(keyValues, thirdPartyHeader, mutable, logger); + /* + can't freeze Baggage right away as we might have to backfill sampleRand + also we don't receive sentry-trace header here or in ctor so we can't + backfill then freeze here unless we pass sentry-trace header. + */ + return new Baggage(keyValues, thirdPartyHeader, true, shouldFreeze, logger); } @ApiStatus.Internal @@ -156,12 +165,17 @@ public static Baggage fromEvent( @ApiStatus.Internal public Baggage(final @NotNull ILogger logger) { - this(new HashMap<>(), null, true, logger); + this(new HashMap<>(), null, true, false, logger); } @ApiStatus.Internal public Baggage(final @NotNull Baggage baggage) { - this(baggage.keyValues, baggage.thirdPartyHeader, baggage.mutable, baggage.logger); + this( + baggage.keyValues, + baggage.thirdPartyHeader, + baggage.mutable, + baggage.shouldFreeze, + baggage.logger); } @ApiStatus.Internal @@ -170,18 +184,32 @@ public Baggage( final @NotNull Map keyValues, final @Nullable String thirdPartyHeader, boolean isMutable, + boolean shouldFreeze, final @NotNull ILogger logger) { this.keyValues = keyValues; this.logger = logger; - this.mutable = isMutable; this.thirdPartyHeader = thirdPartyHeader; - new RuntimeException("creating new baggage" + this).printStackTrace(); + this.mutable = isMutable; + this.shouldFreeze = shouldFreeze; + // new RuntimeException( + // "creating new baggage " + // + this + // + " mutable " + // + mutable + // + " shouldFreeze " + // + shouldFreeze) + // .printStackTrace(); } @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { - new RuntimeException("freezing baggage " + this).printStackTrace(); + // if (mutable) { + // new RuntimeException("freezing baggage " + this).printStackTrace(); + // } else { + // new RuntimeException("freezing baggage that is already frozen " + + // this).printStackTrace(); + // } this.mutable = false; } @@ -190,6 +218,11 @@ public boolean isMutable() { return mutable; } + @ApiStatus.Internal + public boolean isShouldFreeze() { + return shouldFreeze; + } + @Nullable public String getThirdPartyHeader() { return thirdPartyHeader; @@ -249,7 +282,7 @@ public String getThirdPartyHeader() { } } - sb.append(",sbg=" + this); + // sb.append(",sbg=" + this); return sb.toString(); } @@ -501,10 +534,6 @@ private static boolean isHighQualityTransactionName( return null; } - public @NotNull Baggage toReadOnly() { - return new ReadOnlyBaggage(this); - } - @ApiStatus.Internal @Nullable public TraceContext toTraceContext() { @@ -558,96 +587,4 @@ public static final class DSCKeys { SAMPLED, REPLAY_ID); } - - private static final class ReadOnlyBaggage extends Baggage { - - public ReadOnlyBaggage(@NotNull Baggage baggage) { - super(baggage); - } - - @Override - public void freeze() { - // do nothing - } - - @Override - public boolean isMutable() { - return false; - } - - @Override - public void setTraceId(@Nullable String traceId) { - // do nothing - } - - @Override - public void setPublicKey(@Nullable String publicKey) { - // do nothing - } - - @Override - public void setEnvironment(@Nullable String environment) { - // do nothing - } - - @Override - public void setRelease(@Nullable String release) { - // do nothing - } - - @Override - public void setUserId(@Nullable String userId) { - // do nothing - } - - @Override - public void setTransaction(@Nullable String transaction) { - // do nothing - } - - @Override - public void setSampleRate(@Nullable String sampleRate) { - // do nothing - } - - @Override - public void setSampleRand(@Nullable String sampleRand) { - // do nothing - } - - @Override - public void setSampleRandDouble(@Nullable Double sampleRand) { - // do nothing - } - - @Override - public void setSampled(@Nullable String sampled) { - // do nothing - } - - @Override - public void setReplayId(@Nullable String replayId) { - // do nothing - } - - @Override - public void set(@NotNull String key, @Nullable String value) { - // do nothing - } - - @Override - public void setValuesFromTransaction(@NotNull SentryId traceId, @Nullable SentryId replayId, @NotNull SentryOptions sentryOptions, @Nullable TracesSamplingDecision samplingDecision, @Nullable String transactionName, @Nullable TransactionNameSource transactionNameSource) { - // do nothing - } - - @Override - public void setValuesFromScope(@NotNull IScope scope, @NotNull SentryOptions options) { - // do nothing - } - - @Override - public @NotNull Baggage toReadOnly() { - return this; - } - } } diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index fc2f9c15dcd..50dd34adb1b 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -344,6 +344,11 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + @Override + public @NotNull PropagationContext getPropagationContext() { + return Sentry.getCurrentScopes().getPropagationContext(); + } + @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 591852a9adf..5b13970f13d 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -342,4 +342,9 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return scopes.captureReplay(replay, hint); } + + @Override + public @NotNull PropagationContext getPropagationContext() { + return scopes.getPropagationContext(); + } } diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index e07de9c327c..2ceb5c4f52c 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -689,4 +689,8 @@ default boolean isNoOp() { @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint); + + @ApiStatus.Internal + @NotNull + PropagationContext getPropagationContext(); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index d3e0b010c39..8f3741e15d7 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -297,6 +297,11 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } + @Override + public @NotNull PropagationContext getPropagationContext() { + return PropagationContext.NOOP; + } + @Override public @Nullable RateLimiter getRateLimiter() { return null; diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 8255569387d..0ccab9bbdc5 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -301,4 +301,9 @@ public boolean isNoOp() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return SentryId.EMPTY_ID; } + + @Override + public @NotNull PropagationContext getPropagationContext() { + return PropagationContext.NOOP; + } } diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 2b88a5ed457..694f62052d4 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -3,6 +3,8 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; import io.sentry.util.SampleRateUtils; +import io.sentry.util.TracingUtils; + import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; @@ -12,6 +14,9 @@ @ApiStatus.Internal public final class PropagationContext { + public static @NotNull PropagationContext NOOP = + new PropagationContext(SentryId.EMPTY_ID, SpanId.EMPTY_ID, null, Baggage.NOOP, false); + public static PropagationContext fromHeaders( final @NotNull ILogger logger, final @Nullable String sentryTraceHeader, @@ -48,8 +53,7 @@ public static PropagationContext fromHeaders( spanIdToUse, sentryTraceHeader.getSpanId(), baggage, - sentryTraceHeader.isSampled(), - null); + sentryTraceHeader.isSampled()); } private @NotNull SentryId traceId; @@ -57,12 +61,11 @@ public static PropagationContext fromHeaders( private @Nullable SpanId parentSpanId; private @Nullable Boolean sampled; - private @NotNull Double sampleRand; private @NotNull Baggage baggage; public PropagationContext() { - this(new SentryId(), new SpanId(), null, null, null, null); + this(new SentryId(), new SpanId(), null, null, null); } public PropagationContext(final @NotNull PropagationContext propagationContext) { @@ -71,17 +74,7 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.getSpanId(), propagationContext.getParentSpanId(), propagationContext.getBaggage(), - propagationContext.isSampled(), - propagationContext.getSampleRand()); - } - - @SuppressWarnings("UnusedMethod") - private static @Nullable Baggage cloneBaggage(final @Nullable Baggage baggage) { - if (baggage != null) { - return new Baggage(baggage); - } - - return null; + propagationContext.isSampled()); } @SuppressWarnings("ObjectToString") @@ -90,45 +83,12 @@ public PropagationContext( final @NotNull SpanId spanId, final @Nullable SpanId parentSpanId, final @Nullable Baggage baggage, - final @Nullable Boolean sampled, - final @Nullable Double sampleRand) { + final @Nullable Boolean sampled) { this.traceId = traceId; this.spanId = spanId; this.parentSpanId = parentSpanId; - boolean shouldFreezeBaggage = false; - if (baggage != null) { - this.baggage = baggage; - shouldFreezeBaggage = true; - } else { - this.baggage = new Baggage(ScopesAdapter.getInstance().getOptions().getLogger()); - } + this.baggage = TracingUtils.ensureBaggage(baggage, sampled, null, null); this.sampled = sampled; - StringBuilder sb = new StringBuilder("sample rand"); - if (sampleRand != null) { - sb.append(" [passed in as param]"); - this.sampleRand = sampleRand; - } else { - sb.append(" [maybe baggage maybe backfill]"); - final @Nullable Double sampleRandMaybe = this.baggage.getSampleRandDouble(); - sb.append(" [baggage " + sampleRandMaybe + "]"); - final @Nullable Double sampleRateMaybe = this.baggage.getSampleRateDouble(); - this.sampleRand = - SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, sampled); - } - if (this.baggage.getSampleRand() == null) { - sb.append(" [setting sample rand on baggage " + this.baggage + "]"); - this.baggage.setSampleRandDouble(this.sampleRand); - } - if (shouldFreezeBaggage) { - if (this.baggage.isMutable()) { - sb.append(" [freezing baggage]"); - this.baggage.freeze(); - } else { - sb.append(" [baggage already frozen]"); - } - } - sb.append(" {" + this.sampleRand + "}"); - new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); } public @NotNull SentryId getTraceId() { @@ -182,6 +142,8 @@ public void setSampled(final @Nullable Boolean sampled) { } public @NotNull Double getSampleRand() { - return sampleRand; + final @Nullable Double sampleRand = baggage.getSampleRandDouble(); + // should never be null since we ensure it in ctor + return sampleRand == null ? 0.0 : sampleRand; } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 4b4f8f8a877..08febded426 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -857,8 +857,7 @@ public void flush(long timeoutMillis) { SentryLevel.INFO, "Tracing is disabled and this 'startTransaction' returns a no-op."); transaction = NoOpTransaction.getInstance(); } else { - final @NotNull Double sampleRand = - getCombinedScopeView().getPropagationContext().getSampleRand(); + final Double sampleRand = getSampleRand(transactionContext); final SamplingContext samplingContext = new SamplingContext( transactionContext, transactionOptions.getCustomSamplingContext(), sampleRand); @@ -897,6 +896,18 @@ public void flush(long timeoutMillis) { return transaction; } + private @NotNull Double getSampleRand(final @NotNull TransactionContext transactionContext) { + final @Nullable Baggage baggage = transactionContext.getBaggage(); + if (baggage != null) { + final @Nullable Double sampleRandFromBaggageMaybe = baggage.getSampleRandDouble(); + if (sampleRandFromBaggageMaybe != null) { + return sampleRandFromBaggageMaybe; + } + } + + return getCombinedScopeView().getPropagationContext().getSampleRand(); + } + @Override @ApiStatus.Internal public void setSpanContext( @@ -966,7 +977,10 @@ public void reportFullyDisplayed() { PropagationContext.fromHeaders(getOptions().getLogger(), sentryTrace, baggageHeaders); configureScope( (scope) -> { - scope.setPropagationContext(propagationContext); + scope.withPropagationContext( + oldPropagationContext -> { + scope.setPropagationContext(propagationContext); + }); }); if (getOptions().isTracingEnabled()) { return TransactionContext.fromPropagationContext(propagationContext); @@ -1054,6 +1068,11 @@ public void reportFullyDisplayed() { return sentryId; } + @Override + public @NotNull PropagationContext getPropagationContext() { + return getCombinedScopeView().getPropagationContext(); + } + @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 6df6deee3d4..45629babad5 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -347,4 +347,9 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + + @Override + public @NotNull PropagationContext getPropagationContext() { + return Sentry.getCurrentScopes().getPropagationContext(); + } } diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index c442755ae4c..5c73e128257 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -46,7 +46,6 @@ public final class SentryTracer implements ITransaction { private final @NotNull AtomicBoolean isIdleFinishTimerRunning = new AtomicBoolean(false); private final @NotNull AtomicBoolean isDeadlineTimerRunning = new AtomicBoolean(false); - private final @NotNull Baggage baggage; private @NotNull TransactionNameSource transactionNameSource; private final @NotNull Instrumenter instrumenter; private final @NotNull Contexts contexts = new Contexts(); @@ -81,13 +80,6 @@ public SentryTracer( this.transactionNameSource = context.getTransactionNameSource(); this.transactionOptions = transactionOptions; - if (context.getBaggage() != null) { - this.baggage = context.getBaggage(); - } else { - System.out.println("---- new baggage created in SentryTracer"); - this.baggage = new Baggage(scopes.getOptions().getLogger()); - } - // We are currently sending the performance data only in profiles, but we are always sending // performance measurements. if (transactionPerformanceCollector != null) { @@ -643,14 +635,16 @@ public void finish(@Nullable SpanStatus status, @Nullable SentryDate finishDate) @Override public @Nullable TraceContext traceContext() { if (scopes.getOptions().isTraceSampling()) { - updateBaggageValues(); - return baggage.toTraceContext(); - } else { - return null; + final @Nullable Baggage baggage = getSpanContext().getBaggage(); + if (baggage != null) { + updateBaggageValues(baggage); + return baggage.toTraceContext(); + } } + return null; } - private void updateBaggageValues() { + private void updateBaggageValues(final @NotNull Baggage baggage) { try (final @NotNull ISentryLifecycleToken ignored = tracerLock.acquire()) { if (baggage.isMutable()) { final AtomicReference replayId = new AtomicReference<>(); @@ -673,20 +667,24 @@ private void updateBaggageValues() { @Override public @Nullable BaggageHeader toBaggageHeader(@Nullable List thirdPartyBaggageHeaders) { if (scopes.getOptions().isTraceSampling()) { - updateBaggageValues(); + final @Nullable Baggage baggage = getSpanContext().getBaggage(); + if (baggage != null) { + updateBaggageValues(baggage); + BaggageHeader baggageHeader = + BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + if (baggageHeader != null) { + System.out.println("outgoing baggage in SentryTracer:"); + System.out.println(baggageHeader.getValue()); + } else { + System.out.println("baggage header null in SentryTracer"); + } - BaggageHeader baggageHeader = - BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); - if (baggageHeader != null) { - System.out.println(baggageHeader.getName()); - System.out.println(baggageHeader.getValue()); + return baggageHeader; } else { - System.out.println("baggage header null in SentryTracer"); + System.out.println("baggage null in SentryTracer"); } - return baggageHeader; - } else { - return null; } + return null; } private boolean hasAllChildrenFinished() { diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index 3f08cca2a58..da8cf4765e5 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -154,7 +154,10 @@ public Span( @Override public @NotNull SentryTraceHeader toSentryTrace() { - return new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); + SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); + System.out.println("outgoing sentry-trace:"); + System.out.println(sentryTraceHeader.getValue()); + return sentryTraceHeader; } @Override diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index 2f5afe807b5..cdce7a9b0fe 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -30,6 +30,11 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { this(traceId, publicKey, null, null, null, null, null, null, null); } + @SuppressWarnings("InlineMeSuggester") + /** + * @deprecated please use the constructor than also takes sampleRand + */ + @Deprecated TraceContext( @NotNull SentryId traceId, @NotNull String publicKey, diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 12453b169b6..741440369c4 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -2,6 +2,8 @@ import io.sentry.util.Objects; import io.sentry.util.Random; +import io.sentry.util.SampleRateUtils; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -12,23 +14,18 @@ public final class TracesSampler { private final @NotNull SentryOptions options; public TracesSampler(final @NotNull SentryOptions options) { - this(Objects.requireNonNull(options, "options are required"), null); - } - - @TestOnly - TracesSampler(final @NotNull SentryOptions options, final @Nullable Random random) { - this.options = options; + this.options = Objects.requireNonNull(options, "options are required"); } - @SuppressWarnings("deprecation") + @SuppressWarnings({"deprecation", "ObjectToString"}) @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); - System.out.println("sample rand used in TracesSampler " + sampleRand); +// new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { - return samplingContextSamplingDecision; + return SampleRateUtils.backfilledSampleRand(samplingContextSamplingDecision); } Double profilesSampleRate = null; @@ -68,7 +65,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont final TracesSamplingDecision parentSamplingDecision = samplingContext.getTransactionContext().getParentSamplingDecision(); if (parentSamplingDecision != null) { - return parentSamplingDecision; + return SampleRateUtils.backfilledSampleRand(parentSamplingDecision); } final @Nullable Double tracesSampleRateFromOptions = options.getTracesSampleRate(); @@ -86,7 +83,7 @@ public TracesSamplingDecision sample(final @NotNull SamplingContext samplingCont profilesSampleRate); } - return new TracesSamplingDecision(false, null, false, null); + return new TracesSamplingDecision(false, null, sampleRand, false, null); } private boolean sample(final @NotNull Double sampleRate, final @NotNull Double sampleRand) { diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index 233953c4941..fcb4684fbfc 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -3,6 +3,8 @@ import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.Objects; +import io.sentry.util.TracingUtils; + import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,9 +30,6 @@ public static TransactionContext fromPropagationContext( @NotNull Baggage baggage = propagationContext.getBaggage(); - // TODO is this ok? -// baggage.freeze(); - Double sampleRate = baggage.getSampleRateDouble(); if (parentSampled != null) { if (sampleRate != null) { @@ -95,6 +94,7 @@ public TransactionContext( this.name = Objects.requireNonNull(name, "name is required"); this.transactionNameSource = transactionNameSource; this.setSamplingDecision(samplingDecision); + this.baggage = TracingUtils.ensureBaggage(null, samplingDecision); } @ApiStatus.Internal @@ -108,7 +108,7 @@ public TransactionContext( this.name = DEFAULT_TRANSACTION_NAME; this.parentSamplingDecision = parentSamplingDecision; this.transactionNameSource = DEFAULT_NAME_SOURCE; - this.baggage = baggage; + this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); } public @NotNull String getName() { diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index 77392a15720..25cd46d0fdc 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -1,8 +1,11 @@ package io.sentry.util; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import io.sentry.TracesSamplingDecision; + @ApiStatus.Internal public final class SampleRateUtils { @@ -23,7 +26,9 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return isValidRate(profilesSampleRate, true); } - public static Double backfilledSampleRand( + // TODO test + @SuppressWarnings("ObjectToString") + public static @NotNull Double backfilledSampleRand( final @Nullable Double sampleRand, final @Nullable Double sampleRate, final @Nullable Boolean sampled) { @@ -31,6 +36,10 @@ public static Double backfilledSampleRand( return sampleRand; } + new RuntimeException( + "backfilling sample rand " + sampleRand + " rate " + sampleRate + " sampled " + sampled) + .printStackTrace(); + double newSampleRand = SentryRandom.current().nextDouble(); if (sampleRate != null && sampled != null) { if (sampled) { @@ -43,6 +52,17 @@ public static Double backfilledSampleRand( return newSampleRand; } + // TODO test + @SuppressWarnings("ObjectToString") + public static @NotNull TracesSamplingDecision backfilledSampleRand(final @NotNull TracesSamplingDecision samplingDecision) { + if (samplingDecision.getSampleRand() != null) { + return samplingDecision; + } + + final @NotNull Double sampleRand = backfilledSampleRand(null, samplingDecision.getSampleRate(), samplingDecision.getSampled()); + return new TracesSamplingDecision(samplingDecision.getSampled(), samplingDecision.getSampleRate(), sampleRand, samplingDecision.getProfileSampled(), samplingDecision.getProfileSampleRate()); + } + private static boolean isValidRate(final @Nullable Double rate, final boolean allowNull) { if (rate == null) { return allowNull; diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index a8093c89b85..b43fc960608 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -6,9 +6,12 @@ import io.sentry.IScope; import io.sentry.IScopes; import io.sentry.ISpan; +import io.sentry.NoOpLogger; import io.sentry.PropagationContext; import io.sentry.SentryOptions; import io.sentry.SentryTraceHeader; +import io.sentry.TracesSamplingDecision; + import java.util.List; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -58,7 +61,8 @@ public static void startNewTrace(final @NotNull IScopes scopes) { if (returnValue.propagationContext != null) { final @NotNull PropagationContext propagationContext = returnValue.propagationContext; final @NotNull Baggage baggage = propagationContext.getBaggage(); - final @NotNull BaggageHeader baggageHeader = BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); + final @NotNull BaggageHeader baggageHeader = + BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); return new TracingHeaders( new SentryTraceHeader( @@ -144,4 +148,71 @@ public static boolean isIgnored( return false; } + + /** + * Ensures a non null baggage instance is present by creating a new Baggage instance if null + * is passed in. + * + * Also ensures there is a sampleRand value present on the baggage if it is still mutable. + * If the baggage should be frozen, it also takes care of freezing it. + * + * @param incomingBaggage a nullable baggage instance, if null a new one will be created + * @param decision a TracesSamplingDecision for potentially backfilling sampleRand to match that decision + * @return previous baggage instance or a new one + */ + @ApiStatus.Internal + public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable TracesSamplingDecision decision) { + final @Nullable Boolean decisionSampled = decision == null ? null : decision.getSampled(); + final @Nullable Double decisionSampleRate = decision == null ? null : decision.getSampleRate(); + final @Nullable Double decisionSampleRand = decision == null ? null : decision.getSampleRand(); + + return ensureBaggage(incomingBaggage, decisionSampled, decisionSampleRate, decisionSampleRand); + } + + /** + * Ensures a non null baggage instance is present by creating a new Baggage instance if null + * is passed in. + * + * Also ensures there is a sampleRand value present on the baggage if it is still mutable. + * If the baggage should be frozen, it also takes care of freezing it. + * + * @param incomingBaggage a nullable baggage instance, if null a new one will be created + * @param decisionSampled sampled decision for potential backfilling + * @param decisionSampleRate sampleRate for potential backfilling + * @param decisionSampleRand sampleRand to be used if none in baggage + * @return previous baggage instance or a new one + */ + @ApiStatus.Internal + public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable Boolean decisionSampled, final @Nullable Double decisionSampleRate, final @Nullable Double decisionSampleRand) { + final @NotNull Baggage baggage = incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; + + StringBuilder sb = new StringBuilder("sample rand"); + + if (baggage.getSampleRand() == null) { + final @Nullable Double baggageSampleRate = baggage.getSampleRateDouble(); + final @Nullable Double baggageSampleRand = baggage.getSampleRandDouble(); + + final @Nullable Double sampleRandMaybe = baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; + sb.append(" [baggage " + sampleRandMaybe + "]"); + final @Nullable Double sampleRateMaybe = baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; + final @NotNull Double sampleRand = + SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); + sb.append(" [setting sample rand on baggage " + baggage + "]"); + baggage.setSampleRandDouble(sampleRand); + sb.append(" {" + sampleRand + "}"); + } + if (baggage.isMutable()) { + sb.append(" [baggage mutable]"); + // cannot freeze on scope fork + if (baggage.isShouldFreeze()) { + sb.append(" [freezing baggage]"); + baggage.freeze(); + } + } else { + sb.append(" [baggage already frozen]"); + } +// new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); + + return baggage; + } } diff --git a/sentry/src/test/java/io/sentry/BaggageTest.kt b/sentry/src/test/java/io/sentry/BaggageTest.kt index 8beae33668b..242d10dff2e 100644 --- a/sentry/src/test/java/io/sentry/BaggageTest.kt +++ b/sentry/src/test/java/io/sentry/BaggageTest.kt @@ -8,6 +8,7 @@ import java.util.UUID import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertTrue @@ -340,8 +341,9 @@ class BaggageTest { } @Test - fun `setting values if header contains sentry values has no effect`() { + fun `setting values on frozen baggage has no effect`() { val baggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction", logger) + baggage.freeze() baggage.traceId = "b" baggage.traceId = "c" @@ -352,6 +354,18 @@ class BaggageTest { assertEquals("sentry-trace_id=a,sentry-transaction=sentryTransaction", baggage.toHeaderString(null)) } + @Test + fun `if header contains sentry values baggage is marked as shouldFreeze`() { + val baggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction", logger) + assertTrue(baggage.isShouldFreeze) + } + + @Test + fun `if header does not contain sentry values baggage is not marked as shouldFreeze`() { + val baggage = Baggage.fromHeader("a=b", logger) + assertFalse(baggage.isShouldFreeze) + } + @Test fun `value may contain = sign`() { val baggage = Baggage(logger) @@ -535,6 +549,27 @@ class BaggageTest { assertEquals("abc", traceContext.unknown!!["anewkey"]) } + @Test + fun `header with sentry values is marked for freezing`() { + val baggage = + Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction") + assertTrue(baggage.isShouldFreeze) + } + + @Test + fun `header with sentry sample rand only is not marked for freezing`() { + val baggage = + Baggage.fromHeader("sentry-sample_rand=0.3") + assertFalse(baggage.isShouldFreeze) + } + + @Test + fun `header without sentry values is not marked for freezing`() { + val baggage = + Baggage.fromHeader("a=b,c=d") + assertFalse(baggage.isShouldFreeze) + } + /** * token = 1*tchar * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index e9d12ff4b17..8e3140faa11 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -456,16 +456,16 @@ class JsonSerializerTest { @Test fun `serializes trace context`() { - val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", "userId", "transaction", "0.5", "true", SentryId("3367f5196c494acaae85bbbd535379aa"))) - val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","user_id":"userId","transaction":"transaction","sample_rate":"0.5","sampled":"true","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" + val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", "userId", "transaction", "0.5", "true", SentryId("3367f5196c494acaae85bbbd535379aa"), "0.25")) + val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","user_id":"userId","transaction":"transaction","sample_rate":"0.5","sample_rand":"0.25","sampled":"true","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" val json = serializeToString(traceContext) assertEquals(expected, json) } @Test fun `serializes trace context with user having null id`() { - val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", null, "transaction", "0.6", "false", SentryId("3367f5196c494acaae85bbbd535379aa"))) - val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","transaction":"transaction","sample_rate":"0.6","sampled":"false","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" + val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", null, "transaction", "0.6", "false", SentryId("3367f5196c494acaae85bbbd535379aa"), "0.3")) + val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","transaction":"transaction","sample_rate":"0.6","sample_rand":"0.3","sampled":"false","replay_id":"3367f5196c494acaae85bbbd535379aa"}}""" val json = serializeToString(traceContext) assertEquals(expected, json) } diff --git a/sentry/src/test/java/io/sentry/PropagationContextTest.kt b/sentry/src/test/java/io/sentry/PropagationContextTest.kt new file mode 100644 index 00000000000..39fffe9f89a --- /dev/null +++ b/sentry/src/test/java/io/sentry/PropagationContextTest.kt @@ -0,0 +1,43 @@ +package io.sentry + +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class PropagationContextTest { + + @Test + fun `freezes baggage with sentry values`() { + val propagationContext = PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + "2722d9f6ec019ade60c776169d9a8904-cedf5b7571cb4972-1", + "sentry-trace_id=a,sentry-transaction=sentryTransaction" + ) + assertFalse(propagationContext.baggage.isMutable) + assertTrue(propagationContext.baggage.isShouldFreeze) + } + + @Test + fun `does not freeze baggage without sentry values`() { + val propagationContext = PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + "2722d9f6ec019ade60c776169d9a8904-cedf5b7571cb4972-1", + "a=b" + ) + assertTrue(propagationContext.baggage.isMutable) + assertFalse(propagationContext.baggage.isShouldFreeze) + } + + @Test + fun `creates new baggage if none passed`() { + val propagationContext = PropagationContext.fromHeaders( + NoOpLogger.getInstance(), + "2722d9f6ec019ade60c776169d9a8904-cedf5b7571cb4972-1", + null as? String? + ) + assertNotNull(propagationContext.baggage) + assertTrue(propagationContext.baggage.isMutable) + assertFalse(propagationContext.baggage.isShouldFreeze) + } +} diff --git a/sentry/src/test/java/io/sentry/ScopeTest.kt b/sentry/src/test/java/io/sentry/ScopeTest.kt index 0d13f0d21ae..b8025735e8a 100644 --- a/sentry/src/test/java/io/sentry/ScopeTest.kt +++ b/sentry/src/test/java/io/sentry/ScopeTest.kt @@ -828,7 +828,7 @@ class ScopeTest { } val scope = Scope(options) - scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null, null) + scope.propagationContext = PropagationContext(SentryId("64cf554cc8d74c6eafa3e08b7c984f6d"), SpanId(), null, null, null) verify(observer).setTrace( argThat { traceId.toString() == "64cf554cc8d74c6eafa3e08b7c984f6d" }, eq(scope) diff --git a/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt b/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt index 8b00df543d6..ce0e4a4ae40 100644 --- a/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt @@ -24,7 +24,8 @@ class TraceContextSerializationTest { "0252ec25-cd0a-4230-bd2f-936a4585637e", "0.00000021", "true", - SentryId("3367f5196c494acaae85bbbd535379aa") + SentryId("3367f5196c494acaae85bbbd535379aa"), + "0.00000012" ) } private val fixture = Fixture() diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index 06eb60aece2..a192aed6da0 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -9,23 +9,19 @@ import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue class TracesSamplerTest { class Fixture { internal fun getSut( - randomResult: Double? = null, tracesSampleRate: Double? = null, profilesSampleRate: Double? = null, tracesSamplerCallback: SentryOptions.TracesSamplerCallback? = null, profilesSamplerCallback: SentryOptions.ProfilesSamplerCallback? = null, logger: ILogger? = null ): TracesSampler { - val random = mock() - if (randomResult != null) { - whenever(random.nextDouble()).thenReturn(randomResult) - } val options = SentryOptions() if (tracesSampleRate != null) { options.tracesSampleRate = tracesSampleRate @@ -43,7 +39,7 @@ class TracesSamplerTest { options.isDebug = true options.setLogger(logger) } - return TracesSampler(options, random) + return TracesSampler(options) } } @@ -51,103 +47,115 @@ class TracesSamplerTest { @Test fun `when tracesSampleRate is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSampleRate = 0.2, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 0.2, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.9)) assertFalse(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test fun `when tracesSampleRate is set and random returns lower number returns true`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 0.2, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 0.2, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.1)) assertTrue(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSampleRate = 1.0, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.9)) assertTrue(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is set and random returns lower number returns true`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0, profilesSampleRate = 0.2) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSampleRate = 0.2) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.1)) assertTrue(samplingDecision.sampled) assertTrue(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when trace is not sampled, profile is not sampled`() { - val sampler = fixture.getSut(randomResult = 0.3, tracesSampleRate = 0.0, profilesSampleRate = 1.0) - val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null)) + val sampler = fixture.getSut(tracesSampleRate = 0.0, profilesSampleRate = 1.0) + val samplingDecision = sampler.sample(SamplingContext(TransactionContext("name", "op"), null, 0.3)) assertFalse(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertEquals(1.0, samplingDecision.profileSampleRate) + assertEquals(0.3, samplingDecision.sampleRand) } @Test fun `when tracesSampleRate is not set, tracesSampler is set and random returns lower number returns true`() { val sampler = fixture.getSut( - randomResult = 0.1, tracesSamplerCallback = { 0.2 }, profilesSamplerCallback = { 0.2 } ) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is not set, profilesSampler is set and random returns lower number returns true`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertTrue(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when tracesSampleRate is not set, tracesSampler is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSamplerCallback = { 0.2 }) + val sampler = fixture.getSut(tracesSamplerCallback = { 0.2 }) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.9 ) ) assertFalse(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is not set, profilesSampler is set and random returns greater number returns false`() { - val sampler = fixture.getSut(randomResult = 0.9, tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSamplerCallback = { 0.2 }) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.9 ) ) assertTrue(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertEquals(0.2, samplingDecision.profileSampleRate) + assertEquals(0.9, samplingDecision.sampleRand) } @Test @@ -158,11 +166,13 @@ class TracesSamplerTest { val samplingDecision = sampler.sample( SamplingContext( transactionContextParentSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertNotNull(samplingDecision.sampleRand) } @Test @@ -173,34 +183,39 @@ class TracesSamplerTest { val samplingDecision = sampler.sample( SamplingContext( transactionContextParentSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertTrue(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) + assertNotNull(samplingDecision.sampleRand) } @Test fun `when tracesSampler returns null and tracesSampleRate is set sampler uses it as a sampling decision`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 0.2, tracesSamplerCallback = null) + val sampler = fixture.getSut(tracesSampleRate = 0.2, tracesSamplerCallback = null) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertEquals(0.2, samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampler returns null and profilesSampleRate is set sampler uses it as a sampling decision`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0, profilesSampleRate = 0.2, profilesSamplerCallback = null) + val sampler = fixture.getSut(tracesSampleRate = 1.0, profilesSampleRate = 0.2, profilesSamplerCallback = null) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) @@ -210,29 +225,33 @@ class TracesSamplerTest { @Test fun `when tracesSampleRate is not set, and tracesSampler is not set returns false`() { - val sampler = fixture.getSut(randomResult = 0.1) + val sampler = fixture.getSut() val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertFalse(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test fun `when profilesSampleRate is not set, and profilesSampler is not set returns false`() { - val sampler = fixture.getSut(randomResult = 0.1, tracesSampleRate = 1.0) + val sampler = fixture.getSut(tracesSampleRate = 1.0) val samplingDecision = sampler.sample( SamplingContext( TransactionContext("name", "op"), - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecision.sampled) assertFalse(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) + assertEquals(0.1, samplingDecision.sampleRand) } @Test @@ -243,11 +262,13 @@ class TracesSamplerTest { val samplingDecision = sampler.sample( SamplingContext( transactionContextParentNotSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertFalse(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertNotNull(samplingDecision.sampleRand) assertFalse(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) @@ -256,11 +277,13 @@ class TracesSamplerTest { val samplingDecisionParentSampled = sampler.sample( SamplingContext( transactionContextParentSampled, - CustomSamplingContext() + CustomSamplingContext(), + 0.1 ) ) assertTrue(samplingDecisionParentSampled.sampled) assertNull(samplingDecisionParentSampled.sampleRate) + assertNotNull(samplingDecisionParentSampled.sampleRand) assertTrue(samplingDecisionParentSampled.profileSampled) assertNull(samplingDecisionParentSampled.profileSampleRate) } @@ -288,27 +311,30 @@ class TracesSamplerTest { val transactionContextNotSampled = TransactionContext("name", "op") transactionContextNotSampled.sampled = false val samplingDecision = - sampler.sample(SamplingContext(transactionContextNotSampled, CustomSamplingContext())) + sampler.sample(SamplingContext(transactionContextNotSampled, CustomSamplingContext(), 0.1)) assertFalse(samplingDecision.sampled) assertNull(samplingDecision.sampleRate) + assertNotNull(samplingDecision.sampleRand) assertFalse(samplingDecision.profileSampled) assertNull(samplingDecision.profileSampleRate) val transactionContextSampled = TransactionContext("name", "op") transactionContextSampled.setSampled(true, true) val samplingDecisionContextSampled = - sampler.sample(SamplingContext(transactionContextSampled, CustomSamplingContext())) + sampler.sample(SamplingContext(transactionContextSampled, CustomSamplingContext(), 0.1)) assertTrue(samplingDecisionContextSampled.sampled) assertNull(samplingDecisionContextSampled.sampleRate) + assertNotNull(samplingDecisionContextSampled.sampleRand) assertTrue(samplingDecisionContextSampled.profileSampled) assertNull(samplingDecisionContextSampled.profileSampleRate) val transactionContextUnsampledWithProfile = TransactionContext("name", "op") transactionContextUnsampledWithProfile.setSampled(false, true) val samplingDecisionContextUnsampledWithProfile = - sampler.sample(SamplingContext(transactionContextUnsampledWithProfile, CustomSamplingContext())) + sampler.sample(SamplingContext(transactionContextUnsampledWithProfile, CustomSamplingContext(), 0.1)) assertFalse(samplingDecisionContextUnsampledWithProfile.sampled) assertNull(samplingDecisionContextUnsampledWithProfile.sampleRate) + assertNotNull(samplingDecisionContextUnsampledWithProfile.sampleRand) assertFalse(samplingDecisionContextUnsampledWithProfile.profileSampled) assertNull(samplingDecisionContextUnsampledWithProfile.profileSampleRate) } @@ -326,7 +352,7 @@ class TracesSamplerTest { logger = logger ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.1) ) assertFalse(decision.profileSampled) verify(logger).log(eq(SentryLevel.ERROR), any(), eq(exception)) @@ -336,7 +362,6 @@ class TracesSamplerTest { fun `when a profilingRate and a ProfilesSamplerCallback is set but the callback throws an exception then profiling should still be enabled`() { val exception = Exception("faulty ProfilesSamplerCallback") val sampler = fixture.getSut( - randomResult = 0.0, tracesSampleRate = 1.0, profilesSampleRate = 1.0, profilesSamplerCallback = { @@ -344,9 +369,10 @@ class TracesSamplerTest { } ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.0) ) assertTrue(decision.profileSampled) + assertEquals(0.0, decision.sampleRand) } @Test @@ -361,9 +387,10 @@ class TracesSamplerTest { logger = logger ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.1) ) assertFalse(decision.sampled) + assertEquals(0.1, decision.sampleRand) verify(logger).log(eq(SentryLevel.ERROR), any(), eq(exception)) } @@ -371,15 +398,15 @@ class TracesSamplerTest { fun `when a tracesSampleRate and a TracesSamplerCallback is set but the callback throws an exception then tracing should still be enabled`() { val exception = Exception("faulty TracesSamplerCallback") val sampler = fixture.getSut( - randomResult = 0.0, tracesSampleRate = 1.0, tracesSamplerCallback = { throw exception } ) val decision = sampler.sample( - SamplingContext(TransactionContext("name", "op"), null) + SamplingContext(TransactionContext("name", "op"), null, 0.0) ) assertTrue(decision.sampled) + assertEquals(0.0, decision.sampleRand) } } diff --git a/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt b/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt index e5c81bc70e4..5b1117aa6cf 100644 --- a/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt +++ b/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt @@ -1,7 +1,10 @@ package io.sentry.util +import io.sentry.TracesSamplingDecision import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue class SampleRateUtilTest { @@ -130,4 +133,62 @@ class SampleRateUtilTest { fun `accepts null profiles sample rate`() { assertTrue(SampleRateUtils.isValidProfilesSampleRate(null)) } + + @Test + fun `fills sample rand on decision if missing`() { + val decision = SampleRateUtils.backfilledSampleRand(TracesSamplingDecision(true)) + assertNotNull(decision.sampleRand) + } + + @Test + fun `keeps sample rand on decision if present`() { + val decision = SampleRateUtils.backfilledSampleRand(TracesSamplingDecision(true, 0.1, 0.5)) + assertEquals(0.5, decision.sampleRand) + } + + @Test + fun `uses sampleRand and does not backfill`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(0.3, null, null) + assertEquals(0.3, sampleRand) + } + + @Test + fun `backfills sampleRand if missing`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, null, null) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 1) + } + + @Test + fun `backfills sampleRand if missing with sampled true`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, null, true) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 1) + } + + @Test + fun `backfills sampleRand if missing with sampled false`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, null, false) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 1) + } + + @Test + fun `backfills sampleRand if missing with sampled true below samlpe rate`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, 0.0001, true) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0) + assertTrue(sampleRand < 0.0001) + } + + @Test + fun `backfills sampleRand if missing with sampled false above sample rate`() { + val sampleRand = SampleRateUtils.backfilledSampleRand(null, 0.9999, false) + assertNotNull(sampleRand) + assertTrue(sampleRand >= 0.9999) + assertTrue(sampleRand < 1) + } } diff --git a/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt b/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt index e5403f54d99..ed378ee9606 100644 --- a/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt +++ b/sentry/src/test/java/io/sentry/util/TracingUtilsTest.kt @@ -2,15 +2,19 @@ package io.sentry.util import io.sentry.Baggage import io.sentry.IScopes +import io.sentry.NoOpLogger import io.sentry.NoOpSpan +import io.sentry.PropagationContext import io.sentry.Scope import io.sentry.ScopeCallback import io.sentry.SentryOptions import io.sentry.SentryTracer import io.sentry.Span +import io.sentry.SpanId import io.sentry.SpanOptions import io.sentry.TracesSamplingDecision import io.sentry.TransactionContext +import io.sentry.protocol.SentryId import org.junit.Test import org.mockito.kotlin.any import org.mockito.kotlin.doAnswer @@ -21,6 +25,7 @@ import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertSame import kotlin.test.assertTrue class TracingUtilsTest { @@ -129,7 +134,7 @@ class TracingUtilsTest { @Test fun `returns headers if allowed from scope without span leaving frozen baggage alone`() { - fixture.scope.propagationContext.baggage = Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET").also { it.freeze() } + fixture.scope.propagationContext = PropagationContext(SentryId(), SpanId(), null, Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET").also { it.freeze() }, true) fixture.setup() val headers = TracingUtils.traceIfAllowed(fixture.scopes, "https://sentry.io/hello", fixture.preExistingBaggage, null) @@ -208,23 +213,11 @@ class TracingUtilsTest { assertNotEquals(propagationContextBefore.spanId, fixture.scope.propagationContext.spanId) } - @Test - fun `creates new baggage if none present`() { - fixture.setup() - assertNull(fixture.scope.propagationContext.baggage) - - TracingUtils.maybeUpdateBaggage(fixture.scope, fixture.options) - - assertNotNull(fixture.scope.propagationContext.baggage) - assertEquals(fixture.scope.propagationContext.traceId.toString(), fixture.scope.propagationContext.baggage!!.traceId) - assertFalse(fixture.scope.propagationContext.baggage!!.isMutable) - } - @Test fun `updates mutable baggage`() { fixture.setup() // not frozen because it doesn't contain sentry-* keys - fixture.scope.propagationContext.baggage = Baggage.fromHeader(fixture.preExistingBaggage) + fixture.scope.propagationContext = PropagationContext(SentryId(), SpanId(), null, Baggage.fromHeader(fixture.preExistingBaggage), true) TracingUtils.maybeUpdateBaggage(fixture.scope, fixture.options) @@ -236,11 +229,136 @@ class TracingUtilsTest { fun `does not change frozen baggage`() { fixture.setup() // frozen automatically because it contains sentry-* keys - fixture.scope.propagationContext.baggage = Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET") + fixture.scope.propagationContext = PropagationContext(SentryId(), SpanId(), null, Baggage.fromHeader("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET"), true) TracingUtils.maybeUpdateBaggage(fixture.scope, fixture.options) assertEquals("2722d9f6ec019ade60c776169d9a8904", fixture.scope.propagationContext.baggage!!.traceId) assertFalse(fixture.scope.propagationContext.baggage!!.isMutable) } + + @Test + fun `returns baggage if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + } + + @Test + fun `crates new baggage if null passed in that has sampleRand set and is mutable`() { + val baggage = TracingUtils.ensureBaggage( + null, + null as? TracesSamplingDecision? + ) + assertNotNull(baggage) + assertNotNull(baggage.sampleRand) + assertTrue(baggage.isMutable) + assertFalse(baggage.isShouldFreeze) + } + + @Test + fun `backfills sampleRand on passed in baggage if missing`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNotNull(baggage.sampleRand) + assertTrue(baggage.isMutable) + } + + @Test + fun `keeps sampleRand on passed in baggage if present`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + incomingBaggage.sampleRand = "0.3" + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertEquals("0.3", baggage.sampleRand) + assertTrue(baggage.isMutable) + } + + @Test + fun `does not backfill sampleRand on passed in baggage if frozen`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + incomingBaggage.freeze() + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNull(baggage.sampleRand) + assertFalse(baggage.isMutable) + } + + @Test + fun `freezes passed in baggage if should be frozen`() { + // markes as shouldFreeze=true due to sentry values being present in header + val incomingBaggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction") + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNotNull(baggage.sampleRand) + assertFalse(baggage.isMutable) + } + + @Test + fun `does not freeze passed in baggage if should not be frozen`() { + // markes as shouldFreeze=false due to no sentry values being present in header + val incomingBaggage = Baggage.fromHeader("a=b,c=d") + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + null as? TracesSamplingDecision? + ) + assertSame(incomingBaggage, baggage) + assertNotNull(baggage.sampleRand) + assertTrue(baggage.isMutable) + } + + @Test + fun `uses sample rand if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + TracesSamplingDecision(true, null, 0.123) + ) + assertSame(incomingBaggage, baggage) + assertEquals("0.123", baggage.sampleRand) + } + + @Test + fun `uses sample rate and sampled flag true if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + TracesSamplingDecision(true, 0.0001, null) + ) + assertSame(incomingBaggage, baggage) + val sampleRand = baggage.sampleRandDouble + assertNotNull(sampleRand) + assertTrue(sampleRand < 0.0001) + assertTrue(sampleRand >= 0.0) + } + + @Test + fun `uses sample rate and sampled flag false if passed in`() { + val incomingBaggage = Baggage(NoOpLogger.getInstance()) + val baggage = TracingUtils.ensureBaggage( + incomingBaggage, + TracesSamplingDecision(false, 0.9999, null) + ) + assertSame(incomingBaggage, baggage) + val sampleRand = baggage.sampleRandDouble + assertNotNull(sampleRand) + assertTrue(sampleRand < 1.0) + assertTrue(sampleRand >= 0.9999) + } } diff --git a/sentry/src/test/resources/json/sentry_envelope_header.json b/sentry/src/test/resources/json/sentry_envelope_header.json index 626e9cbbc23..23580aab660 100644 --- a/sentry/src/test/resources/json/sentry_envelope_header.json +++ b/sentry/src/test/resources/json/sentry_envelope_header.json @@ -26,6 +26,7 @@ "user_id": "c052c566-6619-45f5-a61f-172802afa39a", "transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e", "sample_rate": "0.00000021", + "sample_rand": "0.00000012", "sampled": "true", "replay_id": "3367f5196c494acaae85bbbd535379aa" }, diff --git a/sentry/src/test/resources/json/trace_state.json b/sentry/src/test/resources/json/trace_state.json index db745e52136..a5eabb35834 100644 --- a/sentry/src/test/resources/json/trace_state.json +++ b/sentry/src/test/resources/json/trace_state.json @@ -6,6 +6,7 @@ "user_id": "c052c566-6619-45f5-a61f-172802afa39a", "transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e", "sample_rate": "0.00000021", + "sample_rand": "0.00000012", "sampled": "true", "replay_id": "3367f5196c494acaae85bbbd535379aa" } From 417ddf0c8ad41989e0bec04fbcf667bb3fd1a485 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 06:51:10 +0100 Subject: [PATCH 17/29] format + api --- sentry/api/sentry.api | 3 ++ .../java/io/sentry/PropagationContext.java | 2 - sentry/src/main/java/io/sentry/Span.java | 3 +- .../main/java/io/sentry/TracesSampler.java | 5 +-- .../java/io/sentry/TransactionContext.java | 3 +- .../java/io/sentry/util/SampleRateUtils.java | 16 ++++--- .../java/io/sentry/util/TracingUtils.java | 42 +++++++++++-------- .../test/java/io/sentry/TracesSamplerTest.kt | 2 - 8 files changed, 43 insertions(+), 33 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 62f62571149..ddea3df1386 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -6297,6 +6297,7 @@ public final class io/sentry/util/Random : java/io/Serializable { public final class io/sentry/util/SampleRateUtils { public fun ()V + public static fun backfilledSampleRand (Lio/sentry/TracesSamplingDecision;)Lio/sentry/TracesSamplingDecision; public static fun backfilledSampleRand (Ljava/lang/Double;Ljava/lang/Double;Ljava/lang/Boolean;)Ljava/lang/Double; public static fun isValidProfilesSampleRate (Ljava/lang/Double;)Z public static fun isValidSampleRate (Ljava/lang/Double;)Z @@ -6333,6 +6334,8 @@ public final class io/sentry/util/StringUtils { public final class io/sentry/util/TracingUtils { public fun ()V + public static fun ensureBaggage (Lio/sentry/Baggage;Lio/sentry/TracesSamplingDecision;)Lio/sentry/Baggage; + public static fun ensureBaggage (Lio/sentry/Baggage;Ljava/lang/Boolean;Ljava/lang/Double;Ljava/lang/Double;)Lio/sentry/Baggage; public static fun isIgnored (Ljava/util/List;Ljava/lang/String;)Z public static fun maybeUpdateBaggage (Lio/sentry/IScope;Lio/sentry/SentryOptions;)Lio/sentry/PropagationContext; public static fun startNewTrace (Lio/sentry/IScopes;)V diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 694f62052d4..c126f19a5d3 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -2,9 +2,7 @@ import io.sentry.exception.InvalidSentryTraceHeaderException; import io.sentry.protocol.SentryId; -import io.sentry.util.SampleRateUtils; import io.sentry.util.TracingUtils; - import java.util.Arrays; import java.util.List; import org.jetbrains.annotations.ApiStatus; diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index da8cf4765e5..5b5cb6eadc8 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -154,7 +154,8 @@ public Span( @Override public @NotNull SentryTraceHeader toSentryTrace() { - SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); + SentryTraceHeader sentryTraceHeader = + new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); System.out.println("outgoing sentry-trace:"); System.out.println(sentryTraceHeader.getValue()); return sentryTraceHeader; diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 741440369c4..300f6cb6672 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -1,13 +1,10 @@ package io.sentry; import io.sentry.util.Objects; -import io.sentry.util.Random; import io.sentry.util.SampleRateUtils; - import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.TestOnly; @ApiStatus.Internal public final class TracesSampler { @@ -21,7 +18,7 @@ public TracesSampler(final @NotNull SentryOptions options) { @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); -// new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); + // new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index fcb4684fbfc..63f6eba9a6d 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -4,7 +4,6 @@ import io.sentry.protocol.TransactionNameSource; import io.sentry.util.Objects; import io.sentry.util.TracingUtils; - import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -108,7 +107,7 @@ public TransactionContext( this.name = DEFAULT_TRANSACTION_NAME; this.parentSamplingDecision = parentSamplingDecision; this.transactionNameSource = DEFAULT_NAME_SOURCE; - this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); + this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); // todo test } public @NotNull String getName() { diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index 25cd46d0fdc..3d7f247dfe2 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -1,11 +1,10 @@ package io.sentry.util; +import io.sentry.TracesSamplingDecision; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import io.sentry.TracesSamplingDecision; - @ApiStatus.Internal public final class SampleRateUtils { @@ -54,13 +53,20 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR // TODO test @SuppressWarnings("ObjectToString") - public static @NotNull TracesSamplingDecision backfilledSampleRand(final @NotNull TracesSamplingDecision samplingDecision) { + public static @NotNull TracesSamplingDecision backfilledSampleRand( + final @NotNull TracesSamplingDecision samplingDecision) { if (samplingDecision.getSampleRand() != null) { return samplingDecision; } - final @NotNull Double sampleRand = backfilledSampleRand(null, samplingDecision.getSampleRate(), samplingDecision.getSampled()); - return new TracesSamplingDecision(samplingDecision.getSampled(), samplingDecision.getSampleRate(), sampleRand, samplingDecision.getProfileSampled(), samplingDecision.getProfileSampleRate()); + final @NotNull Double sampleRand = + backfilledSampleRand(null, samplingDecision.getSampleRate(), samplingDecision.getSampled()); + return new TracesSamplingDecision( + samplingDecision.getSampled(), + samplingDecision.getSampleRate(), + sampleRand, + samplingDecision.getProfileSampled(), + samplingDecision.getProfileSampleRate()); } private static boolean isValidRate(final @Nullable Double rate, final boolean allowNull) { diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index b43fc960608..f1e22e04955 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -11,7 +11,6 @@ import io.sentry.SentryOptions; import io.sentry.SentryTraceHeader; import io.sentry.TracesSamplingDecision; - import java.util.List; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -150,18 +149,20 @@ public static boolean isIgnored( } /** - * Ensures a non null baggage instance is present by creating a new Baggage instance if null - * is passed in. + * Ensures a non null baggage instance is present by creating a new Baggage instance if null is + * passed in. * - * Also ensures there is a sampleRand value present on the baggage if it is still mutable. - * If the baggage should be frozen, it also takes care of freezing it. + *

Also ensures there is a sampleRand value present on the baggage if it is still mutable. If + * the baggage should be frozen, it also takes care of freezing it. * * @param incomingBaggage a nullable baggage instance, if null a new one will be created - * @param decision a TracesSamplingDecision for potentially backfilling sampleRand to match that decision + * @param decision a TracesSamplingDecision for potentially backfilling sampleRand to match that + * decision * @return previous baggage instance or a new one */ @ApiStatus.Internal - public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable TracesSamplingDecision decision) { + public static @NotNull Baggage ensureBaggage( + final @Nullable Baggage incomingBaggage, final @Nullable TracesSamplingDecision decision) { final @Nullable Boolean decisionSampled = decision == null ? null : decision.getSampled(); final @Nullable Double decisionSampleRate = decision == null ? null : decision.getSampleRate(); final @Nullable Double decisionSampleRand = decision == null ? null : decision.getSampleRand(); @@ -170,11 +171,11 @@ public static boolean isIgnored( } /** - * Ensures a non null baggage instance is present by creating a new Baggage instance if null - * is passed in. + * Ensures a non null baggage instance is present by creating a new Baggage instance if null is + * passed in. * - * Also ensures there is a sampleRand value present on the baggage if it is still mutable. - * If the baggage should be frozen, it also takes care of freezing it. + *

Also ensures there is a sampleRand value present on the baggage if it is still mutable. If + * the baggage should be frozen, it also takes care of freezing it. * * @param incomingBaggage a nullable baggage instance, if null a new one will be created * @param decisionSampled sampled decision for potential backfilling @@ -183,8 +184,13 @@ public static boolean isIgnored( * @return previous baggage instance or a new one */ @ApiStatus.Internal - public static @NotNull Baggage ensureBaggage(final @Nullable Baggage incomingBaggage, final @Nullable Boolean decisionSampled, final @Nullable Double decisionSampleRate, final @Nullable Double decisionSampleRand) { - final @NotNull Baggage baggage = incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; + public static @NotNull Baggage ensureBaggage( + final @Nullable Baggage incomingBaggage, + final @Nullable Boolean decisionSampled, + final @Nullable Double decisionSampleRate, + final @Nullable Double decisionSampleRand) { + final @NotNull Baggage baggage = + incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; StringBuilder sb = new StringBuilder("sample rand"); @@ -192,11 +198,13 @@ public static boolean isIgnored( final @Nullable Double baggageSampleRate = baggage.getSampleRateDouble(); final @Nullable Double baggageSampleRand = baggage.getSampleRandDouble(); - final @Nullable Double sampleRandMaybe = baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; + final @Nullable Double sampleRandMaybe = + baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; sb.append(" [baggage " + sampleRandMaybe + "]"); - final @Nullable Double sampleRateMaybe = baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; + final @Nullable Double sampleRateMaybe = + baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; final @NotNull Double sampleRand = - SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); + SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); sb.append(" [setting sample rand on baggage " + baggage + "]"); baggage.setSampleRandDouble(sampleRand); sb.append(" {" + sampleRand + "}"); @@ -211,7 +219,7 @@ public static boolean isIgnored( } else { sb.append(" [baggage already frozen]"); } -// new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); + // new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); return baggage; } diff --git a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt index a192aed6da0..0fbc8e2f679 100644 --- a/sentry/src/test/java/io/sentry/TracesSamplerTest.kt +++ b/sentry/src/test/java/io/sentry/TracesSamplerTest.kt @@ -1,11 +1,9 @@ package io.sentry -import io.sentry.util.Random import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse From 314587780af3c4f84529339e2b0824552faf64c8 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:23:04 +0100 Subject: [PATCH 18/29] cleanup --- .../opentelemetry/OtelSentryPropagator.java | 12 -------- .../spring/boot/jakarta/ApiService.java | 23 -------------- .../spring/boot/jakarta/PersonController.java | 5 +--- sentry/src/main/java/io/sentry/Baggage.java | 18 +---------- .../java/io/sentry/CombinedScopeView.java | 1 + .../src/main/java/io/sentry/HubAdapter.java | 1 + .../main/java/io/sentry/HubScopesWrapper.java | 1 + sentry/src/main/java/io/sentry/NoOpHub.java | 1 + .../src/main/java/io/sentry/NoOpScopes.java | 1 + .../src/main/java/io/sentry/OutboxSender.java | 3 +- .../java/io/sentry/PropagationContext.java | 2 +- sentry/src/main/java/io/sentry/Scopes.java | 1 + .../main/java/io/sentry/ScopesAdapter.java | 1 + .../src/main/java/io/sentry/SentryTracer.java | 13 +------- sentry/src/main/java/io/sentry/Span.java | 6 +--- .../main/java/io/sentry/TracesSampler.java | 1 - .../java/io/sentry/TransactionContext.java | 2 +- .../java/io/sentry/util/SampleRateUtils.java | 8 ----- .../java/io/sentry/util/TracingUtils.java | 10 ------- .../java/io/sentry/TransactionContextTest.kt | 30 +++++++++++++++++++ 20 files changed, 45 insertions(+), 95 deletions(-) delete mode 100644 sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java index c57fdcbc8c8..fc2e3d426b7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSentryPropagator.java @@ -82,12 +82,8 @@ public void inject(final Context context, final C carrier, final TextMapSett setter.set(carrier, sentryTraceHeader.getName(), sentryTraceHeader.getValue()); final @Nullable BaggageHeader baggageHeader = tracingHeaders.getBaggageHeader(); if (baggageHeader != null) { - System.out.println("outgoing baggage: "); - System.out.println(baggageHeader.getValue()); setter.set(carrier, baggageHeader.getName(), baggageHeader.getValue()); } - } else { - System.out.println("not tracing headers found"); } } @@ -110,10 +106,6 @@ public Context extract( SentryTraceHeader sentryTraceHeader = new SentryTraceHeader(sentryTraceString); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); - System.out.println("incoming sentry-trace:"); - System.out.println(sentryTraceString); - System.out.println("incoming baggage:"); - System.out.println(baggageString); final Baggage baggage = Baggage.fromHeader(baggageString); final @NotNull TraceState traceState = TraceState.getDefault(); @@ -138,10 +130,6 @@ public Context extract( .getLogger() .log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId()); - // final @NotNull PropagationContext propagationContext = - // PropagationContext.fromHeaders(sentryTraceHeader, baggage, null); - // scopesToUse.getIsolationScope().setPropagationContext(propagationContext); - return modifiedContext; } catch (InvalidSentryTraceHeaderException e) { scopes diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java deleted file mode 100644 index c895b8b979c..00000000000 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/ApiService.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.sentry.samples.spring.boot.jakarta; - -import io.sentry.spring.jakarta.tracing.SentrySpan; -import org.jetbrains.annotations.NotNull; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; - -@Service -public class ApiService { - - private final RestClient restClient; - - public ApiService(RestClient restClient) { - this.restClient = restClient; - } - - @SentrySpan("annotation-span") - void apiRequest(final @NotNull String name) { - // restClient.get().uri("http://localhost:8000?q={name}", - // name).retrieve().body(String.class); - restClient.get().uri("http://localhost:8081/articles").retrieve().body(String.class); - } -} diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java index fe79122f5ff..5ce11d0a528 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java @@ -20,13 +20,11 @@ public class PersonController { private final PersonService personService; private final Tracer tracer; - private final ApiService apiService; private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class); - public PersonController(PersonService personService, Tracer tracer, ApiService apiService) { + public PersonController(PersonService personService, Tracer tracer) { this.personService = personService; this.tracer = tracer; - this.apiService = apiService; } @GetMapping("{id}") @@ -36,7 +34,6 @@ Person person(@PathVariable Long id) { ISpan currentSpan = Sentry.getSpan(); ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi"); try { - apiService.apiRequest(id.toString()); LOGGER.error("Trying person with id={}", id, new RuntimeException("error while loading")); throw new IllegalArgumentException("Something went wrong [id=" + id + "]"); } finally { diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 10a5d7bf68b..53b18e48a9e 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -152,7 +152,7 @@ public static Baggage fromEvent( // we don't persist sample rate baggage.setSampleRate(null); baggage.setSampled(null); - baggage.setSampleRand(null); // TODO do we need this? + baggage.setSampleRand(null); final @Nullable Object replayId = event.getContexts().get(REPLAY_ID); if (replayId != null && !replayId.toString().equals(SentryId.EMPTY_ID.toString())) { baggage.setReplayId(replayId.toString()); @@ -191,25 +191,11 @@ public Baggage( this.thirdPartyHeader = thirdPartyHeader; this.mutable = isMutable; this.shouldFreeze = shouldFreeze; - // new RuntimeException( - // "creating new baggage " - // + this - // + " mutable " - // + mutable - // + " shouldFreeze " - // + shouldFreeze) - // .printStackTrace(); } @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { - // if (mutable) { - // new RuntimeException("freezing baggage " + this).printStackTrace(); - // } else { - // new RuntimeException("freezing baggage that is already frozen " + - // this).printStackTrace(); - // } this.mutable = false; } @@ -282,8 +268,6 @@ public String getThirdPartyHeader() { } } - // sb.append(",sbg=" + this); - return sb.toString(); } diff --git a/sentry/src/main/java/io/sentry/CombinedScopeView.java b/sentry/src/main/java/io/sentry/CombinedScopeView.java index 3523afa4d3d..129066450f3 100644 --- a/sentry/src/main/java/io/sentry/CombinedScopeView.java +++ b/sentry/src/main/java/io/sentry/CombinedScopeView.java @@ -426,6 +426,7 @@ public void setPropagationContext(@NotNull PropagationContext propagationContext getDefaultWriteScope().setPropagationContext(propagationContext); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return getDefaultWriteScope().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 50dd34adb1b..8dbcff4ef6e 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -344,6 +344,7 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return Sentry.getCurrentScopes().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 5b13970f13d..4427f33c4bc 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -343,6 +343,7 @@ public void reportFullyDisplayed() { return scopes.captureReplay(replay, hint); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return scopes.getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index 8f3741e15d7..7ef1a52cdec 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -297,6 +297,7 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return PropagationContext.NOOP; diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 0ccab9bbdc5..779a67024a9 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -302,6 +302,7 @@ public boolean isNoOp() { return SentryId.EMPTY_ID; } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return PropagationContext.NOOP; diff --git a/sentry/src/main/java/io/sentry/OutboxSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java index f5c3813414d..b878606c00b 100644 --- a/sentry/src/main/java/io/sentry/OutboxSender.java +++ b/sentry/src/main/java/io/sentry/OutboxSender.java @@ -252,7 +252,8 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN } } - return new TracesSamplingDecision(true, sampleRate); + return SampleRateUtils.backfilledSampleRand( + new TracesSamplingDecision(true, sampleRate)); } } catch (Exception e) { logger.log( diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index c126f19a5d3..9b1de29371c 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -60,7 +60,7 @@ public static PropagationContext fromHeaders( private @Nullable Boolean sampled; - private @NotNull Baggage baggage; + private final @NotNull Baggage baggage; public PropagationContext() { this(new SentryId(), new SpanId(), null, null, null); diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 08febded426..773fdc0e04d 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1068,6 +1068,7 @@ public void reportFullyDisplayed() { return sentryId; } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return getCombinedScopeView().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 45629babad5..794449305e1 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -348,6 +348,7 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } + @ApiStatus.Internal @Override public @NotNull PropagationContext getPropagationContext() { return Sentry.getCurrentScopes().getPropagationContext(); diff --git a/sentry/src/main/java/io/sentry/SentryTracer.java b/sentry/src/main/java/io/sentry/SentryTracer.java index 5c73e128257..36329db7a71 100644 --- a/sentry/src/main/java/io/sentry/SentryTracer.java +++ b/sentry/src/main/java/io/sentry/SentryTracer.java @@ -670,18 +670,7 @@ private void updateBaggageValues(final @NotNull Baggage baggage) { final @Nullable Baggage baggage = getSpanContext().getBaggage(); if (baggage != null) { updateBaggageValues(baggage); - BaggageHeader baggageHeader = - BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); - if (baggageHeader != null) { - System.out.println("outgoing baggage in SentryTracer:"); - System.out.println(baggageHeader.getValue()); - } else { - System.out.println("baggage header null in SentryTracer"); - } - - return baggageHeader; - } else { - System.out.println("baggage null in SentryTracer"); + return BaggageHeader.fromBaggageAndOutgoingHeader(baggage, thirdPartyBaggageHeaders); } } return null; diff --git a/sentry/src/main/java/io/sentry/Span.java b/sentry/src/main/java/io/sentry/Span.java index 5b5cb6eadc8..3f08cca2a58 100644 --- a/sentry/src/main/java/io/sentry/Span.java +++ b/sentry/src/main/java/io/sentry/Span.java @@ -154,11 +154,7 @@ public Span( @Override public @NotNull SentryTraceHeader toSentryTrace() { - SentryTraceHeader sentryTraceHeader = - new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); - System.out.println("outgoing sentry-trace:"); - System.out.println(sentryTraceHeader.getValue()); - return sentryTraceHeader; + return new SentryTraceHeader(context.getTraceId(), context.getSpanId(), context.getSampled()); } @Override diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index 300f6cb6672..e89065d3af9 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -18,7 +18,6 @@ public TracesSampler(final @NotNull SentryOptions options) { @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); - // new RuntimeException("sample rand used in TracesSampler " + sampleRand).printStackTrace(); final TracesSamplingDecision samplingContextSamplingDecision = samplingContext.getTransactionContext().getSamplingDecision(); if (samplingContextSamplingDecision != null) { diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index 63f6eba9a6d..64b60d4c3b8 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -107,7 +107,7 @@ public TransactionContext( this.name = DEFAULT_TRANSACTION_NAME; this.parentSamplingDecision = parentSamplingDecision; this.transactionNameSource = DEFAULT_NAME_SOURCE; - this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); // todo test + this.baggage = TracingUtils.ensureBaggage(baggage, parentSamplingDecision); } public @NotNull String getName() { diff --git a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java index 3d7f247dfe2..225ce58a3b5 100644 --- a/sentry/src/main/java/io/sentry/util/SampleRateUtils.java +++ b/sentry/src/main/java/io/sentry/util/SampleRateUtils.java @@ -25,8 +25,6 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return isValidRate(profilesSampleRate, true); } - // TODO test - @SuppressWarnings("ObjectToString") public static @NotNull Double backfilledSampleRand( final @Nullable Double sampleRand, final @Nullable Double sampleRate, @@ -35,10 +33,6 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return sampleRand; } - new RuntimeException( - "backfilling sample rand " + sampleRand + " rate " + sampleRate + " sampled " + sampled) - .printStackTrace(); - double newSampleRand = SentryRandom.current().nextDouble(); if (sampleRate != null && sampled != null) { if (sampled) { @@ -51,8 +45,6 @@ public static boolean isValidProfilesSampleRate(@Nullable Double profilesSampleR return newSampleRand; } - // TODO test - @SuppressWarnings("ObjectToString") public static @NotNull TracesSamplingDecision backfilledSampleRand( final @NotNull TracesSamplingDecision samplingDecision) { if (samplingDecision.getSampleRand() != null) { diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index f1e22e04955..7a6d053f138 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -192,34 +192,24 @@ public static boolean isIgnored( final @NotNull Baggage baggage = incomingBaggage == null ? new Baggage(NoOpLogger.getInstance()) : incomingBaggage; - StringBuilder sb = new StringBuilder("sample rand"); - if (baggage.getSampleRand() == null) { final @Nullable Double baggageSampleRate = baggage.getSampleRateDouble(); final @Nullable Double baggageSampleRand = baggage.getSampleRandDouble(); final @Nullable Double sampleRandMaybe = baggageSampleRand == null ? decisionSampleRand : baggageSampleRand; - sb.append(" [baggage " + sampleRandMaybe + "]"); final @Nullable Double sampleRateMaybe = baggageSampleRate == null ? decisionSampleRate : baggageSampleRate; final @NotNull Double sampleRand = SampleRateUtils.backfilledSampleRand(sampleRandMaybe, sampleRateMaybe, decisionSampled); - sb.append(" [setting sample rand on baggage " + baggage + "]"); baggage.setSampleRandDouble(sampleRand); - sb.append(" {" + sampleRand + "}"); } if (baggage.isMutable()) { - sb.append(" [baggage mutable]"); // cannot freeze on scope fork if (baggage.isShouldFreeze()) { - sb.append(" [freezing baggage]"); baggage.freeze(); } - } else { - sb.append(" [baggage already frozen]"); } - // new RuntimeException("PropagationContext ctor" + sb.toString()).printStackTrace(); return baggage; } diff --git a/sentry/src/test/java/io/sentry/TransactionContextTest.kt b/sentry/src/test/java/io/sentry/TransactionContextTest.kt index d6b715bd841..8a66870bc26 100644 --- a/sentry/src/test/java/io/sentry/TransactionContextTest.kt +++ b/sentry/src/test/java/io/sentry/TransactionContextTest.kt @@ -1,10 +1,12 @@ package io.sentry import io.sentry.protocol.SentryId +import io.sentry.protocol.TransactionNameSource import org.mockito.kotlin.mock import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue @@ -91,4 +93,32 @@ class TransactionContextTest { context.isForNextAppStart = true assertTrue(context.isForNextAppStart) } + + @Test + fun `when passing null baggage creates a new one`() { + val context = TransactionContext(SentryId(), SpanId(), null, null, null) + assertNotNull(context.baggage) + assertNotNull(context.baggage?.sampleRand) + } + + @Test + fun `when passing null baggage creates a new one and uses parent sampling decision`() { + val context = TransactionContext(SentryId(), SpanId(), null, TracesSamplingDecision(true, 0.1, 0.2), null) + assertNotNull(context.baggage) + assertEquals("0.2", context.baggage?.sampleRand) + } + + @Test + fun `when using few param ctor creates a new baggage`() { + val context = TransactionContext("name", "op") + assertNotNull(context.baggage) + assertNotNull(context.baggage?.sampleRand) + } + + @Test + fun `when using few param ctor creates a new baggage and uses sampling decision`() { + val context = TransactionContext("name", TransactionNameSource.CUSTOM, "op", TracesSamplingDecision(true, 0.1, 0.2)) + assertNotNull(context.baggage) + assertEquals("0.2", context.baggage?.sampleRand) + } } From a0bd76647572f2825ff784d9bba7402575e2c91d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:39:11 +0100 Subject: [PATCH 19/29] revert change to demo --- .../boot/jakarta/SentryDemoApplication.java | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java index 4c61bf79278..a6eb46f4c74 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/src/main/java/io/sentry/samples/spring/boot/jakarta/SentryDemoApplication.java @@ -1,12 +1,21 @@ package io.sentry.samples.spring.boot.jakarta; +import static io.sentry.quartz.SentryJobListener.SENTRY_SLUG_KEY; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Tracer; +import io.sentry.samples.spring.boot.jakarta.quartz.SampleJob; +import java.util.Collections; +import org.quartz.JobDetail; +import org.quartz.SimpleTrigger; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.quartz.CronTriggerFactoryBean; +import org.springframework.scheduling.quartz.JobDetailFactoryBean; +import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; @@ -33,34 +42,34 @@ RestClient restClient(RestClient.Builder builder) { return builder.build(); } - // @Bean - // public JobDetailFactoryBean jobDetail() { - // JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); - // jobDetailFactory.setJobClass(SampleJob.class); - // jobDetailFactory.setDurability(true); - // jobDetailFactory.setJobDataAsMap( - // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); - // return jobDetailFactory; - // } - // - // @Bean - // public SimpleTriggerFactoryBean trigger(JobDetail job) { - // SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); - // trigger.setJobDetail(job); - // trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes - // trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); - // trigger.setJobDataAsMap( - // Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); - // return trigger; - // } - // - // @Bean - // public CronTriggerFactoryBean cronTrigger(JobDetail job) { - // CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); - // trigger.setJobDetail(job); - // trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes - // return trigger; - // } + @Bean + public JobDetailFactoryBean jobDetail() { + JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean(); + jobDetailFactory.setJobClass(SampleJob.class); + jobDetailFactory.setDurability(true); + jobDetailFactory.setJobDataAsMap( + Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail")); + return jobDetailFactory; + } + + @Bean + public SimpleTriggerFactoryBean trigger(JobDetail job) { + SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean(); + trigger.setJobDetail(job); + trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes + trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY); + trigger.setJobDataAsMap( + Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger")); + return trigger; + } + + @Bean + public CronTriggerFactoryBean cronTrigger(JobDetail job) { + CronTriggerFactoryBean trigger = new CronTriggerFactoryBean(); + trigger.setJobDetail(job); + trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes + return trigger; + } @Bean public Tracer tracer() { From f9bec8d8531a5b6a9598f99927bc6f4145648779 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:40:46 +0100 Subject: [PATCH 20/29] make baggage final again --- sentry/src/main/java/io/sentry/Baggage.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 53b18e48a9e..5cf7d264202 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -2,7 +2,6 @@ import static io.sentry.protocol.Contexts.REPLAY_ID; -import com.jakewharton.nopen.annotation.Open; import io.sentry.protocol.SentryId; import io.sentry.protocol.TransactionNameSource; import io.sentry.util.SampleRateUtils; @@ -26,8 +25,7 @@ import org.jetbrains.annotations.Nullable; @ApiStatus.Experimental -@Open -public class Baggage { +public final class Baggage { public static final @NotNull Baggage NOOP = new Baggage(new HashMap<>(), null, false, true, NoOpLogger.getInstance()); From 8add76d743167d091dac0ed681b42dc0a019da39 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:45:48 +0100 Subject: [PATCH 21/29] remove ObjectToString suppressing --- sentry/src/main/java/io/sentry/Baggage.java | 3 --- sentry/src/main/java/io/sentry/PropagationContext.java | 1 - sentry/src/main/java/io/sentry/TracesSampler.java | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 5cf7d264202..acd5fdb6e0a 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -177,7 +177,6 @@ public Baggage(final @NotNull Baggage baggage) { } @ApiStatus.Internal - @SuppressWarnings("ObjectToString") public Baggage( final @NotNull Map keyValues, final @Nullable String thirdPartyHeader, @@ -191,7 +190,6 @@ public Baggage( this.shouldFreeze = shouldFreeze; } - @SuppressWarnings("ObjectToString") @ApiStatus.Internal public void freeze() { this.mutable = false; @@ -212,7 +210,6 @@ public String getThirdPartyHeader() { return thirdPartyHeader; } - @SuppressWarnings("ObjectToString") public @NotNull String toHeaderString(@Nullable String thirdPartyBaggageHeaderString) { final StringBuilder sb = new StringBuilder(); String separator = ""; diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index 9b1de29371c..d863955bdb0 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -75,7 +75,6 @@ public PropagationContext(final @NotNull PropagationContext propagationContext) propagationContext.isSampled()); } - @SuppressWarnings("ObjectToString") public PropagationContext( final @NotNull SentryId traceId, final @NotNull SpanId spanId, diff --git a/sentry/src/main/java/io/sentry/TracesSampler.java b/sentry/src/main/java/io/sentry/TracesSampler.java index e89065d3af9..b3da8d63ccf 100644 --- a/sentry/src/main/java/io/sentry/TracesSampler.java +++ b/sentry/src/main/java/io/sentry/TracesSampler.java @@ -14,7 +14,7 @@ public TracesSampler(final @NotNull SentryOptions options) { this.options = Objects.requireNonNull(options, "options are required"); } - @SuppressWarnings({"deprecation", "ObjectToString"}) + @SuppressWarnings("deprecation") @NotNull public TracesSamplingDecision sample(final @NotNull SamplingContext samplingContext) { final @NotNull Double sampleRand = samplingContext.getSampleRand(); From b03791e76b2c243c85105c894c18da1839558abd Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 10:54:19 +0100 Subject: [PATCH 22/29] remove outdated comment --- sentry/src/main/java/io/sentry/util/TracingUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sentry/src/main/java/io/sentry/util/TracingUtils.java b/sentry/src/main/java/io/sentry/util/TracingUtils.java index 7a6d053f138..075bfcccd01 100644 --- a/sentry/src/main/java/io/sentry/util/TracingUtils.java +++ b/sentry/src/main/java/io/sentry/util/TracingUtils.java @@ -205,7 +205,6 @@ public static boolean isIgnored( baggage.setSampleRandDouble(sampleRand); } if (baggage.isMutable()) { - // cannot freeze on scope fork if (baggage.isShouldFreeze()) { baggage.freeze(); } From 18b1093ca21c3f592e45248fde1177daa7087261 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 11:33:31 +0100 Subject: [PATCH 23/29] remove getPropagationContext from Scopes again --- sentry/api/sentry.api | 9 +-------- sentry/src/main/java/io/sentry/HubAdapter.java | 6 ------ sentry/src/main/java/io/sentry/HubScopesWrapper.java | 6 ------ sentry/src/main/java/io/sentry/IScopes.java | 4 ---- sentry/src/main/java/io/sentry/NoOpHub.java | 6 ------ sentry/src/main/java/io/sentry/NoOpScopes.java | 6 ------ sentry/src/main/java/io/sentry/Scopes.java | 6 ------ sentry/src/main/java/io/sentry/ScopesAdapter.java | 6 ------ 8 files changed, 1 insertion(+), 48 deletions(-) diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index ddea3df1386..165c52873d7 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -29,7 +29,7 @@ public final class io/sentry/Attachment { public abstract interface class io/sentry/BackfillingEventProcessor : io/sentry/EventProcessor { } -public class io/sentry/Baggage { +public final class io/sentry/Baggage { public static final field NOOP Lio/sentry/Baggage; public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V @@ -600,7 +600,6 @@ public final class io/sentry/HubAdapter : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -665,7 +664,6 @@ public final class io/sentry/HubScopesWrapper : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -892,7 +890,6 @@ public abstract interface class io/sentry/IScopes { public abstract fun getLastEventId ()Lio/sentry/protocol/SentryId; public abstract fun getOptions ()Lio/sentry/SentryOptions; public abstract fun getParentScopes ()Lio/sentry/IScopes; - public abstract fun getPropagationContext ()Lio/sentry/PropagationContext; public abstract fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public abstract fun getScope ()Lio/sentry/IScope; public abstract fun getSpan ()Lio/sentry/ISpan; @@ -1415,7 +1412,6 @@ public final class io/sentry/NoOpHub : io/sentry/IHub { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -1575,7 +1571,6 @@ public final class io/sentry/NoOpScopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -2178,7 +2173,6 @@ public final class io/sentry/Scopes : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; @@ -2243,7 +2237,6 @@ public final class io/sentry/ScopesAdapter : io/sentry/IScopes { public fun getLastEventId ()Lio/sentry/protocol/SentryId; public fun getOptions ()Lio/sentry/SentryOptions; public fun getParentScopes ()Lio/sentry/IScopes; - public fun getPropagationContext ()Lio/sentry/PropagationContext; public fun getRateLimiter ()Lio/sentry/transport/RateLimiter; public fun getScope ()Lio/sentry/IScope; public fun getSpan ()Lio/sentry/ISpan; diff --git a/sentry/src/main/java/io/sentry/HubAdapter.java b/sentry/src/main/java/io/sentry/HubAdapter.java index 8dbcff4ef6e..fc2f9c15dcd 100644 --- a/sentry/src/main/java/io/sentry/HubAdapter.java +++ b/sentry/src/main/java/io/sentry/HubAdapter.java @@ -344,12 +344,6 @@ public void reportFullyDisplayed() { return Sentry.getCurrentScopes().captureReplay(replay, hint); } - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return Sentry.getCurrentScopes().getPropagationContext(); - } - @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/HubScopesWrapper.java b/sentry/src/main/java/io/sentry/HubScopesWrapper.java index 4427f33c4bc..591852a9adf 100644 --- a/sentry/src/main/java/io/sentry/HubScopesWrapper.java +++ b/sentry/src/main/java/io/sentry/HubScopesWrapper.java @@ -342,10 +342,4 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return scopes.captureReplay(replay, hint); } - - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return scopes.getPropagationContext(); - } } diff --git a/sentry/src/main/java/io/sentry/IScopes.java b/sentry/src/main/java/io/sentry/IScopes.java index 2ceb5c4f52c..e07de9c327c 100644 --- a/sentry/src/main/java/io/sentry/IScopes.java +++ b/sentry/src/main/java/io/sentry/IScopes.java @@ -689,8 +689,4 @@ default boolean isNoOp() { @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint); - - @ApiStatus.Internal - @NotNull - PropagationContext getPropagationContext(); } diff --git a/sentry/src/main/java/io/sentry/NoOpHub.java b/sentry/src/main/java/io/sentry/NoOpHub.java index 7ef1a52cdec..d3e0b010c39 100644 --- a/sentry/src/main/java/io/sentry/NoOpHub.java +++ b/sentry/src/main/java/io/sentry/NoOpHub.java @@ -297,12 +297,6 @@ public void reportFullyDisplayed() {} return SentryId.EMPTY_ID; } - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return PropagationContext.NOOP; - } - @Override public @Nullable RateLimiter getRateLimiter() { return null; diff --git a/sentry/src/main/java/io/sentry/NoOpScopes.java b/sentry/src/main/java/io/sentry/NoOpScopes.java index 779a67024a9..8255569387d 100644 --- a/sentry/src/main/java/io/sentry/NoOpScopes.java +++ b/sentry/src/main/java/io/sentry/NoOpScopes.java @@ -301,10 +301,4 @@ public boolean isNoOp() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return SentryId.EMPTY_ID; } - - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return PropagationContext.NOOP; - } } diff --git a/sentry/src/main/java/io/sentry/Scopes.java b/sentry/src/main/java/io/sentry/Scopes.java index 773fdc0e04d..14025a1d774 100644 --- a/sentry/src/main/java/io/sentry/Scopes.java +++ b/sentry/src/main/java/io/sentry/Scopes.java @@ -1068,12 +1068,6 @@ public void reportFullyDisplayed() { return sentryId; } - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return getCombinedScopeView().getPropagationContext(); - } - @ApiStatus.Internal @Override public @Nullable RateLimiter getRateLimiter() { diff --git a/sentry/src/main/java/io/sentry/ScopesAdapter.java b/sentry/src/main/java/io/sentry/ScopesAdapter.java index 794449305e1..6df6deee3d4 100644 --- a/sentry/src/main/java/io/sentry/ScopesAdapter.java +++ b/sentry/src/main/java/io/sentry/ScopesAdapter.java @@ -347,10 +347,4 @@ public void reportFullyDisplayed() { public @NotNull SentryId captureReplay(@NotNull SentryReplayEvent replay, @Nullable Hint hint) { return Sentry.getCurrentScopes().captureReplay(replay, hint); } - - @ApiStatus.Internal - @Override - public @NotNull PropagationContext getPropagationContext() { - return Sentry.getCurrentScopes().getPropagationContext(); - } } From 0cb6e0d06470908e9136242b99069e78fab66224 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 10 Feb 2025 11:50:02 +0100 Subject: [PATCH 24/29] remove noop baggage and propagation context again --- sentry/src/main/java/io/sentry/Baggage.java | 2 -- sentry/src/main/java/io/sentry/PropagationContext.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index acd5fdb6e0a..155b455c6db 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -27,8 +27,6 @@ @ApiStatus.Experimental public final class Baggage { - public static final @NotNull Baggage NOOP = - new Baggage(new HashMap<>(), null, false, true, NoOpLogger.getInstance()); static final @NotNull String CHARSET = "UTF-8"; static final @NotNull Integer MAX_BAGGAGE_STRING_LENGTH = 8192; static final @NotNull Integer MAX_BAGGAGE_LIST_MEMBER_COUNT = 64; diff --git a/sentry/src/main/java/io/sentry/PropagationContext.java b/sentry/src/main/java/io/sentry/PropagationContext.java index d863955bdb0..791cb1d3d36 100644 --- a/sentry/src/main/java/io/sentry/PropagationContext.java +++ b/sentry/src/main/java/io/sentry/PropagationContext.java @@ -12,9 +12,6 @@ @ApiStatus.Internal public final class PropagationContext { - public static @NotNull PropagationContext NOOP = - new PropagationContext(SentryId.EMPTY_ID, SpanId.EMPTY_ID, null, Baggage.NOOP, false); - public static PropagationContext fromHeaders( final @NotNull ILogger logger, final @Nullable String sentryTraceHeader, From c239a32f38d08b6149862552789efb807069a16f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Tue, 11 Feb 2025 06:41:34 +0100 Subject: [PATCH 25/29] fix outbox sender; test; cleanup api file --- sentry/api/sentry.api | 2 - .../src/main/java/io/sentry/OutboxSender.java | 4 +- .../test/java/io/sentry/OutboxSenderTest.kt | 59 +++++++++++++++++++ .../envelope-transaction-with-sample-rand.txt | 3 + 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 sentry/src/test/resources/envelope-transaction-with-sample-rand.txt diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 165c52873d7..eb63ba38c4a 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -30,7 +30,6 @@ public abstract interface class io/sentry/BackfillingEventProcessor : io/sentry/ } public final class io/sentry/Baggage { - public static final field NOOP Lio/sentry/Baggage; public fun (Lio/sentry/Baggage;)V public fun (Lio/sentry/ILogger;)V public fun (Ljava/util/Map;Ljava/lang/String;ZZLio/sentry/ILogger;)V @@ -1946,7 +1945,6 @@ public final class io/sentry/ProfilingTransactionData$JsonKeys { } public final class io/sentry/PropagationContext { - public static field NOOP Lio/sentry/PropagationContext; public fun ()V public fun (Lio/sentry/PropagationContext;)V public fun (Lio/sentry/protocol/SentryId;Lio/sentry/SpanId;Lio/sentry/SpanId;Lio/sentry/Baggage;Ljava/lang/Boolean;)V diff --git a/sentry/src/main/java/io/sentry/OutboxSender.java b/sentry/src/main/java/io/sentry/OutboxSender.java index b878606c00b..cbe4f6ee007 100644 --- a/sentry/src/main/java/io/sentry/OutboxSender.java +++ b/sentry/src/main/java/io/sentry/OutboxSender.java @@ -244,10 +244,10 @@ private void processEnvelope(final @NotNull SentryEnvelope envelope, final @NotN "Invalid sample rate parsed from TraceContext: %s", sampleRateString); } else { - final @Nullable String sampleRandString = traceContext.getSampleRate(); + final @Nullable String sampleRandString = traceContext.getSampleRand(); if (sampleRandString != null) { final Double sampleRand = Double.parseDouble(sampleRandString); - if (!SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { + if (SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { return new TracesSamplingDecision(true, sampleRate, sampleRand); } } diff --git a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt index 8a1850e7ddc..9136494ddf4 100644 --- a/sentry/src/test/java/io/sentry/OutboxSenderTest.kt +++ b/sentry/src/test/java/io/sentry/OutboxSenderTest.kt @@ -23,6 +23,7 @@ import java.util.Date import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse +import kotlin.test.assertNotNull import kotlin.test.assertTrue class OutboxSenderTest { @@ -141,6 +142,63 @@ class OutboxSenderTest { whenever(fixture.scopes.options).thenReturn(fixture.options) whenever(fixture.options.transactionProfiler).thenReturn(NoOpTransactionProfiler.getInstance()) + val transactionContext = TransactionContext("fixture-name", "http") + transactionContext.description = "fixture-request" + transactionContext.status = SpanStatus.OK + transactionContext.setTag("fixture-tag", "fixture-value") + transactionContext.samplingDecision = TracesSamplingDecision(true, 0.00000021, 0.021) + + val sentryTracer = SentryTracer(transactionContext, fixture.scopes) + val span = sentryTracer.startChild("child") + span.finish(SpanStatus.OK) + sentryTracer.finish() + + val sentryTracerSpy = spy(sentryTracer) + whenever(sentryTracerSpy.eventId).thenReturn(SentryId("3367f5196c494acaae85bbbd535379ac")) + + val expected = SentryTransaction(sentryTracerSpy) + whenever(fixture.serializer.deserialize(any(), eq(SentryTransaction::class.java))).thenReturn(expected) + + val sut = fixture.getSut() + val path = getTempEnvelope(fileName = "envelope-transaction-with-sample-rand.txt") + assertTrue(File(path).exists()) + + val hints = HintUtils.createWithTypeCheckHint(mock()) + sut.processEnvelopeFile(path, hints) + + verify(fixture.scopes).captureTransaction( + check { + assertEquals(expected, it) + assertTrue(it.isSampled) + assertEquals(0.00000021, it.samplingDecision?.sampleRate) + assertEquals(0.021, it.samplingDecision?.sampleRand) + assertTrue(it.samplingDecision!!.sampled) + }, + check { + assertEquals("b156a475de54423d9c1571df97ec7eb6", it.traceId.toString()) + assertEquals("key", it.publicKey) + assertEquals("0.00000021", it.sampleRate) + assertEquals("1.0-beta.1", it.release) + assertEquals("prod", it.environment) + assertEquals("usr1", it.userId) + assertEquals("tx1", it.transaction) + }, + any() + ) + assertFalse(File(path).exists()) + + // Additionally make sure we have no errors logged + verify(fixture.logger, never()).log(eq(SentryLevel.ERROR), any(), any()) + verify(fixture.logger, never()).log(eq(SentryLevel.ERROR), any(), any()) + } + + @Test + fun `backfills sampleRand`() { + fixture.envelopeReader = EnvelopeReader(JsonSerializer(fixture.options)) + whenever(fixture.options.maxSpans).thenReturn(1000) + whenever(fixture.scopes.options).thenReturn(fixture.options) + whenever(fixture.options.transactionProfiler).thenReturn(NoOpTransactionProfiler.getInstance()) + val transactionContext = TransactionContext("fixture-name", "http") transactionContext.description = "fixture-request" transactionContext.status = SpanStatus.OK @@ -170,6 +228,7 @@ class OutboxSenderTest { assertEquals(expected, it) assertTrue(it.isSampled) assertEquals(0.00000021, it.samplingDecision?.sampleRate) + assertNotNull(it.samplingDecision?.sampleRand) assertTrue(it.samplingDecision!!.sampled) }, check { diff --git a/sentry/src/test/resources/envelope-transaction-with-sample-rand.txt b/sentry/src/test/resources/envelope-transaction-with-sample-rand.txt new file mode 100644 index 00000000000..1a6b3120b81 --- /dev/null +++ b/sentry/src/test/resources/envelope-transaction-with-sample-rand.txt @@ -0,0 +1,3 @@ +{"event_id":"3367f5196c494acaae85bbbd535379ac","trace":{"trace_id":"b156a475de54423d9c1571df97ec7eb6","public_key":"key","release":"1.0-beta.1","environment":"prod","user_id":"usr1","transaction":"tx1","sample_rate":"0.00000021","sample_rand":"0.021"}} +{"type":"transaction","length":640,"content_type":"application/json"} +{"transaction":"a-transaction","type":"transaction","start_timestamp":"2020-10-23T10:24:01.791Z","timestamp":"2020-10-23T10:24:02.791Z","event_id":"3367f5196c494acaae85bbbd535379ac","contexts":{"trace":{"trace_id":"b156a475de54423d9c1571df97ec7eb6","span_id":"0a53026963414893","op":"http","status":"ok"},"custom":{"some-key":"some-value"}},"spans":[{"start_timestamp":"2021-03-05T08:51:12.838Z","timestamp":"2021-03-05T08:51:12.949Z","trace_id":"2b099185293344a5bfdd7ad89ebf9416","span_id":"5b95c29a5ded4281","parent_span_id":"a3b2d1d58b344b07","op":"PersonService.create","description":"desc","status":"aborted","tags":{"name":"value"}}]} From d3adc66e22e343ead99cd6572a7a3ebe03d36af8 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Feb 2025 07:00:32 +0100 Subject: [PATCH 26/29] review changes --- sentry/src/main/java/io/sentry/Baggage.java | 11 +++++++++ .../java/io/sentry/TransactionContext.java | 24 +++++-------------- sentry/src/test/java/io/sentry/BaggageTest.kt | 12 ---------- .../java/io/sentry/util/SampleRateUtilTest.kt | 2 +- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 155b455c6db..641c53385b8 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -104,6 +104,17 @@ public static Baggage fromHeader( final String valueDecoded = decode(value); keyValues.put(keyDecoded, valueDecoded); + + // Without ignoring SAMPLE_RAND here, we'd be freezing baggage that we're transporting + // via OTel span attributes. + // This is done when a transaction is created via Sentry API. + // In that case Baggage is created before the OTel span is created and we put it on + // the span attributes. + // It does however only contain the sample random value as its only value. + // The OTel code then uses it to create a propagation context from it and ends up + // freezing it, + // preventing outgoing requests (to other systems or Sentry) from adding info to + // baggage and only then freeze it. if (!DSCKeys.SAMPLE_RAND.equalsIgnoreCase(key)) { shouldFreeze = true; } diff --git a/sentry/src/main/java/io/sentry/TransactionContext.java b/sentry/src/main/java/io/sentry/TransactionContext.java index 64b60d4c3b8..866c1c00daa 100644 --- a/sentry/src/main/java/io/sentry/TransactionContext.java +++ b/sentry/src/main/java/io/sentry/TransactionContext.java @@ -21,26 +21,14 @@ public final class TransactionContext extends SpanContext { @ApiStatus.Internal public static TransactionContext fromPropagationContext( final @NotNull PropagationContext propagationContext) { - @Nullable Boolean parentSampled = propagationContext.isSampled(); - TracesSamplingDecision samplingDecision = + final @Nullable Boolean parentSampled = propagationContext.isSampled(); + final @NotNull Baggage baggage = propagationContext.getBaggage(); + final @Nullable Double sampleRate = baggage.getSampleRateDouble(); + final @Nullable TracesSamplingDecision samplingDecision = parentSampled == null ? null - : new TracesSamplingDecision(parentSampled, null, propagationContext.getSampleRand()); - - @NotNull Baggage baggage = propagationContext.getBaggage(); - - Double sampleRate = baggage.getSampleRateDouble(); - if (parentSampled != null) { - if (sampleRate != null) { - samplingDecision = - new TracesSamplingDecision( - parentSampled.booleanValue(), sampleRate, propagationContext.getSampleRand()); - } else { - samplingDecision = - new TracesSamplingDecision( - parentSampled.booleanValue(), null, propagationContext.getSampleRand()); - } - } + : new TracesSamplingDecision( + parentSampled, sampleRate, propagationContext.getSampleRand()); return new TransactionContext( propagationContext.getTraceId(), diff --git a/sentry/src/test/java/io/sentry/BaggageTest.kt b/sentry/src/test/java/io/sentry/BaggageTest.kt index 242d10dff2e..128c96fb1fb 100644 --- a/sentry/src/test/java/io/sentry/BaggageTest.kt +++ b/sentry/src/test/java/io/sentry/BaggageTest.kt @@ -354,18 +354,6 @@ class BaggageTest { assertEquals("sentry-trace_id=a,sentry-transaction=sentryTransaction", baggage.toHeaderString(null)) } - @Test - fun `if header contains sentry values baggage is marked as shouldFreeze`() { - val baggage = Baggage.fromHeader("sentry-trace_id=a,sentry-transaction=sentryTransaction", logger) - assertTrue(baggage.isShouldFreeze) - } - - @Test - fun `if header does not contain sentry values baggage is not marked as shouldFreeze`() { - val baggage = Baggage.fromHeader("a=b", logger) - assertFalse(baggage.isShouldFreeze) - } - @Test fun `value may contain = sign`() { val baggage = Baggage(logger) diff --git a/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt b/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt index 5b1117aa6cf..8576e9b10f2 100644 --- a/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt +++ b/sentry/src/test/java/io/sentry/util/SampleRateUtilTest.kt @@ -177,7 +177,7 @@ class SampleRateUtilTest { } @Test - fun `backfills sampleRand if missing with sampled true below samlpe rate`() { + fun `backfills sampleRand if missing with sampled true below sample rate`() { val sampleRand = SampleRateUtils.backfilledSampleRand(null, 0.0001, true) assertNotNull(sampleRand) assertTrue(sampleRand >= 0) From 208d41fa20db890e995d328c806f2591cf0cb493 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 12 Feb 2025 16:54:16 +0000 Subject: [PATCH 27/29] Format code --- sentry/src/test/java/io/sentry/BaggageTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/test/java/io/sentry/BaggageTest.kt b/sentry/src/test/java/io/sentry/BaggageTest.kt index 1a901df9c42..27cfcfd49e5 100644 --- a/sentry/src/test/java/io/sentry/BaggageTest.kt +++ b/sentry/src/test/java/io/sentry/BaggageTest.kt @@ -609,7 +609,7 @@ class BaggageTest { assertEquals("0.121", baggage.sampleRate) assertEquals("0.025", baggage.sampleRand) } - + fun `sample rate can be retrieved as double`() { val baggage = Baggage.fromHeader("a=b,c=d") baggage.sampleRate = "0.1" From 5d8da55fd961a0f0f10a997623ae0eea71837c62 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Feb 2025 18:04:53 +0100 Subject: [PATCH 28/29] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f681818c9b5..dd6c239f1b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ - (Internal) Add API to filter native debug images based on stacktrace addresses ([#4089](https://github.com/getsentry/sentry-java/pull/4089)) - Propagate sampling random value ([#4153](https://github.com/getsentry/sentry-java/pull/4153)) - The random value used for sampling traces is now sent to Sentry and attached to the `baggage` header on outgoing requests +- Update `sampleRate` that is sent to Sentry and attached to the `baggage` header on outgoing requests ([#4158](https://github.com/getsentry/sentry-java/pull/4158)) + - If the SDK uses its `sampleRate` or `tracesSampler` callback, it now updates the `sampleRate` in Dynamic Sampling Context. ### Fixes From 715eef8b25121660d272873fc802313898baf005 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 12 Feb 2025 18:11:44 +0100 Subject: [PATCH 29/29] fix build --- sentry/src/main/java/io/sentry/Baggage.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 101f5259afd..4b83b91d406 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -557,22 +557,6 @@ private static boolean isHighQualityTransactionName( return null; } - @ApiStatus.Internal - public @Nullable Double getSampleRandDouble() { - final String sampleRandString = getSampleRand(); - if (sampleRandString != null) { - try { - double sampleRand = Double.parseDouble(sampleRandString); - if (SampleRateUtils.isValidTracesSampleRate(sampleRand, false)) { - return sampleRand; - } - } catch (NumberFormatException e) { - return null; - } - } - return null; - } - @ApiStatus.Internal @Nullable public TraceContext toTraceContext() {