diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 10c1b80af3..a7f02708e3 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -47,6 +47,7 @@ public final class io/sentry/Baggage { public fun getRelease ()Ljava/lang/String; public fun getSampleRate ()Ljava/lang/String; public fun getSampleRateDouble ()Ljava/lang/Double; + public fun getSampled ()Ljava/lang/String; public fun getThirdPartyHeader ()Ljava/lang/String; public fun getTraceId ()Ljava/lang/String; public fun getTransaction ()Ljava/lang/String; @@ -59,6 +60,7 @@ public final class io/sentry/Baggage { public fun setPublicKey (Ljava/lang/String;)V public fun setRelease (Ljava/lang/String;)V public fun setSampleRate (Ljava/lang/String;)V + public fun setSampled (Ljava/lang/String;)V public fun setTraceId (Ljava/lang/String;)V public fun setTransaction (Ljava/lang/String;)V public fun setUserId (Ljava/lang/String;)V @@ -74,6 +76,7 @@ public final class io/sentry/Baggage$DSCKeys { public static final field ENVIRONMENT Ljava/lang/String; public static final field PUBLIC_KEY Ljava/lang/String; public static final field RELEASE Ljava/lang/String; + public static final field SAMPLED 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; @@ -2293,6 +2296,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 getSampleRate ()Ljava/lang/String; + public fun getSampled ()Ljava/lang/String; public fun getTraceId ()Lio/sentry/protocol/SentryId; public fun getTransaction ()Ljava/lang/String; public fun getUnknown ()Ljava/util/Map; @@ -2312,6 +2316,7 @@ public final class io/sentry/TraceContext$JsonKeys { public static final field ENVIRONMENT Ljava/lang/String; public static final field PUBLIC_KEY Ljava/lang/String; public static final field RELEASE Ljava/lang/String; + public static final field SAMPLED 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; @@ -4305,6 +4310,7 @@ public final class io/sentry/util/StringUtils { public static fun join (Ljava/lang/CharSequence;Ljava/lang/Iterable;)Ljava/lang/String; public static fun normalizeUUID (Ljava/lang/String;)Ljava/lang/String; public static fun removeSurrounding (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static fun toString (Ljava/lang/Object;)Ljava/lang/String; } public final class io/sentry/util/TracingUtils { diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index 8329c7c051..945183fa83 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -140,6 +140,7 @@ public static Baggage fromEvent( baggage.setTransaction(event.getTransaction()); // we don't persist sample rate baggage.setSampleRate(null); + baggage.setSampled(null); baggage.freeze(); return baggage; } @@ -329,11 +330,21 @@ public void setTransaction(final @Nullable String transaction) { return get(DSCKeys.SAMPLE_RATE); } + @ApiStatus.Internal + public @Nullable String getSampled() { + return get(DSCKeys.SAMPLED); + } + @ApiStatus.Internal public void setSampleRate(final @Nullable String sampleRate) { set(DSCKeys.SAMPLE_RATE, sampleRate); } + @ApiStatus.Internal + public void setSampled(final @Nullable String sampled) { + set(DSCKeys.SAMPLED, sampled); + } + @ApiStatus.Internal public void set(final @NotNull String key, final @Nullable String value) { if (mutable) { @@ -374,6 +385,7 @@ public void setValuesFromTransaction( ? transaction.getName() : null); setSampleRate(sampleRateToString(sampleRate(samplingDecision))); + setSampled(StringUtils.toString(sampled(samplingDecision))); } @ApiStatus.Internal @@ -387,6 +399,7 @@ public void setValuesFromScope(final @NotNull Scope scope, final @NotNull Sentry setUserSegment(user != null ? getSegment(user) : null); setTransaction(null); setSampleRate(null); + setSampled(null); } private static @Nullable String getSegment(final @NotNull User user) { @@ -420,6 +433,14 @@ public void setValuesFromScope(final @NotNull Scope scope, final @NotNull Sentry return df.format(sampleRateAsDouble); } + private static @Nullable Boolean sampled(@Nullable TracesSamplingDecision samplingDecision) { + if (samplingDecision == null) { + return null; + } + + return samplingDecision.getSampled(); + } + private static boolean isHighQualityTransactionName( @Nullable TransactionNameSource transactionNameSource) { return transactionNameSource != null @@ -458,7 +479,8 @@ public TraceContext toTraceContext() { getUserId(), getUserSegment(), getTransaction(), - getSampleRate()); + getSampleRate(), + getSampled()); traceContext.setUnknown(getUnknown()); return traceContext; } else { @@ -476,6 +498,7 @@ public static final class DSCKeys { public static final String USER_SEGMENT = "sentry-user_segment"; public static final String TRANSACTION = "sentry-transaction"; public static final String SAMPLE_RATE = "sentry-sample_rate"; + public static final String SAMPLED = "sentry-sampled"; public static final List ALL = Arrays.asList( @@ -486,6 +509,7 @@ public static final class DSCKeys { ENVIRONMENT, USER_SEGMENT, TRANSACTION, - SAMPLE_RATE); + SAMPLE_RATE, + SAMPLED); } } diff --git a/sentry/src/main/java/io/sentry/TraceContext.java b/sentry/src/main/java/io/sentry/TraceContext.java index fa3bcfb0c2..ef2944a9e9 100644 --- a/sentry/src/main/java/io/sentry/TraceContext.java +++ b/sentry/src/main/java/io/sentry/TraceContext.java @@ -20,12 +20,13 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { private final @Nullable String userSegment; private final @Nullable String transaction; private final @Nullable String sampleRate; + private final @Nullable String sampled; @SuppressWarnings("unused") private @Nullable Map unknown; TraceContext(@NotNull SentryId traceId, @NotNull String publicKey) { - this(traceId, publicKey, null, null, null, null, null, null); + this(traceId, publicKey, null, null, null, null, null, null, null); } TraceContext( @@ -36,7 +37,8 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { @Nullable String userId, @Nullable String userSegment, @Nullable String transaction, - @Nullable String sampleRate) { + @Nullable String sampleRate, + @Nullable String sampled) { this.traceId = traceId; this.publicKey = publicKey; this.release = release; @@ -45,6 +47,7 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { this.userSegment = userSegment; this.transaction = transaction; this.sampleRate = sampleRate; + this.sampled = sampled; } @SuppressWarnings("UnusedMethod") @@ -89,6 +92,10 @@ public final class TraceContext implements JsonUnknown, JsonSerializable { return sampleRate; } + public @Nullable String getSampled() { + return sampled; + } + /** * @deprecated only here to support parsing legacy JSON with non flattened user */ @@ -190,6 +197,7 @@ public static final class JsonKeys { public static final String USER_SEGMENT = "user_segment"; public static final String TRANSACTION = "transaction"; public static final String SAMPLE_RATE = "sample_rate"; + public static final String SAMPLED = "sampled"; } @Override @@ -216,6 +224,9 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger if (sampleRate != null) { writer.name(TraceContext.JsonKeys.SAMPLE_RATE).value(sampleRate); } + if (sampled != null) { + writer.name(TraceContext.JsonKeys.SAMPLED).value(sampled); + } if (unknown != null) { for (String key : unknown.keySet()) { Object value = unknown.get(key); @@ -241,6 +252,7 @@ public static final class Deserializer implements JsonDeserializer String userSegment = null; String transaction = null; String sampleRate = null; + String sampled = null; Map unknown = null; while (reader.peek() == JsonToken.NAME) { @@ -273,6 +285,9 @@ public static final class Deserializer implements JsonDeserializer case TraceContext.JsonKeys.SAMPLE_RATE: sampleRate = reader.nextStringOrNull(); break; + case TraceContext.JsonKeys.SAMPLED: + sampled = reader.nextStringOrNull(); + break; default: if (unknown == null) { unknown = new ConcurrentHashMap<>(); @@ -304,7 +319,8 @@ public static final class Deserializer implements JsonDeserializer userId, userSegment, transaction, - sampleRate); + sampleRate, + sampled); traceContext.setUnknown(unknown); reader.endObject(); return traceContext; diff --git a/sentry/src/main/java/io/sentry/util/StringUtils.java b/sentry/src/main/java/io/sentry/util/StringUtils.java index 0eef8e9cb3..2158576619 100644 --- a/sentry/src/main/java/io/sentry/util/StringUtils.java +++ b/sentry/src/main/java/io/sentry/util/StringUtils.java @@ -181,4 +181,12 @@ public static String join( return stringBuilder.toString(); } + + public static @Nullable String toString(final @Nullable Object object) { + if (object == null) { + return null; + } + + return object.toString(); + } } diff --git a/sentry/src/test/java/io/sentry/BaggageTest.kt b/sentry/src/test/java/io/sentry/BaggageTest.kt index da8bd2fcc8..eb1cfa0383 100644 --- a/sentry/src/test/java/io/sentry/BaggageTest.kt +++ b/sentry/src/test/java/io/sentry/BaggageTest.kt @@ -319,8 +319,9 @@ class BaggageTest { baggage.setUserId(userId) baggage.setUserSegment("segmentA") baggage.setSampleRate((1.0 / 3.0).toString()) + baggage.setSampled("true") - assertEquals("sentry-environment=production,sentry-public_key=$publicKey,sentry-release=1.0-rc.1,sentry-sample_rate=0.3333333333333333,sentry-trace_id=$traceId,sentry-transaction=TX,sentry-user_id=$userId,sentry-user_segment=segmentA", baggage.toHeaderString(null)) + assertEquals("sentry-environment=production,sentry-public_key=$publicKey,sentry-release=1.0-rc.1,sentry-sample_rate=0.3333333333333333,sentry-sampled=true,sentry-trace_id=$traceId,sentry-transaction=TX,sentry-user_id=$userId,sentry-user_segment=segmentA", baggage.toHeaderString(null)) } @Test diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index 1c9243d2ff..3d07b3e37c 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -437,16 +437,16 @@ class JsonSerializerTest { @Test fun `serializes trace context`() { - val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", "userId", "segment", "transaction", "0.5")) - val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","user_id":"userId","user_segment":"segment","transaction":"transaction","sample_rate":"0.5"}}""" + val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", "userId", "segment", "transaction", "0.5", "true")) + val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","user_id":"userId","user_segment":"segment","transaction":"transaction","sample_rate":"0.5","sampled":"true"}}""" val json = serializeToString(traceContext) assertEquals(expected, json) } @Test fun `serializes trace context with user having null id and segment`() { - val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", null, null, "transaction", "0.6")) - val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","transaction":"transaction","sample_rate":"0.6"}}""" + val traceContext = SentryEnvelopeHeader(null, null, TraceContext(SentryId("3367f5196c494acaae85bbbd535379ac"), "key", "release", "environment", null, null, "transaction", "0.6", "false")) + val expected = """{"trace":{"trace_id":"3367f5196c494acaae85bbbd535379ac","public_key":"key","release":"release","environment":"environment","transaction":"transaction","sample_rate":"0.6","sampled":"false"}}""" val json = serializeToString(traceContext) assertEquals(expected, json) } diff --git a/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt b/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt index b928f791c1..e79e5ebf8c 100644 --- a/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt +++ b/sentry/src/test/java/io/sentry/TraceContextSerializationTest.kt @@ -23,7 +23,8 @@ class TraceContextSerializationTest { "c052c566-6619-45f5-a61f-172802afa39a", "f7d8662b-5551-4ef8-b6a8-090f0561a530", "0252ec25-cd0a-4230-bd2f-936a4585637e", - "0.00000021" + "0.00000021", + "true" ) } private val fixture = Fixture() diff --git a/sentry/src/test/resources/json/sentry_envelope_header.json b/sentry/src/test/resources/json/sentry_envelope_header.json index ab5169ae06..14c144f820 100644 --- a/sentry/src/test/resources/json/sentry_envelope_header.json +++ b/sentry/src/test/resources/json/sentry_envelope_header.json @@ -26,7 +26,8 @@ "user_id": "c052c566-6619-45f5-a61f-172802afa39a", "user_segment": "f7d8662b-5551-4ef8-b6a8-090f0561a530", "transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e", - "sample_rate": "0.00000021" + "sample_rate": "0.00000021", + "sampled": "true" }, "sent_at": "2020-02-07T14:16:00.000Z" } diff --git a/sentry/src/test/resources/json/trace_state.json b/sentry/src/test/resources/json/trace_state.json index ff4a4f86d9..17a95fdc33 100644 --- a/sentry/src/test/resources/json/trace_state.json +++ b/sentry/src/test/resources/json/trace_state.json @@ -6,5 +6,6 @@ "user_id": "c052c566-6619-45f5-a61f-172802afa39a", "user_segment": "f7d8662b-5551-4ef8-b6a8-090f0561a530", "transaction": "0252ec25-cd0a-4230-bd2f-936a4585637e", - "sample_rate": "0.00000021" + "sample_rate": "0.00000021", + "sampled": "true" }