open-telemetry / open-telemetry/opentelemetry-java-instrumentation

Add extension for flexible span filtering before span creation

Open
#15,477 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement needs triage
Dominant language
Java
Stars
2.6k
Forks
1.2k
Avg merge
2d 18h
Merged PRs (30d)
228

Description

Is your feature request related to a problem? Please describe.

Currently, opentelemetry-java-instrumentation lacks readily available extension points for filtering Spat creation actions before they are actually created.
For example, if my distro contains background threads, I don't want to create corresponding Spats for these threads, I'd like to filter Spat creation based on thread information.

Additionally, we'd like to set up some rate-limiting logic before Spat creation to control its creation.

Describe the solution you'd like

Add a ShouldStartFilter extension point that allows filtering before span creation:

@FunctionalInterface
public interface ShouldStartFilter<REQUEST> {
  /**
   * Determines whether a span should be started for the given operation.
   */
  boolean shouldStart(Context parentContext, REQUEST request, SpanKind spanKind, String instrumentationName);
  
  /**
   * Filter priority (lower numbers = higher priority)
   */
  default int getPriority() { return 0; }
  
  /**
   * Combines multiple filters
   */
  static <REQUEST> ShouldStartFilter<REQUEST> allOf(List<ShouldStartFilter<REQUEST>> filters) {
    // Implementation that executes filters in priority order
  }
}
Integration with InstrumenterBuilder
public class InstrumenterBuilder<REQUEST, RESPONSE> {
  private final List<ShouldStartFilter<REQUEST>> shouldStartFilters = new ArrayList<>();
  
  public InstrumenterBuilder<REQUEST, RESPONSE> addShouldStartFilter(ShouldStartFilter<REQUEST> filter) {
    shouldStartFilters.add(filter);
    return this;
  }
  
  // Build the composite filter during instrumenter construction
  private ShouldStartFilter<REQUEST> buildShouldStartFilter() {
    if (shouldStartFilters.isEmpty()) {
      return ShouldStartFilter.none(); // Pass-through filter
    }
    return ShouldStartFilter.allOf(shouldStartFilters);
  }
  
  // Modified build methods to include the filter
  public Instrumenter<REQUEST, RESPONSE> buildInstrumenter() {
    return new Instrumenter<>(
        // ... existing parameters ...
        buildShouldStartFilter() // Pass the composite filter
    );
  }
}
Integration with InstrumenterCustomizer
public interface InstrumenterCustomizer {
  // Existing methods...
  
  InstrumenterCustomizer addShouldStartFilter(ShouldStartFilter<?> filter);
}
Core Integration: Modified Instrumenter Class

The key integration point is in the Instrumenter.shouldStart() method:

public class Instrumenter<REQUEST, RESPONSE> {
  private final ShouldStartFilter<REQUEST> shouldStartFilter;
  // ... other existing fields ...
  
  // Constructor receives the filter from InstrumenterBuilder
  Instrumenter(
      // ... existing parameters ...
      ShouldStartFilter<REQUEST> shouldStartFilter) {
    // ... existing initialization ...
    this.shouldStartFilter = shouldStartFilter;
  }
  
  /**
   * Modified shouldStart method that applies filters before existing logic
   */
  public boolean shouldStart(Context parentContext, REQUEST request) {
    // 1. Check if instrumenter is enabled (existing logic)
    if (!enabled) {
      return false;
    }
    
    // 2. Extract span kind (needed for both filters and suppressors)
    SpanKind spanKind = spanKindExtractor.extract(request);
    
    // 3. NEW: Apply ShouldStartFilter before any span creation logic
    if (!shouldStartFilter.shouldStart(parentContext, request, spanKind, instrumentationName)) {
      // Filter rejected - record metrics and return false immediately
      supportability.recordFilteredSpan(spanKind, instrumentationName, "should_start_filter");
      return false;
    }
    
    // 4. Apply existing SpanSuppressor logic (unchanged)
    boolean suppressed = spanSuppressor.shouldSuppress(parentContext, spanKind);
    if (suppressed) {
      supportability.recordSuppressedSpan(spanKind, instrumentationName);
    }
    
    return !suppressed;
  }
}
Describe alternatives you've considered

No response

Additional context

No response

Tip

React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read InstrumenterBuilder and InstrumenterCustomizer first, then follow Instrumenter.shouldStart(), the named integration point for applying filtering before existing suppression logic. Done means defining the ShouldStartFilter extension, composing filters by priority, wiring it through the builder and customizer, and applying it before span creation.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.