From 37fdc93ce7d2d0f34c7093baa1935d73eefa2a8e Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Sat, 29 Nov 2025 18:17:28 +0800 Subject: [PATCH 1/3] feat: add ShouldStartFilter to control span creation in Instrumenter --- .../instrumenter/InstrumenterCustomizer.java | 10 +++ .../internal/InstrumenterCustomizerImpl.java | 7 ++ .../api/instrumenter/Instrumenter.java | 7 ++ .../api/instrumenter/InstrumenterBuilder.java | 17 ++++ .../api/instrumenter/ShouldStartFilter.java | 84 +++++++++++++++++++ .../InternalInstrumenterCustomizer.java | 3 + .../api/instrumenter/InstrumenterTest.java | 15 ++++ .../instrumenter/ShouldStartFilterTest.java | 79 +++++++++++++++++ .../InstrumentationCustomizerTest.java | 22 +++++ 9 files changed, 244 insertions(+) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java create mode 100644 instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java index 0873f4054969..5d2446464138 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java @@ -10,6 +10,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractor; @@ -81,6 +82,15 @@ InstrumenterCustomizer addAttributesExtractors( */ InstrumenterCustomizer addContextCustomizer(ContextCustomizer customizer); + /** + * Adds a {@link ShouldStartFilter} that will be used to determine whether a span should be + * started for the given operation. The filter is called before any span creation logic. + * + * @param filter the should start filter to add + * @return this InstrumenterCustomizer for method chaining + */ + InstrumenterCustomizer addShouldStartFilter(ShouldStartFilter filter); + /** * Sets a transformer function that will modify the {@link SpanNameExtractor}. This allows * customizing how span names are generated for the instrumented operations. diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java index a45862652156..254e10e2900e 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java @@ -9,6 +9,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import io.opentelemetry.instrumentation.api.internal.InternalInstrumenterCustomizer; @@ -81,6 +82,12 @@ public InstrumenterCustomizer addContextCustomizer(ContextCustomizer customiz return this; } + @Override + public InstrumenterCustomizer addShouldStartFilter(ShouldStartFilter filter) { + customizer.addShouldStartFilter(filter); + return this; + } + @Override @SuppressWarnings("FunctionalInterfaceClash") // interface has deprecated overload public InstrumenterCustomizer setSpanNameExtractor( diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java index 5e2ff98b0048..27d423b8454a 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java @@ -18,6 +18,7 @@ import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; import java.time.Instant; +import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -84,6 +85,7 @@ public static InstrumenterBuilder builder private final boolean propagateOperationListenersToOnEnd; private final boolean enabled; private final SpanSuppressor spanSuppressor; + private final ShouldStartFilter shouldStartFilter; // to allow converting generic lists to arrays with toArray @SuppressWarnings({"rawtypes", "unchecked"}) @@ -103,6 +105,7 @@ public static InstrumenterBuilder builder this.propagateOperationListenersToOnEnd = builder.propagateOperationListenersToOnEnd; this.enabled = builder.enabled; this.spanSuppressor = builder.buildSpanSuppressor(); + this.shouldStartFilter = ShouldStartFilter.allOf((List>) (List) builder.shouldStartFilters); } /** @@ -120,6 +123,10 @@ public boolean shouldStart(Context parentContext, REQUEST request) { return false; } SpanKind spanKind = spanKindExtractor.extract(request); + + if (!shouldStartFilter.shouldStart(parentContext, request, spanKind, instrumentationName)) { + return false; + } boolean suppressed = spanSuppressor.shouldSuppress(parentContext, spanKind); if (suppressed) { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java index eb80bd8dd1d7..b63d2a66f6df 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java @@ -64,6 +64,7 @@ public final class InstrumenterBuilder { final List> operationListenerAttributesExtractors = new ArrayList<>(); final List> contextCustomizers = new ArrayList<>(); + final List> shouldStartFilters = new ArrayList<>(); private final List operationListeners = new ArrayList<>(); private final List operationMetrics = new ArrayList<>(); @@ -187,6 +188,17 @@ public InstrumenterBuilder addOperationMetrics(OperationMetri return this; } + /** + * Adds a {@link ShouldStartFilter} that will be used to determine whether a span should be + * started for the given operation. The filter is called before any span creation logic. + */ + @CanIgnoreReturnValue + public InstrumenterBuilder addShouldStartFilter( + ShouldStartFilter filter) { + shouldStartFilters.add(requireNonNull(filter, "shouldStartFilter")); + return this; + } + /** * Sets the {@link ErrorCauseExtractor} that will extract the root cause of an error thrown during * request processing. @@ -436,6 +448,11 @@ public void addContextCustomizer(ContextCustomizer customizer) { builder.addContextCustomizer(customizer); } + @Override + public void addShouldStartFilter(ShouldStartFilter filter) { + builder.addShouldStartFilter(filter); + } + @Override public void setSpanNameExtractor( UnaryOperator> spanNameExtractorTransformer) { diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java new file mode 100644 index 000000000000..39cafe67823b --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import java.util.List; + +/** + * A filter that determines whether a span should be started for the given operation. + * + *

This filter is called before any span creation logic and allows for early filtering of spans + * based on the request context, span kind, and instrumentation name. This is useful for scenarios + * such as: + * + *

    + *
  • Filtering spans based on thread information (e.g., background threads) + *
  • Implementing rate limiting logic before span creation + *
  • Custom filtering based on request properties + *
+ * + *

Filters are executed in priority order (lower numbers = higher priority) and all filters must + * return {@code true} for a span to be started. + */ +public interface ShouldStartFilter { + + /** + * Determines whether a span should be started for the given operation. + * + * @param parentContext the parent context of the operation + * @param request the request object of the operation + * @param spanKind the span kind that would be created + * @param instrumentationName the name of the instrumentation + * @return {@code true} if the span should be started, {@code false} otherwise + */ + boolean shouldStart(Context parentContext, REQUEST request, SpanKind spanKind, String instrumentationName); + + /** + * Returns the priority of this filter. Filters with lower numbers have higher priority and are + * executed first. + * + * @return the priority of this filter, defaults to 0 + */ + default int getPriority() { + return 0; + } + + /** + * Returns a filter that always allows spans to be started. + * + * @return a pass-through filter + */ + static ShouldStartFilter none() { + return (parentContext, request, spanKind, instrumentationName) -> true; + } + + /** + * Combines multiple filters into a single composite filter. + */ + static ShouldStartFilter allOf(List> filters) { + if (filters.isEmpty()) { + return none(); + } + if (filters.size() == 1) { + return filters.get(0); + } + + List> sortedFilters = filters.stream() + .sorted((f1, f2) -> Integer.compare(f1.getPriority(), f2.getPriority())) + .collect(java.util.stream.Collectors.toList()); + + return (parentContext, request, spanKind, instrumentationName) -> { + for (ShouldStartFilter filter : sortedFilters) { + if (!filter.shouldStart(parentContext, request, spanKind, instrumentationName)) { + return false; + } + } + return true; + }; + } +} diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java index 6f88bd8c225e..901368a5b170 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java @@ -8,6 +8,7 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import java.util.function.UnaryOperator; @@ -31,6 +32,8 @@ void addAttributesExtractors( void addContextCustomizer(ContextCustomizer customizer); + void addShouldStartFilter(ShouldStartFilter filter); + void setSpanNameExtractor( UnaryOperator> spanNameExtractorTransformer); diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index 792193a749b0..ec27a84c3d1d 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -619,6 +619,21 @@ void shouldDisableInstrumenter() { assertThat(instrumenter.shouldStart(Context.root(), "request")).isFalse(); } + @Test + void shouldStartFilter() { + ShouldStartFilter filter = (context, request, spanKind, instrumentationName) -> + !request.equals("blocked"); + + Instrumenter instrumenter = + Instrumenter.builder( + otelTesting.getOpenTelemetry(), "test", request -> "test span") + .addShouldStartFilter(filter) + .buildInstrumenter(); + + assertThat(instrumenter.shouldStart(Context.root(), "allowed")).isTrue(); + assertThat(instrumenter.shouldStart(Context.root(), "blocked")).isFalse(); + } + @Test void instrumentationVersion_default() { InstrumenterBuilder, Map> builder = diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java new file mode 100644 index 000000000000..54879743a990 --- /dev/null +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.instrumenter; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class ShouldStartFilterTest { + + @Test + void none() { + ShouldStartFilter filter = ShouldStartFilter.none(); + + assertThat(filter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test")) + .isTrue(); + assertThat(filter.getPriority()).isEqualTo(0); + } + + @Test + void allOf() { + ShouldStartFilter filter1 = (context, request, spanKind, instrumentationName) -> + !request.equals("blocked1"); + ShouldStartFilter filter2 = (context, request, spanKind, instrumentationName) -> + !request.equals("blocked2"); + + ShouldStartFilter compositeFilter = ShouldStartFilter.allOf(Arrays.asList(filter1, filter2)); + + assertThat(compositeFilter.shouldStart(Context.root(), "allowed", SpanKind.CLIENT, "test")) + .isTrue(); + assertThat(compositeFilter.shouldStart(Context.root(), "blocked1", SpanKind.CLIENT, "test")) + .isFalse(); + assertThat(compositeFilter.shouldStart(Context.root(), "blocked2", SpanKind.CLIENT, "test")) + .isFalse(); + } + + @Test + void allOfPriority() { + StringBuilder executionOrder = new StringBuilder(); + + ShouldStartFilter highPriority = new ShouldStartFilter() { + @Override + public boolean shouldStart(Context parentContext, String request, SpanKind spanKind, String instrumentationName) { + executionOrder.append("high"); + return false; + } + + @Override + public int getPriority() { + return 1; + } + }; + + ShouldStartFilter lowPriority = new ShouldStartFilter() { + @Override + public boolean shouldStart(Context parentContext, String request, SpanKind spanKind, String instrumentationName) { + executionOrder.append("low"); + return true; + } + + @Override + public int getPriority() { + return 10; + } + }; + + ShouldStartFilter compositeFilter = ShouldStartFilter.allOf(Arrays.asList(lowPriority, highPriority)); + boolean result = compositeFilter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test"); + + assertThat(result).isFalse(); + assertThat(executionOrder.toString()).isEqualTo("high"); + } +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java index 725f71726c1c..a45afd7c183a 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java @@ -404,6 +404,28 @@ void testSetSpanStatusExtractor() { .hasAttributes(Attributes.empty()))); } + @Test + void testAddShouldStartFilter() { + AtomicBoolean customizerCalled = new AtomicBoolean(); + setCustomizer( + customizer -> { + customizerCalled.set(true); + customizer.addShouldStartFilter( + (context, request, spanKind, instrumentationName) -> + !request.equals("blocked")); + }); + + Instrumenter instrumenter = + Instrumenter.builder( + otelTesting.getOpenTelemetry(), "test", unused -> "span") + .buildInstrumenter(); + + assertThat(customizerCalled).isTrue(); + + assertThat(instrumenter.shouldStart(Context.root(), "allowed")).isTrue(); + assertThat(instrumenter.shouldStart(Context.root(), "blocked")).isFalse(); + } + static class AttributesExtractor1 implements AttributesExtractor, Map> { From c953b2e3f9c3b56166fc9f051c2380afd475656b Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Mon, 1 Dec 2025 14:50:54 +0800 Subject: [PATCH 2/3] Add demo --- examples/distro/README.md | 7 ++ .../DemoInstrumenterCustomizerProvider.java | 17 ++++ examples/extension/README.md | 7 ++ .../DemoInstrumenterCustomizerProvider.java | 17 ++++ .../api/instrumenter/Instrumenter.java | 6 +- .../api/instrumenter/ShouldStartFilter.java | 31 ++----- .../api/instrumenter/InstrumenterTest.java | 6 +- .../instrumenter/ShouldStartFilterTest.java | 92 +++++++++++-------- .../InstrumentationCustomizerTest.java | 3 +- 9 files changed, 116 insertions(+), 70 deletions(-) diff --git a/examples/distro/README.md b/examples/distro/README.md index 7ad6355590e4..cc60bb41b9b2 100644 --- a/examples/distro/README.md +++ b/examples/distro/README.md @@ -45,11 +45,18 @@ The `InstrumenterCustomizerProvider` extension point allows you to customize ins - Customize context - Transform span names to match your naming conventions - Apply customizations conditionally based on instrumentation name or type (HTTP client, HTTP server, DB client, etc.) +- Filter out spans before creation using ShouldStartFilter ### I don't want this span at all The easiest case. You can just pre-configure your distribution and disable given instrumentation. +### I want to filter out specific requests or operations before span creation + +Use a custom `ShouldStartFilter` to prevent spans from being created for specific requests, operations, or conditions. This is more efficient than creating spans and filtering them later, as it prevents the overhead of span creation entirely. + +See the `DemoShouldStartFilter` inner class in [DemoInstrumenterCustomizerProvider](custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java). + ### I want to add/modify some attributes and their values does NOT depend on a specific db connection instance E.g. you want to add some data from call stack as span attribute. diff --git a/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java b/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java index e57e8d5e146a..5ee6363c41e0 100644 --- a/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java +++ b/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java @@ -18,7 +18,9 @@ import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.api.trace.SpanKind; import java.util.concurrent.atomic.AtomicLong; /** @@ -32,6 +34,7 @@ *

  • Custom metrics for HTTP operations *
  • Request correlation IDs via context customization *
  • Custom span name transformation + *
  • Filter out spans before creation using ShouldStartFilter * * *

    The customizer will be automatically applied to instrumenters that match the specified @@ -44,6 +47,9 @@ public class DemoInstrumenterCustomizerProvider implements InstrumenterCustomize @Override public void customize(InstrumenterCustomizer customizer) { + + customizer.addShouldStartFilter(new DemoShouldStartFilter()); + String instrumentationName = customizer.getInstrumentationName(); if (isHttpServerInstrumentation(instrumentationName)) { customizeHttpServer(customizer); @@ -159,4 +165,15 @@ public Context onStart(Context context, Object request, Attributes startAttribut return context; } } + + /** Simple ShouldStartFilter that skips spans for monitoring threads. */ + private static class DemoShouldStartFilter implements ShouldStartFilter { + @Override + public boolean shouldStart( + Context parentContext, Object request, SpanKind spanKind, String instrumentationName) { + // Skip spans for monitoring/metrics threads to reduce noise + String threadName = Thread.currentThread().getName().toLowerCase(); + return !threadName.contains("metrics") && !threadName.contains("monitor"); + } + } } diff --git a/examples/extension/README.md b/examples/extension/README.md index ad16a0801a9a..efa298979607 100644 --- a/examples/extension/README.md +++ b/examples/extension/README.md @@ -75,11 +75,18 @@ The `InstrumenterCustomizerProvider` extension point allows you to customize ins - Customize context - Transform span names to match your naming conventions - Apply customizations conditionally based on instrumentation name or type (HTTP client, HTTP server, DB client, etc.) +- Filter out spans before creation using ShouldStartFilter ### "I don't want this span at all" Create an extension to disable selected instrumentation by providing new default settings. +### "I want to filter out specific requests before span creation" + +Create an extension with a custom `ShouldStartFilter` to prevent spans from being created for specific requests (e.g., background threads, monitoring operations). This is more efficient than creating spans and then dropping them later, as it prevents the overhead of span creation entirely. + +For example, see the `DemoShouldStartFilter` inner class in [DemoInstrumenterCustomizerProvider](src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java). + ### "I want to edit some attributes that don't depend on any db connection instance" Create an extension that provide a custom `SpanProcessor`. diff --git a/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java b/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java index f9e1e96727b3..4e5c77bd7924 100644 --- a/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java +++ b/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java @@ -19,7 +19,9 @@ import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import io.opentelemetry.api.trace.SpanKind; import java.util.concurrent.atomic.AtomicLong; /** @@ -34,6 +36,7 @@ *
  • Custom metrics for HTTP operations *
  • Request correlation IDs via context customization *
  • Custom span name transformation + *
  • Filter out spans before creation using ShouldStartFilter * * *

    The customizer will be automatically applied to instrumenters that match the specified @@ -47,6 +50,9 @@ public class DemoInstrumenterCustomizerProvider implements InstrumenterCustomize @Override public void customize(InstrumenterCustomizer customizer) { + + customizer.addShouldStartFilter(new DemoShouldStartFilter()); + String instrumentationName = customizer.getInstrumentationName(); if (isHttpServerInstrumentation(instrumentationName)) { customizeHttpServer(customizer); @@ -162,4 +168,15 @@ public Context onStart(Context context, Object request, Attributes startAttribut return context; } } + + /** Simple ShouldStartFilter that skips spans for background threads. */ + private static class DemoShouldStartFilter implements ShouldStartFilter { + @Override + public boolean shouldStart( + Context parentContext, Object request, SpanKind spanKind, String instrumentationName) { + // Skip spans for monitoring/metrics threads to reduce noise + String threadName = Thread.currentThread().getName().toLowerCase(); + return !threadName.contains("metrics") && !threadName.contains("monitor"); + } + } } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java index 27d423b8454a..dd943d73681f 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java @@ -105,7 +105,9 @@ public static InstrumenterBuilder builder this.propagateOperationListenersToOnEnd = builder.propagateOperationListenersToOnEnd; this.enabled = builder.enabled; this.spanSuppressor = builder.buildSpanSuppressor(); - this.shouldStartFilter = ShouldStartFilter.allOf((List>) (List) builder.shouldStartFilters); + this.shouldStartFilter = + ShouldStartFilter.allOf( + (List>) (List) builder.shouldStartFilters); } /** @@ -123,7 +125,7 @@ public boolean shouldStart(Context parentContext, REQUEST request) { return false; } SpanKind spanKind = spanKindExtractor.extract(request); - + if (!shouldStartFilter.shouldStart(parentContext, request, spanKind, instrumentationName)) { return false; } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java index 39cafe67823b..90ff8068aa68 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java @@ -9,22 +9,7 @@ import io.opentelemetry.context.Context; import java.util.List; -/** - * A filter that determines whether a span should be started for the given operation. - * - *

    This filter is called before any span creation logic and allows for early filtering of spans - * based on the request context, span kind, and instrumentation name. This is useful for scenarios - * such as: - * - *

      - *
    • Filtering spans based on thread information (e.g., background threads) - *
    • Implementing rate limiting logic before span creation - *
    • Custom filtering based on request properties - *
    - * - *

    Filters are executed in priority order (lower numbers = higher priority) and all filters must - * return {@code true} for a span to be started. - */ +/** A filter that determines whether a span should be started for the given operation. */ public interface ShouldStartFilter { /** @@ -36,7 +21,8 @@ public interface ShouldStartFilter { * @param instrumentationName the name of the instrumentation * @return {@code true} if the span should be started, {@code false} otherwise */ - boolean shouldStart(Context parentContext, REQUEST request, SpanKind spanKind, String instrumentationName); + boolean shouldStart( + Context parentContext, REQUEST request, SpanKind spanKind, String instrumentationName); /** * Returns the priority of this filter. Filters with lower numbers have higher priority and are @@ -57,9 +43,7 @@ static ShouldStartFilter none() { return (parentContext, request, spanKind, instrumentationName) -> true; } - /** - * Combines multiple filters into a single composite filter. - */ + /** Combines multiple filters into a single composite filter. */ static ShouldStartFilter allOf(List> filters) { if (filters.isEmpty()) { return none(); @@ -68,9 +52,10 @@ static ShouldStartFilter allOf(List> sortedFilters = filters.stream() - .sorted((f1, f2) -> Integer.compare(f1.getPriority(), f2.getPriority())) - .collect(java.util.stream.Collectors.toList()); + List> sortedFilters = + filters.stream() + .sorted((f1, f2) -> Integer.compare(f1.getPriority(), f2.getPriority())) + .collect(java.util.stream.Collectors.toList()); return (parentContext, request, spanKind, instrumentationName) -> { for (ShouldStartFilter filter : sortedFilters) { diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index ec27a84c3d1d..d0fd0258fc5d 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -621,9 +621,9 @@ void shouldDisableInstrumenter() { @Test void shouldStartFilter() { - ShouldStartFilter filter = (context, request, spanKind, instrumentationName) -> - !request.equals("blocked"); - + ShouldStartFilter filter = + (context, request, spanKind, instrumentationName) -> !request.equals("blocked"); + Instrumenter instrumenter = Instrumenter.builder( otelTesting.getOpenTelemetry(), "test", request -> "test span") diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java index 54879743a990..3574d8d10592 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java @@ -17,21 +17,21 @@ class ShouldStartFilterTest { @Test void none() { ShouldStartFilter filter = ShouldStartFilter.none(); - - assertThat(filter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test")) - .isTrue(); + + assertThat(filter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test")).isTrue(); assertThat(filter.getPriority()).isEqualTo(0); } @Test void allOf() { - ShouldStartFilter filter1 = (context, request, spanKind, instrumentationName) -> - !request.equals("blocked1"); - ShouldStartFilter filter2 = (context, request, spanKind, instrumentationName) -> - !request.equals("blocked2"); - - ShouldStartFilter compositeFilter = ShouldStartFilter.allOf(Arrays.asList(filter1, filter2)); - + ShouldStartFilter filter1 = + (context, request, spanKind, instrumentationName) -> !request.equals("blocked1"); + ShouldStartFilter filter2 = + (context, request, spanKind, instrumentationName) -> !request.equals("blocked2"); + + ShouldStartFilter compositeFilter = + ShouldStartFilter.allOf(Arrays.asList(filter1, filter2)); + assertThat(compositeFilter.shouldStart(Context.root(), "allowed", SpanKind.CLIENT, "test")) .isTrue(); assertThat(compositeFilter.shouldStart(Context.root(), "blocked1", SpanKind.CLIENT, "test")) @@ -43,36 +43,48 @@ void allOf() { @Test void allOfPriority() { StringBuilder executionOrder = new StringBuilder(); - - ShouldStartFilter highPriority = new ShouldStartFilter() { - @Override - public boolean shouldStart(Context parentContext, String request, SpanKind spanKind, String instrumentationName) { - executionOrder.append("high"); - return false; - } - - @Override - public int getPriority() { - return 1; - } - }; - - ShouldStartFilter lowPriority = new ShouldStartFilter() { - @Override - public boolean shouldStart(Context parentContext, String request, SpanKind spanKind, String instrumentationName) { - executionOrder.append("low"); - return true; - } - - @Override - public int getPriority() { - return 10; - } - }; - - ShouldStartFilter compositeFilter = ShouldStartFilter.allOf(Arrays.asList(lowPriority, highPriority)); - boolean result = compositeFilter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test"); - + + ShouldStartFilter highPriority = + new ShouldStartFilter() { + @Override + public boolean shouldStart( + Context parentContext, + String request, + SpanKind spanKind, + String instrumentationName) { + executionOrder.append("high"); + return false; + } + + @Override + public int getPriority() { + return 1; + } + }; + + ShouldStartFilter lowPriority = + new ShouldStartFilter() { + @Override + public boolean shouldStart( + Context parentContext, + String request, + SpanKind spanKind, + String instrumentationName) { + executionOrder.append("low"); + return true; + } + + @Override + public int getPriority() { + return 10; + } + }; + + ShouldStartFilter compositeFilter = + ShouldStartFilter.allOf(Arrays.asList(lowPriority, highPriority)); + boolean result = + compositeFilter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test"); + assertThat(result).isFalse(); assertThat(executionOrder.toString()).isEqualTo("high"); } diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java index a45afd7c183a..2d6690420f82 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/InstrumentationCustomizerTest.java @@ -411,8 +411,7 @@ void testAddShouldStartFilter() { customizer -> { customizerCalled.set(true); customizer.addShouldStartFilter( - (context, request, spanKind, instrumentationName) -> - !request.equals("blocked")); + (context, request, spanKind, instrumentationName) -> !request.equals("blocked")); }); Instrumenter instrumenter = From 26140e5c1206b324ebcaf622f6acd5c95e55f569 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Mon, 1 Dec 2025 18:19:29 +0800 Subject: [PATCH 3/3] Optimize codes --- .../opentelemetry-instrumentation-api.txt | 5 +- .../DemoInstrumenterCustomizerProvider.java | 6 +- .../DemoInstrumenterCustomizerProvider.java | 6 +- .../instrumenter/InstrumenterCustomizer.java | 1 - .../instrumenter/ShouldStartFilter.java | 10 ++- .../internal/InstrumenterCustomizerImpl.java | 9 ++- .../api/instrumenter/Instrumenter.java | 7 +- .../api/instrumenter/InstrumenterBuilder.java | 11 +-- .../InternalInstrumenterCustomizer.java | 3 +- .../internal/InternalShouldStartFilter.java | 77 +++++++++++++++++++ .../api/instrumenter/InstrumenterTest.java | 3 +- .../ShouldStartFilterTest.java | 10 +-- 12 files changed, 120 insertions(+), 28 deletions(-) rename {instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api => instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator}/instrumenter/ShouldStartFilter.java (87%) create mode 100644 instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalShouldStartFilter.java rename instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/{instrumenter => internal}/ShouldStartFilterTest.java (87%) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt index e3651d8b9d4e..3ff88e47422f 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-instrumentation-api.txt @@ -1,2 +1,5 @@ Comparing source compatibility of opentelemetry-instrumentation-api-2.23.0-SNAPSHOT.jar against opentelemetry-instrumentation-api-2.22.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + GENERIC TEMPLATES: === REQUEST:java.lang.Object, === RESPONSE:java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder addShouldStartFilter(io.opentelemetry.instrumentation.api.internal.InternalShouldStartFilter) diff --git a/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java b/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java index 5ee6363c41e0..0128a2975ccf 100644 --- a/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java +++ b/examples/distro/custom/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java @@ -10,17 +10,17 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizer; import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizerProvider; +import io.opentelemetry.instrumentation.api.incubator.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; -import io.opentelemetry.api.trace.SpanKind; import java.util.concurrent.atomic.AtomicLong; /** @@ -49,7 +49,7 @@ public class DemoInstrumenterCustomizerProvider implements InstrumenterCustomize public void customize(InstrumenterCustomizer customizer) { customizer.addShouldStartFilter(new DemoShouldStartFilter()); - + String instrumentationName = customizer.getInstrumentationName(); if (isHttpServerInstrumentation(instrumentationName)) { customizeHttpServer(customizer); diff --git a/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java b/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java index 4e5c77bd7924..d6c812dbf685 100644 --- a/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java +++ b/examples/extension/src/main/java/com/example/javaagent/DemoInstrumenterCustomizerProvider.java @@ -11,17 +11,17 @@ import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.context.ContextKey; import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizer; import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizerProvider; +import io.opentelemetry.instrumentation.api.incubator.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationListener; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; -import io.opentelemetry.api.trace.SpanKind; import java.util.concurrent.atomic.AtomicLong; /** @@ -52,7 +52,7 @@ public class DemoInstrumenterCustomizerProvider implements InstrumenterCustomize public void customize(InstrumenterCustomizer customizer) { customizer.addShouldStartFilter(new DemoShouldStartFilter()); - + String instrumentationName = customizer.getInstrumentationName(); if (isHttpServerInstrumentation(instrumentationName)) { customizeHttpServer(customizer); diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java index 5d2446464138..64eb8c475120 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/InstrumenterCustomizer.java @@ -10,7 +10,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import io.opentelemetry.instrumentation.api.semconv.http.HttpClientAttributesExtractor; diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/ShouldStartFilter.java similarity index 87% rename from instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java rename to instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/ShouldStartFilter.java index 90ff8068aa68..53d9f9d1b2e2 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilter.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/ShouldStartFilter.java @@ -3,13 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.api.instrumenter; +package io.opentelemetry.instrumentation.api.incubator.instrumenter; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import java.util.List; -/** A filter that determines whether a span should be started for the given operation. */ +/** + * A filter that determines whether a span should be started for the given operation. + * + *

    This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@FunctionalInterface public interface ShouldStartFilter { /** diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java index 254e10e2900e..893d6b138536 100644 --- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java +++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/instrumenter/internal/InstrumenterCustomizerImpl.java @@ -6,13 +6,14 @@ package io.opentelemetry.instrumentation.api.incubator.instrumenter.internal; import io.opentelemetry.instrumentation.api.incubator.instrumenter.InstrumenterCustomizer; +import io.opentelemetry.instrumentation.api.incubator.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import io.opentelemetry.instrumentation.api.internal.InternalInstrumenterCustomizer; +import io.opentelemetry.instrumentation.api.internal.InternalShouldStartFilter; import io.opentelemetry.instrumentation.api.internal.SpanKey; import java.util.HashMap; import java.util.Map; @@ -84,7 +85,11 @@ public InstrumenterCustomizer addContextCustomizer(ContextCustomizer customiz @Override public InstrumenterCustomizer addShouldStartFilter(ShouldStartFilter filter) { - customizer.addShouldStartFilter(filter); + InternalShouldStartFilter internalFilter = + (parentContext, request, spanKind, instrumentationName) -> + ((ShouldStartFilter) filter) + .shouldStart(parentContext, request, spanKind, instrumentationName); + customizer.addShouldStartFilter(internalFilter); return this; } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java index dd943d73681f..a608d67b0745 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/Instrumenter.java @@ -16,6 +16,7 @@ import io.opentelemetry.instrumentation.api.internal.InstrumenterAccess; import io.opentelemetry.instrumentation.api.internal.InstrumenterContext; import io.opentelemetry.instrumentation.api.internal.InstrumenterUtil; +import io.opentelemetry.instrumentation.api.internal.InternalShouldStartFilter; import io.opentelemetry.instrumentation.api.internal.SupportabilityMetrics; import java.time.Instant; import java.util.List; @@ -85,7 +86,7 @@ public static InstrumenterBuilder builder private final boolean propagateOperationListenersToOnEnd; private final boolean enabled; private final SpanSuppressor spanSuppressor; - private final ShouldStartFilter shouldStartFilter; + private final InternalShouldStartFilter shouldStartFilter; // to allow converting generic lists to arrays with toArray @SuppressWarnings({"rawtypes", "unchecked"}) @@ -106,8 +107,8 @@ public static InstrumenterBuilder builder this.enabled = builder.enabled; this.spanSuppressor = builder.buildSpanSuppressor(); this.shouldStartFilter = - ShouldStartFilter.allOf( - (List>) (List) builder.shouldStartFilters); + InternalShouldStartFilter.allOf( + (List>) (List) builder.shouldStartFilters); } /** diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java index b63d2a66f6df..2224162b3456 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java @@ -27,6 +27,7 @@ import io.opentelemetry.instrumentation.api.internal.InternalInstrumenterCustomizer; import io.opentelemetry.instrumentation.api.internal.InternalInstrumenterCustomizerProvider; import io.opentelemetry.instrumentation.api.internal.InternalInstrumenterCustomizerUtil; +import io.opentelemetry.instrumentation.api.internal.InternalShouldStartFilter; import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; @@ -64,7 +65,7 @@ public final class InstrumenterBuilder { final List> operationListenerAttributesExtractors = new ArrayList<>(); final List> contextCustomizers = new ArrayList<>(); - final List> shouldStartFilters = new ArrayList<>(); + final List> shouldStartFilters = new ArrayList<>(); private final List operationListeners = new ArrayList<>(); private final List operationMetrics = new ArrayList<>(); @@ -189,12 +190,12 @@ public InstrumenterBuilder addOperationMetrics(OperationMetri } /** - * Adds a {@link ShouldStartFilter} that will be used to determine whether a span should be - * started for the given operation. The filter is called before any span creation logic. + * Adds a {@link InternalShouldStartFilter} that will be used to determine whether a span should + * be started for the given operation. The filter is called before any span creation logic. */ @CanIgnoreReturnValue public InstrumenterBuilder addShouldStartFilter( - ShouldStartFilter filter) { + InternalShouldStartFilter filter) { shouldStartFilters.add(requireNonNull(filter, "shouldStartFilter")); return this; } @@ -449,7 +450,7 @@ public void addContextCustomizer(ContextCustomizer customizer) { } @Override - public void addShouldStartFilter(ShouldStartFilter filter) { + public void addShouldStartFilter(InternalShouldStartFilter filter) { builder.addShouldStartFilter(filter); } diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java index 901368a5b170..4050c20cfd6c 100644 --- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalInstrumenterCustomizer.java @@ -8,7 +8,6 @@ import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; import io.opentelemetry.instrumentation.api.instrumenter.ContextCustomizer; import io.opentelemetry.instrumentation.api.instrumenter.OperationMetrics; -import io.opentelemetry.instrumentation.api.instrumenter.ShouldStartFilter; import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; import java.util.function.UnaryOperator; @@ -32,7 +31,7 @@ void addAttributesExtractors( void addContextCustomizer(ContextCustomizer customizer); - void addShouldStartFilter(ShouldStartFilter filter); + void addShouldStartFilter(InternalShouldStartFilter filter); void setSpanNameExtractor( UnaryOperator> spanNameExtractorTransformer); diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalShouldStartFilter.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalShouldStartFilter.java new file mode 100644 index 000000000000..6d8ee933a24a --- /dev/null +++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/InternalShouldStartFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.api.internal; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import java.util.List; + +/** + * Internal interface for filtering spans before they are started. This is the stable internal API + * that corresponds to the incubator {@code ShouldStartFilter}. + * + *

    This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +@FunctionalInterface +public interface InternalShouldStartFilter { + + /** + * Determines whether a span should be started for the given operation. + * + * @param parentContext the parent context of the operation + * @param request the request object of the operation + * @param spanKind the span kind that would be created + * @param instrumentationName the name of the instrumentation + * @return {@code true} if the span should be started, {@code false} otherwise + */ + boolean shouldStart( + Context parentContext, REQUEST request, SpanKind spanKind, String instrumentationName); + + /** + * Returns the priority of this filter. Filters with lower numbers have higher priority and are + * executed first. + * + * @return the priority of this filter, defaults to 0 + */ + default int getPriority() { + return 0; + } + + /** + * Returns a filter that always allows spans to be started. + * + * @return a pass-through filter + */ + static InternalShouldStartFilter none() { + return (parentContext, request, spanKind, instrumentationName) -> true; + } + + /** Combines multiple filters into a single composite filter. */ + static InternalShouldStartFilter allOf( + List> filters) { + if (filters.isEmpty()) { + return none(); + } + if (filters.size() == 1) { + return filters.get(0); + } + + List> sortedFilters = + filters.stream() + .sorted((f1, f2) -> Integer.compare(f1.getPriority(), f2.getPriority())) + .collect(java.util.stream.Collectors.toList()); + + return (parentContext, request, spanKind, instrumentationName) -> { + for (InternalShouldStartFilter filter : sortedFilters) { + if (!filter.shouldStart(parentContext, request, spanKind, instrumentationName)) { + return false; + } + } + return true; + }; + } +} diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java index d0fd0258fc5d..23c29026c02b 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterTest.java @@ -26,6 +26,7 @@ import io.opentelemetry.context.ContextKey; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.instrumentation.api.internal.Experimental; +import io.opentelemetry.instrumentation.api.internal.InternalShouldStartFilter; import io.opentelemetry.instrumentation.api.internal.SchemaUrlProvider; import io.opentelemetry.instrumentation.api.internal.SpanKey; import io.opentelemetry.instrumentation.api.internal.SpanKeyProvider; @@ -621,7 +622,7 @@ void shouldDisableInstrumenter() { @Test void shouldStartFilter() { - ShouldStartFilter filter = + InternalShouldStartFilter filter = (context, request, spanKind, instrumentationName) -> !request.equals("blocked"); Instrumenter instrumenter = diff --git a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/ShouldStartFilterTest.java similarity index 87% rename from instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java rename to instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/ShouldStartFilterTest.java index 3574d8d10592..500958a19f5a 100644 --- a/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/instrumenter/ShouldStartFilterTest.java +++ b/instrumentation-api/src/test/java/io/opentelemetry/instrumentation/api/internal/ShouldStartFilterTest.java @@ -3,13 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.instrumentation.api.instrumenter; +package io.opentelemetry.instrumentation.api.internal; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; -import java.util.Arrays; +import io.opentelemetry.instrumentation.api.incubator.instrumenter.ShouldStartFilter; import org.junit.jupiter.api.Test; class ShouldStartFilterTest { @@ -29,8 +30,7 @@ void allOf() { ShouldStartFilter filter2 = (context, request, spanKind, instrumentationName) -> !request.equals("blocked2"); - ShouldStartFilter compositeFilter = - ShouldStartFilter.allOf(Arrays.asList(filter1, filter2)); + ShouldStartFilter compositeFilter = ShouldStartFilter.allOf(asList(filter1, filter2)); assertThat(compositeFilter.shouldStart(Context.root(), "allowed", SpanKind.CLIENT, "test")) .isTrue(); @@ -81,7 +81,7 @@ public int getPriority() { }; ShouldStartFilter compositeFilter = - ShouldStartFilter.allOf(Arrays.asList(lowPriority, highPriority)); + ShouldStartFilter.allOf(asList(lowPriority, highPriority)); boolean result = compositeFilter.shouldStart(Context.root(), "request", SpanKind.CLIENT, "test");