Skip to content

Commit f6f288f

Browse files
adinauerlciangetsentry-bot
authored
Add span based feature flags (#4831)
* Add scope based feature flags * fix equals * add serialization tests * Create new reference on buffer clone * add comment explaining the merge method * optimize merge method * make flag and result nullable params * handle empty/noop buffer on merge and add tests for it * optimize add method * format; fix test * Add rules for feature flags and mention in overview_dev * Fix duplicate check * Update sentry/src/main/java/io/sentry/SentryOptions.java Co-authored-by: Lorenzo Cian <17258265+lcian@users.noreply.github.com> * Format code * Add feature flags to spans * move feature flag buffer to span context; add e2e test * e2e tests; fix POTel feature flags * Format code * test feature flag serialization from SentryTracer * Format code * fix e2e test assertions; handle null data map * Format code * changelog * changelog * nullable getFeatureFlags * update changelog * address pr review feedback * Format code * fix(otel): Copy active span on scope clone (#4878) * Copy active span when cloning scope * changelog * api --------- Co-authored-by: Lorenzo Cian <17258265+lcian@users.noreply.github.com> Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
1 parent ce0ff30 commit f6f288f

File tree

43 files changed

+553
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+553
-29
lines changed

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44

55
### Features
66

7-
- Add feature flags API ([#4812](https://github.com/getsentry/sentry-java/pull/4812))
7+
- Add feature flags API ([#4812](https://github.com/getsentry/sentry-java/pull/4812)) and ([#4831](https://github.com/getsentry/sentry-java/pull/4831))
88
- You may now keep track of your feature flag evaluations and have them show up in Sentry.
9-
- You may use top level API (`Sentry.addFeatureFlag("my-feature-flag", true);`) or `IScope` and `IScopes` API
9+
- Top level API (`Sentry.addFeatureFlag("my-feature-flag", true);`) writes to scopes and the current span (if there is one)
10+
- It is also possible to use API on `IScope`, `IScopes`, `ISpan` and `ITransaction` directly
1011
- Feature flag evaluations tracked on scope(s) will be added to any errors reported to Sentry.
11-
- The SDK keeps the latest 100 evaluations from scope(s), replacing old entries as new evaluations are added.
12+
- The SDK keeps the latest 100 evaluations from scope(s), replacing old entries as new evaluations are added.
13+
- For feature flag evaluations tracked on spans:
14+
- Only 10 evaluations are tracked per span, existing flags are updated but new ones exceeding the limit are ignored
15+
- Spans do not inherit evaluations from their parent
1216

1317
### Fixes
1418

@@ -20,6 +24,7 @@
2024
- Fix log count in client reports ([#4869](https://github.com/getsentry/sentry-java/pull/4869))
2125
- Fix profilerId propagation ([#4833](https://github.com/getsentry/sentry-java/pull/4833))
2226
- Fix profiling init for Spring and Spring Boot w Agent auto-init ([#4815](https://github.com/getsentry/sentry-java/pull/4815))
27+
- Copy active span on scope clone ([#4878](https://github.com/getsentry/sentry-java/pull/4878))
2328

2429
### Improvements
2530

sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public final class io/sentry/opentelemetry/OtelSpanFactory : io/sentry/ISpanFact
4343

4444
public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/opentelemetry/IOtelSpanWrapper {
4545
public fun <init> (Lio/opentelemetry/api/trace/Span;Lio/sentry/opentelemetry/IOtelSpanWrapper;)V
46+
public fun addFeatureFlag (Ljava/lang/String;Ljava/lang/Boolean;)V
4647
public fun finish ()V
4748
public fun finish (Lio/sentry/SpanStatus;)V
4849
public fun finish (Lio/sentry/SpanStatus;Lio/sentry/SentryDate;)V
@@ -96,6 +97,7 @@ public final class io/sentry/opentelemetry/OtelStrongRefSpanWrapper : io/sentry/
9697

9798
public final class io/sentry/opentelemetry/OtelTransactionSpanForwarder : io/sentry/ITransaction {
9899
public fun <init> (Lio/sentry/opentelemetry/IOtelSpanWrapper;)V
100+
public fun addFeatureFlag (Ljava/lang/String;Ljava/lang/Boolean;)V
99101
public fun finish ()V
100102
public fun finish (Lio/sentry/SpanStatus;)V
101103
public fun finish (Lio/sentry/SpanStatus;Lio/sentry/SentryDate;)V

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelStrongRefSpanWrapper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,9 @@ public void setContext(@Nullable String key, @Nullable Object context) {
310310
public @Nullable Attributes getOpenTelemetrySpanAttributes() {
311311
return delegate.getOpenTelemetrySpanAttributes();
312312
}
313+
314+
@Override
315+
public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {
316+
delegate.addFeatureFlag(flag, result);
317+
}
313318
}

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelTransactionSpanForwarder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,4 +309,9 @@ public void setName(@NotNull String name, @NotNull TransactionNameSource nameSou
309309
}
310310
return name;
311311
}
312+
313+
@Override
314+
public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {
315+
rootSpan.addFeatureFlag(flag, result);
316+
}
312317
}

sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public final class io/sentry/opentelemetry/OtelSpanUtils {
5757

5858
public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelemetry/IOtelSpanWrapper {
5959
public fun <init> (Lio/opentelemetry/sdk/trace/ReadWriteSpan;Lio/sentry/IScopes;Lio/sentry/SentryDate;Lio/sentry/TracesSamplingDecision;Lio/sentry/opentelemetry/IOtelSpanWrapper;Lio/sentry/SpanId;Lio/sentry/Baggage;)V
60+
public fun addFeatureFlag (Ljava/lang/String;Ljava/lang/Boolean;)V
6061
public fun finish ()V
6162
public fun finish (Lio/sentry/SpanStatus;)V
6263
public fun finish (Lio/sentry/SpanStatus;Lio/sentry/SentryDate;)V

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,11 @@ public Map<String, MeasurementValue> getMeasurements() {
505505
return scopes;
506506
}
507507

508+
@Override
509+
public void addFeatureFlag(final @Nullable String flag, final @Nullable Boolean result) {
510+
context.addFeatureFlag(flag, result);
511+
}
512+
508513
@Override
509514
public @NotNull Context storeInContext(Context context) {
510515
final @Nullable ReadWriteSpan otelSpan = getSpan();

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
import io.sentry.SpanStatus;
3434
import io.sentry.TransactionContext;
3535
import io.sentry.TransactionOptions;
36+
import io.sentry.featureflags.IFeatureFlagBuffer;
3637
import io.sentry.protocol.Contexts;
38+
import io.sentry.protocol.FeatureFlag;
39+
import io.sentry.protocol.FeatureFlags;
3740
import io.sentry.protocol.SentryId;
3841
import io.sentry.protocol.TransactionNameSource;
3942
import java.util.Arrays;
@@ -260,6 +263,16 @@ private void transferSpanDetails(
260263
targetSpan.setData(entry.getKey(), entry.getValue());
261264
}
262265

266+
final @NotNull SpanContext spanContext = sourceSpan.getSpanContext();
267+
final @NotNull IFeatureFlagBuffer featureFlagBuffer = spanContext.getFeatureFlagBuffer();
268+
final @Nullable FeatureFlags featureFlags = featureFlagBuffer.getFeatureFlags();
269+
if (featureFlags != null) {
270+
for (FeatureFlag featureFlag : featureFlags.getValues()) {
271+
targetSpan.setData(
272+
FeatureFlag.DATA_PREFIX + featureFlag.getFlag(), featureFlag.getResult());
273+
}
274+
}
275+
263276
final @NotNull Map<String, String> tags = sourceSpan.getTags();
264277
for (Map.Entry<String, String> entry : tags.entrySet()) {
265278
targetSpan.setTag(entry.getKey(), entry.getValue());

sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/src/main/java/io/sentry/samples/spring/boot4/PersonController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ public PersonController(PersonService personService, Tracer tracer) {
3131
@GetMapping("{id}")
3232
@WithSpan("personSpanThroughOtelAnnotation")
3333
Person person(@PathVariable Long id) {
34+
Sentry.addFeatureFlag("outer-feature-flag", true);
3435
Span span = tracer.spanBuilder("spanCreatedThroughOtelApi").startSpan();
3536
try (final @NotNull Scope spanScope = span.makeCurrent()) {
3637
Sentry.logger().warn("warn Sentry logging");
3738
Sentry.logger().error("error Sentry logging");
3839
Sentry.logger().info("hello %s %s", "there", "world!");
39-
Sentry.addFeatureFlag("my-feature-flag", true);
40+
Sentry.addFeatureFlag("inner-feature-flag", true);
4041
ISpan currentSpan = Sentry.getSpan();
4142
ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi");
4243
try {

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

Lines changed: 19 additions & 4 deletions
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
@@ -22,17 +23,31 @@ class PersonSystemTest {
2223

2324
testHelper.ensureErrorReceived { event ->
2425
event.message?.formatted == "Trying person with id=1" &&
25-
testHelper.doesEventHaveFlag(event, "my-feature-flag", true)
26+
testHelper.doesEventHaveFlag(event, "inner-feature-flag", true)
2627
}
2728

2829
testHelper.ensureErrorReceived { event ->
2930
testHelper.doesEventHaveExceptionMessage(event, "Something went wrong [id=1]") &&
30-
testHelper.doesEventHaveFlag(event, "my-feature-flag", true)
31+
testHelper.doesEventHaveFlag(event, "inner-feature-flag", true)
3132
}
3233

3334
testHelper.ensureTransactionReceived { transaction, envelopeHeader ->
34-
testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughOtelApi") &&
35-
testHelper.doesTransactionContainSpanWithOp(transaction, "spanCreatedThroughSentryApi")
35+
testHelper.doesTransactionHave(transaction, op = "http.server") &&
36+
testHelper.doesTransactionHaveSpanWith(
37+
transaction,
38+
op = "personSpanThroughOtelAnnotation",
39+
featureFlag = FeatureFlag("flag.evaluation.outer-feature-flag", true),
40+
) &&
41+
testHelper.doesTransactionHaveSpanWith(
42+
transaction,
43+
op = "spanCreatedThroughOtelApi",
44+
featureFlag = FeatureFlag("flag.evaluation.inner-feature-flag", true),
45+
) &&
46+
testHelper.doesTransactionHaveSpanWith(
47+
transaction,
48+
op = "spanCreatedThroughSentryApi",
49+
noFeatureFlags = true,
50+
)
3651
}
3752

3853
Thread.sleep(10000)

sentry-samples/sentry-samples-spring-boot-4-opentelemetry/src/main/java/io/sentry/samples/spring/boot4/PersonController.java

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

3030
@GetMapping("{id}")
3131
Person person(@PathVariable Long id) {
32+
Sentry.addFeatureFlag("transaction-feature-flag", true);
3233
Span span = tracer.spanBuilder("spanCreatedThroughOtelApi").startSpan();
3334
try (final @NotNull Scope spanScope = span.makeCurrent()) {
3435
Sentry.logger().warn("warn Sentry logging");

0 commit comments

Comments
 (0)