Skip to content

Commit 9194849

Browse files
committed
move feature flag buffer to span context; add e2e test
1 parent 196b3b4 commit 9194849

File tree

9 files changed

+81
-15
lines changed

9 files changed

+81
-15
lines changed

sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ public final class OtelSpanWrapper implements IOtelSpanWrapper {
7777

7878
private @NotNull Deque<ISentryLifecycleToken> tokensToCleanup = new ArrayDeque<>(1);
7979

80-
private final @NotNull IFeatureFlagBuffer featureFlags = SpanFeatureFlagBuffer.create();
81-
8280
public OtelSpanWrapper(
8381
final @NotNull ReadWriteSpan span,
8482
final @NotNull IScopes scopes,
@@ -511,7 +509,7 @@ public Map<String, MeasurementValue> getMeasurements() {
511509

512510
@Override
513511
public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {
514-
featureFlags.add(flag, result);
512+
context.addFeatureFlag(flag, result);
515513
}
516514

517515
@Override

sentry-samples/sentry-samples-spring-boot-jakarta/src/main/java/io/sentry/samples/spring/boot/jakarta/PersonController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public PersonController(PersonService personService) {
2323

2424
@GetMapping("{id}")
2525
Person person(@PathVariable Long id) {
26+
Sentry.addFeatureFlag("transaction-feature-flag", true);
2627
ISpan currentSpan = Sentry.getSpan();
2728
ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi");
2829
try {

sentry-samples/sentry-samples-spring-boot-jakarta/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.systemtest
22

3+
import io.sentry.protocol.FeatureFlag
34
import io.sentry.systemtest.util.TestHelper
45
import kotlin.test.Test
56
import kotlin.test.assertEquals
@@ -31,7 +32,8 @@ class PersonSystemTest {
3132
}
3233

3334
testHelper.ensureTransactionReceived { transaction, envelopeHeader ->
34-
testHelper.doesTransactionHaveOp(transaction, "http.server")
35+
testHelper.doesTransactionHave(transaction, op = "http.server", featureFlag = FeatureFlag("flag.evaluation.transaction-feature-flag", true))
36+
&& testHelper.doesTransactionHaveSpanWith(transaction, op = "spanCreatedThroughSentryApi", featureFlag = FeatureFlag("flag.evaluation.my-feature-flag", true))
3537
}
3638

3739
Thread.sleep(10000)

sentry-samples/sentry-samples-spring-boot/src/test/kotlin/io/sentry/systemtest/PersonSystemTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.systemtest
22

3+
import io.sentry.protocol.FeatureFlag
34
import io.sentry.systemtest.util.TestHelper
45
import kotlin.test.Test
56
import kotlin.test.assertEquals
@@ -26,7 +27,7 @@ class PersonSystemTest {
2627
}
2728

2829
testHelper.ensureTransactionReceived { transaction, envelopeHeader ->
29-
testHelper.doesTransactionHaveOp(transaction, "http.server")
30+
testHelper.doesTransactionHave(transaction, op = "http.server", featureFlag = FeatureFlag("flag.evaluation.my-feature-flag", true))
3031
}
3132

3233
Thread.sleep(10000)

sentry-system-test-support/src/main/kotlin/io/sentry/systemtest/util/TestHelper.kt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import io.sentry.SentryEvent
88
import io.sentry.SentryItemType
99
import io.sentry.SentryLogEvents
1010
import io.sentry.SentryOptions
11+
import io.sentry.protocol.FeatureFlag
1112
import io.sentry.protocol.SentrySpan
1213
import io.sentry.protocol.SentryTransaction
1314
import io.sentry.systemtest.graphql.GraphqlTestClient
@@ -154,7 +155,7 @@ class TestHelper(backendUrl: String) {
154155
}
155156

156157
fun ensureErrorReceived(callback: ((SentryEvent) -> Boolean)) {
157-
ensureEnvelopeReceived { envelopeString ->
158+
ensureEnvelopeReceived(retryCount = 3) { envelopeString ->
158159
val deserializeEnvelope = jsonSerializer.deserializeEnvelope(envelopeString.byteInputStream())
159160
if (deserializeEnvelope == null) {
160161
return@ensureEnvelopeReceived false
@@ -275,6 +276,52 @@ class TestHelper(backendUrl: String) {
275276
return true
276277
}
277278

279+
fun doesTransactionHave(transaction: SentryTransaction, op: String, featureFlag: FeatureFlag? = null): Boolean {
280+
val matches = transaction.contexts.trace?.operation == op
281+
if (!matches) {
282+
println("Unable to find transaction with op $op:")
283+
logObject(transaction)
284+
return false
285+
}
286+
287+
val foundFlag = transaction.contexts.trace?.data?.get(featureFlag?.flag)
288+
if (featureFlag != null && foundFlag == null) {
289+
println("Unable to find span with feature flag ${featureFlag?.flag}:")
290+
logObject(transaction)
291+
return false
292+
}
293+
if (featureFlag != null && foundFlag != featureFlag.result) {
294+
println("Feature flag ${featureFlag?.flag} has unexpected result ${foundFlag}:")
295+
logObject(transaction)
296+
return false
297+
}
298+
299+
return true
300+
}
301+
302+
fun doesTransactionHaveSpanWith(transaction: SentryTransaction, op: String, featureFlag: FeatureFlag? = null): Boolean {
303+
val foundSpan = transaction.spans.firstOrNull { span -> span.op == op }
304+
if (foundSpan == null) {
305+
println("Unable to find span with op $op:")
306+
logObject(transaction)
307+
return false
308+
}
309+
310+
val foundFlag = foundSpan.data?.get(featureFlag?.flag)
311+
if (featureFlag != null && foundFlag == null) {
312+
println("Unable to find span with feature flag ${featureFlag?.flag}:")
313+
logObject(transaction)
314+
return false
315+
}
316+
if (featureFlag != null && foundFlag != featureFlag.result) {
317+
println("Feature flag ${featureFlag?.flag} has unexpected result ${foundFlag}:")
318+
logObject(transaction)
319+
return false
320+
}
321+
322+
return true
323+
}
324+
278325
fun doesEventHaveExceptionMessage(event: SentryEvent, expectedMessage: String): Boolean {
279326
val exceptions = event.exceptions
280327
if (exceptions == null) {

sentry/src/main/java/io/sentry/Span.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ public final class Span implements ISpan {
5151

5252
private final @NotNull Contexts contexts = new Contexts();
5353

54-
private final @NotNull IFeatureFlagBuffer featureFlags = SpanFeatureFlagBuffer.create();
55-
5654
Span(
5755
final @NotNull SentryTracer transaction,
5856
final @NotNull IScopes scopes,
@@ -465,11 +463,6 @@ private List<Span> getDirectChildren() {
465463

466464
@Override
467465
public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {
468-
featureFlags.add(flag, result);
469-
}
470-
471-
@ApiStatus.Internal
472-
public @NotNull IFeatureFlagBuffer getFeatureFlagBuffer() {
473-
return featureFlags;
466+
context.addFeatureFlag(flag, result);
474467
}
475468
}

sentry/src/main/java/io/sentry/SpanContext.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package io.sentry;
22

33
import com.jakewharton.nopen.annotation.Open;
4+
5+
import io.sentry.featureflags.IFeatureFlagBuffer;
6+
import io.sentry.featureflags.SpanFeatureFlagBuffer;
47
import io.sentry.protocol.SentryId;
58
import io.sentry.util.CollectionUtils;
69
import io.sentry.util.Objects;
@@ -55,6 +58,8 @@ public class SpanContext implements JsonUnknown, JsonSerializable {
5558

5659
protected @Nullable Baggage baggage;
5760

61+
protected @NotNull IFeatureFlagBuffer featureFlags = SpanFeatureFlagBuffer.create();
62+
5863
/**
5964
* Set the profiler id associated with this transaction. If set to a non-empty id, this value will
6065
* be sent to sentry instead of {@link SentryOptions#getContinuousProfiler}
@@ -320,6 +325,16 @@ public void setProfilerId(@NotNull SentryId profilerId) {
320325
this.profilerId = profilerId;
321326
}
322327

328+
@ApiStatus.Internal
329+
public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {
330+
featureFlags.add(flag, result);
331+
}
332+
333+
@ApiStatus.Internal
334+
public @NotNull IFeatureFlagBuffer getFeatureFlagBuffer() {
335+
return featureFlags;
336+
}
337+
323338
// region JsonSerializable
324339

325340
public static final class JsonKeys {

sentry/src/main/java/io/sentry/protocol/SentrySpan.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public SentrySpan(final @NotNull Span span, final @Nullable Map<String, Object>
7474
// we lose precision here, from potential nanosecond precision down to 10 microsecond precision
7575
this.startTimestamp = DateUtils.nanosToSeconds(span.getStartDate().nanoTimestamp());
7676
this.data = data;
77-
final @NotNull IFeatureFlagBuffer featureFlagBuffer = span.getFeatureFlagBuffer();
77+
final @NotNull IFeatureFlagBuffer featureFlagBuffer = span.getSpanContext().getFeatureFlagBuffer();
7878
final @Nullable FeatureFlags featureFlags = featureFlagBuffer.getFeatureFlags();
7979
if (featureFlags != null && data != null) {
8080
for (FeatureFlag featureFlag : featureFlags.getValues()) {

sentry/src/main/java/io/sentry/protocol/SentryTransaction.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.sentry.SpanContext;
1616
import io.sentry.SpanStatus;
1717
import io.sentry.TracesSamplingDecision;
18+
import io.sentry.featureflags.IFeatureFlagBuffer;
1819
import io.sentry.util.Objects;
1920
import io.sentry.vendor.gson.stream.JsonToken;
2021
import java.io.IOException;
@@ -99,6 +100,14 @@ public SentryTransaction(final @NotNull SentryTracer sentryTracer) {
99100
}
100101
}
101102

103+
final @NotNull IFeatureFlagBuffer featureFlagBuffer = tracerContext.getFeatureFlagBuffer();
104+
final @Nullable FeatureFlags featureFlags = featureFlagBuffer.getFeatureFlags();
105+
if (featureFlags != null) {
106+
for (FeatureFlag featureFlag : featureFlags.getValues()) {
107+
tracerContextToSend.setData("flag.evaluation." + featureFlag.getFlag(), featureFlag.getResult());
108+
}
109+
}
110+
102111
contexts.setTrace(tracerContextToSend);
103112

104113
this.transactionInfo = new TransactionInfo(sentryTracer.getTransactionNameSource().apiName());

0 commit comments

Comments
 (0)