Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
*** 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<REQUEST,RESPONSE> addShouldStartFilter(io.opentelemetry.instrumentation.api.internal.InternalShouldStartFilter<? super REQUEST>)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this extension, I added new method addShouldStartFilter in InstrumenterBuilder. Do you have any comment or guidance about this point? @laurit

7 changes: 7 additions & 0 deletions examples/distro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
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;
Expand All @@ -32,6 +34,7 @@
* <li>Custom metrics for HTTP operations
* <li>Request correlation IDs via context customization
* <li>Custom span name transformation
* <li>Filter out spans before creation using ShouldStartFilter
* </ul>
*
* <p>The customizer will be automatically applied to instrumenters that match the specified
Expand All @@ -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);
Expand Down Expand Up @@ -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<Object> {
@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");
}
}
}
7 changes: 7 additions & 0 deletions examples/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
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;
Expand All @@ -34,6 +36,7 @@
* <li>Custom metrics for HTTP operations
* <li>Request correlation IDs via context customization
* <li>Custom span name transformation
* <li>Filter out spans before creation using ShouldStartFilter
* </ul>
*
* <p>The customizer will be automatically applied to instrumenters that match the specified
Expand All @@ -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);
Expand Down Expand Up @@ -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<Object> {
@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");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

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.
*
* <p>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<REQUEST> {

/**
* 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 <REQUEST> ShouldStartFilter<REQUEST> none() {
return (parentContext, request, spanKind, instrumentationName) -> true;
}

/** Combines multiple filters into a single composite filter. */
static <REQUEST> ShouldStartFilter<REQUEST> allOf(List<ShouldStartFilter<REQUEST>> filters) {
if (filters.isEmpty()) {
return none();
}
if (filters.size() == 1) {
return filters.get(0);
}

List<ShouldStartFilter<REQUEST>> 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<REQUEST> filter : sortedFilters) {
if (!filter.shouldStart(parentContext, request, spanKind, instrumentationName)) {
return false;
}
}
return true;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +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.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;
Expand Down Expand Up @@ -81,6 +83,16 @@ public InstrumenterCustomizer addContextCustomizer(ContextCustomizer<?> customiz
return this;
}

@Override
public InstrumenterCustomizer addShouldStartFilter(ShouldStartFilter<?> filter) {
InternalShouldStartFilter<Object> internalFilter =
(parentContext, request, spanKind, instrumentationName) ->
((ShouldStartFilter<Object>) filter)
.shouldStart(parentContext, request, spanKind, instrumentationName);
customizer.addShouldStartFilter(internalFilter);
return this;
}

@Override
@SuppressWarnings("FunctionalInterfaceClash") // interface has deprecated overload
public InstrumenterCustomizer setSpanNameExtractor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
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;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -84,6 +86,7 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
private final boolean propagateOperationListenersToOnEnd;
private final boolean enabled;
private final SpanSuppressor spanSuppressor;
private final InternalShouldStartFilter<? super REQUEST> shouldStartFilter;

// to allow converting generic lists to arrays with toArray
@SuppressWarnings({"rawtypes", "unchecked"})
Expand All @@ -103,6 +106,9 @@ public static <REQUEST, RESPONSE> InstrumenterBuilder<REQUEST, RESPONSE> builder
this.propagateOperationListenersToOnEnd = builder.propagateOperationListenersToOnEnd;
this.enabled = builder.enabled;
this.spanSuppressor = builder.buildSpanSuppressor();
this.shouldStartFilter =
InternalShouldStartFilter.allOf(
(List<InternalShouldStartFilter<Object>>) (List<?>) builder.shouldStartFilters);
}

/**
Expand All @@ -120,6 +126,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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,6 +65,7 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {
final List<AttributesExtractor<? super REQUEST, ? super RESPONSE>>
operationListenerAttributesExtractors = new ArrayList<>();
final List<ContextCustomizer<? super REQUEST>> contextCustomizers = new ArrayList<>();
final List<InternalShouldStartFilter<? super REQUEST>> shouldStartFilters = new ArrayList<>();
private final List<OperationListener> operationListeners = new ArrayList<>();
private final List<OperationMetrics> operationMetrics = new ArrayList<>();

Expand Down Expand Up @@ -187,6 +189,17 @@ public InstrumenterBuilder<REQUEST, RESPONSE> addOperationMetrics(OperationMetri
return this;
}

/**
* 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<REQUEST, RESPONSE> addShouldStartFilter(
InternalShouldStartFilter<? super REQUEST> 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.
Expand Down Expand Up @@ -436,6 +449,11 @@ public void addContextCustomizer(ContextCustomizer<REQUEST> customizer) {
builder.addContextCustomizer(customizer);
}

@Override
public void addShouldStartFilter(InternalShouldStartFilter<? super REQUEST> filter) {
builder.addShouldStartFilter(filter);
}

@Override
public void setSpanNameExtractor(
UnaryOperator<SpanNameExtractor<? super REQUEST>> spanNameExtractorTransformer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ void addAttributesExtractors(

void addContextCustomizer(ContextCustomizer<REQUEST> customizer);

void addShouldStartFilter(InternalShouldStartFilter<? super REQUEST> filter);

void setSpanNameExtractor(
UnaryOperator<SpanNameExtractor<? super REQUEST>> spanNameExtractorTransformer);

Expand Down
Loading
Loading