如何通过 OpenTelemetry 过滤指定 Span
OpenTelemetry Samplers(采样器)用于控制哪些链路数据会被采集和发送,因此可以使用 Samplers 来过滤指定的Span。本文介绍如何使用 OpenTelemetry Samplers 过滤 Java 应用或者 Node.js 应用中的指定 Span。
Java
方法一:无代码侵入方式(基于 Java Agent Extension 实现)
该方法需要对 OpenTelemetry Java Agent 的功能进行扩展,在扩展程序中编写自定义的 OpenTelemetry Sampler,并在 Sampler 中定义 Span 过滤规则。最后在应用启动时同时加载 OpenTelemetry Java Agent 和扩展,实现无侵入的 Span 过滤。
新建 Java Agent 扩展项目
请在 pom.xml 中添加以下依赖,并替换 <font style="color:rgb(24, 24, 24);">${OPENTELEMETRY_VERSION}</font>
,确保与您使用的OpenTelemetry Java Agent版本一致。
<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>${OPENTELEMETRY_VERSION}</version> <scope>compile</scope></dependency>
<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk-trace</artifactId> <version>${OPENTELEMETRY_VERSION}</version></dependency>
<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId> <version>${OPENTELEMETRY_VERSION}</version></dependency>
<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-semconv</artifactId> <version>${OPENTELEMETRY_VERSION}-alpha</version></dependency>
在Java Agent 扩展项目中实现采样处理类
- 新建 SpanFilterSampler 类文件(类名可自定义)
SpanFilterSampler 类需要实现io.opentelemetry.sdk.trace.samplers.Sampler
接口,并实现 shouldSample
方法和 getDescription
方法。
- shouldSample 方法:可以在方法中自定义过滤条件。对于要过滤的 Span,返回
SamplingResult.create(SamplingDecision.DROP)
,需要保留并继续上报的 Span 则返回SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)
。 - getDescription 方法:返回自定义 Sampler 的名称。
- 示例代码
在下面示例代码中,SpanFilterSampler 会过滤名称为“spanName1”或“spanName2”的Span,以及 attributes.http.target 为“/api/checkHealth”或““/health/checks””的所有 Span。
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 { /* * 过滤 Span名称 在 EXCLUDED_SPAN_NAMES 中的所有 Span */ private static List<String> EXCLUDED_SPAN_NAMES = Collections.unmodifiableList( Arrays.asList("spanName1", "spanName2") );
/* * 过滤 attributes.http.target 在 EXCLUDED_HTTP_REQUEST_TARGETS 中的所有 Span */ 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)) { // 根据条件进行过滤 return SamplingResult.create(SamplingDecision.DROP); } else { return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); } }
@Override public String getDescription() { return "SpanFilterSampler"; // SpanFilterSampler可以替换为自定义的名称 }}
实现采样提供者类
- 新建 SpanFilterSamplerProvider 类文件(类名可自定义)
SpanFilterSamplerProvider 类需要实现<font style="color:rgba(0, 0, 0, 0.85);">io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider</font>
接口,实现 createSampler 方法和 getName 方法。
- createSampler 方法:创建并返回您自定义的 Sampler 实例。
- getName 方法:获取自定义的 Sampler 名称,OpenTelemetry Java Agent 通过该名称找到这个 Sampler。
- 示例代码
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"; // SpanFilterSampler可以替换为自定义的名称 }}
构建
将程序打包成 JAR 包,构建后存储在 target 目录下:
mvn clean pacakage
启动应用时加载 OpenTelemetry Java Agent 扩展
有两种加载方式:
- VM 参数方式加载
- 将
${your-sampler-name}
替换为您自定义的 Sampler 名称,也就是 getName 方法的返回值。
-Dotel.traces.sampler=${your-sampler-name}
- 示例
java -javaagent:path/to/opentelemetry-javaagent.jar \ -Dotel.javaagent.extensions=path/to/opentelemetry-java-agent-extension.jar \ -Dotel.traces.sampler=${your-sampler-name} \ -Dotel.exporter.otlp.endpoint=${endpoint} \ -jar yourapp.jar
- 环境变量方式加载
- 将
${your-sampler-name}
替换为您自定义的 Sampler 名称,也就是 getName 方法的返回值。
export OTEL_TRACES_SAMPLER="${your-sampler-name}"
- 示例
export OTEL_JAVAAGENT_EXTENSIONS="path/to/opentelemetry-java-agent-extension.jar"export OTEL_TRACES_SAMPLER="${your-sampler-name}"export OTEL_EXPORTER_OTLP_ENDPOINT="${endpoint}"
java -javaagent:path/to/opentelemetry-javaagent.jar \ -jar yourapp.jar
方法二:使用 OpenTelemetry Java SDK
前提条件
已经使用 OpenTelemetry Java SDK 为应用程序手动埋点。
在业务代码中创建采样处理类
- SpanFilterSampler 类需要实现
io.opentelemetry.sdk.trace.samplers.Sampler
接口,并实现shouldSample
方法和getDescription
方法。
- shouldSample 方法:可以在方法中自定义过滤条件。对于要过滤的 Span,返回
SamplingResult.create(SamplingDecision.DROP)
,需要保留并继续上报的 Span 则返回SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE)
。 - getDescription 方法:返回自定义 Sampler 的名称。
- 示例代码
在下面示例代码中,SpanFilterSampler 会过滤名称为“spanName1”或“spanName2”的Span,以及 attributes.http.target 为“/api/checkHealth”或““/health/checks””的所有 Span。
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 { /* * 过滤 Span名称 在 EXCLUDED_SPAN_NAMES 中的所有 Span */ private static List<String> EXCLUDED_SPAN_NAMES = Collections.unmodifiableList( Arrays.asList("spanName1", "spanName2") );
/* * 过滤 attributes.http.target 在 EXCLUDED_HTTP_REQUEST_TARGETS 中的所有 Span */ 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)) { // 根据条件进行过滤 return SamplingResult.create(SamplingDecision.DROP); } else { return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); } }
@Override public String getDescription() { return "SpanFilterSampler"; // SpanFilterSampler可以替换为自定义的名称 }}
在创建 SdkTracerProvider 实例时设置自定义 Sampler
在创建 SdkTracerProvider 实例时,调用 setSampler(new SpanFilterSampler())
,即可完成配置。
...
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder() .setSampler(new MySampler()) // 添加这一行 .addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder() .setEndpoint("${endpoint}") .build()).build()) .setResource(resource) .build();...
启动应用
Node.js
实现自定义 Sampler 类
创建一个实现了Sampler接口的自定义Sampler类。该接口定义了是否采样的规则。例如:
const opentelemetry = require('@opentelemetry/api');
class SpanFilterSampler { shouldSample(spanContext, parentContext) { // 在此处实现您的自定义采样逻辑 }}
创建 NodeTracerProvider 实例时设置自定义 Sampler
...const provider = new NodeTracerProvider({ sampler: new SpanFilterSampler(), // 添加这一行代码,设置自定义 Sampler resource: new Resource({ [SemanticResourceAttributes.HOST_NAME]: require("os").hostname(), // your host name [SemanticResourceAttributes.SERVICE_NAME]: "${your-service-name}", }),});...