Filter out health checks, internal probes, or other noise spans from Java and Node.js applications by writing a custom sampler with Managed Service for OpenTelemetry.
Java application
Choose a method based on how your application reports data:
|
Scenario |
Method |
|
Using OpenTelemetry Java Agent (auto-instrumentation) — no code changes wanted |
Method 1: Create an extension for Managed Service for OpenTelemetry Java Agent |
|
Using Managed Service for OpenTelemetry SDK for Java (manual instrumentation) |
Method 2: Use Managed Service for OpenTelemetry SDK for Java |
Both methods work by implementing a custom Sampler with shouldSample(). Return SamplingDecision.DROP to discard a span, or SamplingDecision.RECORD_AND_SAMPLE to keep and export it.
Method 1: Create an extension for Managed Service for OpenTelemetry Java Agent
Prerequisites
The application is automatically instrumented using OpenTelemetry Java Agent. For more information, see Report trace data from Java applications by using OpenTelemetry.
-
Create a Maven project.
This project builds the extension JAR that the agent loads at startup.
-
Add the following dependencies to
pom.xml.ImportantUse the same version for all OpenTelemetry dependencies. The version must match the OpenTelemetry Java Agent version you are running.
<dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>io.opentelemetry.javaagent</groupId> <artifactId>opentelemetry-javaagent</artifactId> <version>1.28.0</version> <!-- Set the value of the scope field to compile. --> <scope>compile</scope> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk-trace</artifactId> <version>1.28.0</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId> <version>1.28.0</version> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-semconv</artifactId> <version>1.28.0-alpha</version> </dependency> -
Create the
SpanFilterSamplerclass.The class implements
io.opentelemetry.sdk.trace.samplers.Samplerand must provide two methods:-
shouldSample
Defines the filter rules. Return
SamplingResult.create(SamplingDecision.DROP)to discard a span, orSamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)to keep it.
-
getDescription
Returns the sampler name. The agent uses this name to look up the sampler.
The following example filters out spans by name (
spanName1,spanName2) and by HTTP target (/api/checkHealth,/health/checks):package org.example; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.*; public class SpanFilterSampler implements Sampler { /* * Filter out spans whose name is in EXCLUDED_SPAN_NAMES. */ private static List<String> EXCLUDED_SPAN_NAMES = Collections.unmodifiableList( Arrays.asList("spanName1", "spanName2") ); /* * Filter out spans whose attributes.http.target is in EXCLUDED_HTTP_REQUEST_TARGETS. */ private static List<String> EXCLUDED_HTTP_REQUEST_TARGETS = Collections.unmodifiableList( Arrays.asList("/api/checkHealth", "/health/checks") ); @Override public SamplingResult shouldSample(Context parentContext, String traceId, String name, SpanKind spanKind, Attributes attributes, List<LinkData> parentLinks) { String httpTarget = attributes.get(SemanticAttributes.HTTP_TARGET) != null ? attributes.get(SemanticAttributes.HTTP_TARGET) : ""; if (EXCLUDED_SPAN_NAMES.contains(name) || EXCLUDED_HTTP_REQUEST_TARGETS.contains(httpTarget)) { // Drop spans matching the filter rules. return SamplingResult.create(SamplingDecision.DROP); } else { return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); } } @Override public String getDescription() { return "SpanFilterSampler"; // Replace SpanFilterSampler with a custom name if needed. } } -
-
Create the
SpanFilterSamplerProviderclass.The class implements
io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProviderand must provide two methods:-
createSampler
Creates and returns your custom sampler instance.
-
getName
Returns the sampler name. OpenTelemetry Java Agent uses this name to find the sampler at startup.
package org.example; import com.google.auto.service.AutoService; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider; import io.opentelemetry.sdk.trace.samplers.Sampler; @AutoService(ConfigurableSamplerProvider.class) public class SpanFilterSamplerProvider implements ConfigurableSamplerProvider { @Override public Sampler createSampler(ConfigProperties configProperties) { return new SpanFilterSampler(); } @Override public String getName() { return "SpanFilterSampler"; // Replace SpanFilterSampler with a custom name if needed. } } -
-
Build the extension JAR.
mvn clean packageThe JAR is saved to the
targetdirectory. -
Load the extension when starting the application.
Set the custom sampler name (the value returned by
getName) using one of the following approaches:-
Add a JVM parameter:
-Dotel.traces.sampler=<your-sampler-name>Replace
<your-sampler-name>with the value returned by thegetNamemethod. -
Set an environment variable:
export OTEL_TRACES_SAMPLER="<your-sampler-name>"Replace
<your-sampler-name>with the value returned by thegetNamemethod.
-
Method 2: Use Managed Service for OpenTelemetry SDK for Java
Prerequisites
The application is manually instrumented using Managed Service for OpenTelemetry SDK for Java. For more information, see Report trace data from Java applications by using OpenTelemetry.
-
Create a custom sampler class in your application code.
The class implements
io.opentelemetry.sdk.trace.samplers.Samplerand must provide two methods:-
shouldSample
Defines the filter rules. Return
SamplingResult.create(SamplingDecision.DROP)to discard a span, orSamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)to keep it.
-
getDescription
Returns the sampler name.
The following example filters out spans by name (
spanName1,spanName2) and by HTTP target (/api/checkHealth,/health/checks):package org.example; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; import java.util.*; public class SpanFilterSampler implements Sampler { /* * Filter out spans whose name is in EXCLUDED_SPAN_NAMES. */ private static List<String> EXCLUDED_SPAN_NAMES = Collections.unmodifiableList( Arrays.asList("spanName1", "spanName2") ); /* * Filter out spans whose attributes.http.target is in EXCLUDED_HTTP_REQUEST_TARGETS. */ private static List<String> EXCLUDED_HTTP_REQUEST_TARGETS = Collections.unmodifiableList( Arrays.asList("/api/checkHealth", "/health/checks") ); @Override public SamplingResult shouldSample(Context parentContext, String traceId, String name, SpanKind spanKind, Attributes attributes, List<LinkData> parentLinks) { String httpTarget = attributes.get(SemanticAttributes.HTTP_TARGET) != null ? attributes.get(SemanticAttributes.HTTP_TARGET) : ""; if (EXCLUDED_SPAN_NAMES.contains(name) || EXCLUDED_HTTP_REQUEST_TARGETS.contains(httpTarget)) { // Drop spans matching the filter rules. return SamplingResult.create(SamplingDecision.DROP); } else { return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); } } @Override public String getDescription() { return "SpanFilterSampler"; // Replace SpanFilterSampler with a custom name if needed. } } -
-
Register the custom sampler when creating a
SdkTracerProviderinstance.Call
setSampler(new SpanFilterSampler())in the builder:SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .setSampler(new SpanFilterSampler()) // Add this line. .addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder() .setEndpoint("<endpoint>") .addHeader("Authentication", "<token>") .build()).build()) .setResource(resource) .build(); Start the application.
Node.js application
Demo: opentelemetry-nodejs-demo.
Prerequisites
The application is instrumented using the Managed Service for OpenTelemetry API for JavaScript. For more information, see Submit trace data from a Node.js application.
Method 1: Filter out spans when they are created
-
Configure
ignoreIncomingRequestHookwhen constructingHttpInstrumentation.ignoreIncomingRequestHookaccepts a function that runs before the request is processed. Returntrueto skip instrumentation for that request — no span is created, and the request completes normally.The following example skips instrumentation for requests to
/api/checkHealth:// Replace the original registration block: // registerInstrumentations({ // tracerProvider: provider, // instrumentations: [new HttpInstrumentation(), ExpressInstrumentation], // }); const httpInstrumentation = new HttpInstrumentation({ ignoreIncomingRequestHook: (request) => { // Skip requests to /api/checkHealth. if (request.url === '/api/checkHealth') { return true; } return false; }, }); registerInstrumentations({ tracerProvider: provider, instrumentations: [httpInstrumentation, ExpressInstrumentation], }); Start the application.
Method 2: Filter out spans when they are reported
-
Create a custom sampler class that implements the
Samplerinterface.Define your sampling logic in
shouldSample. Example:const opentelemetry = require('@opentelemetry/api'); class SpanFilterSampler { shouldSample(spanContext, parentContext) { // Implement your custom sampling logic here. } } -
Register the custom sampler when creating a
NodeTracerProviderinstance.const provider = new NodeTracerProvider({ sampler: new SpanFilterSampler(), // Add this line to set the custom sampler. resource: new Resource({ [SemanticResourceAttributes.HOST_NAME]: require("os").hostname(), [SemanticResourceAttributes.SERVICE_NAME]: "<your-service-name>", }), });