Spring Boot 3.1.0自定义OpenTelemetry Sampler无法获取请求URL
问题
我在Spring Boot(MVC Web服务)中使用Micrometer/OTLP,追踪信息通过Jaeger可视化。TraceId和SpanId已生成并添加至MDC,Jaeger中可正常查看包含应用名称与REST方法信息的所有span。
但尝试通过自定义io.opentelemetry.sdk.trace.samplers.Sampler实现排除部分span(如健康检查)时遇到问题:调用REST方法时,调试进入shouldSample方法,但name显示为<unspecified name>,attributes为空,无法通过URL或请求路径判断是否要采样。
环境版本
- Spring Boot:3.1.0
- micrometer-tracing-bom:1.1.2
依赖配置(pom.xml片段)
<dependencyManagement> <dependencies> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bom</artifactId> <version>1.1.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-otel</artifactId> </dependency> <dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-exporter-otlp</artifactId> </dependency>
配置文件(application.yaml片段)
management: tracing: enabled: true sampling: probability: 1.0 otlp: tracing: endpoint: http://localhost:14318/v1/traces
自定义Sampler代码(Kotlin)
class CustomOtlpSampler : Sampler { override fun shouldSample( parentContext: Context, traceId: String, name: String, spanKind: SpanKind, attributes: Attributes, parentLinks: List<LinkData> ): SamplingResult { if (name.contains("health") || name.contains("apidoc")) { return SamplingResult.create(SamplingDecision.DROP) } return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); } override fun getDescription(): String = "CustomSampler" }
解决方案
核心原因
采样决策是在span初始化的最早阶段执行的,此时Spring MVC还未将请求上下文绑定到span,所以默认情况下name和attributes为空,无法直接获取URL信息。
方案1:使用OpenTelemetry View过滤(推荐)
Sampler负责trace级别的采样,而排除特定端点的span更适合用OpenTelemetry的View实现span级过滤:
- 自定义View实现:
import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.sdk.trace.view.SpanFilter import io.opentelemetry.sdk.trace.view.View class ExcludeHealthCheckView : View { override fun getName() = "exclude-health-check" override fun getSpanFilter() = SpanFilter { spanData: SpanData -> // 排除健康检查和apidoc相关的span val spanName = spanData.name !spanName.contains("health") && !spanName.contains("apidoc") } }
- 注册为Spring Bean:
import io.opentelemetry.sdk.trace.view.View import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class OtelConfig { @Bean fun excludeHealthCheckView(): View = ExcludeHealthCheckView() }
方案2:通过SpanProcessor提前注入请求属性(适合必须用Sampler的场景)
如果一定要在Sampler中做决策,需要通过SpanProcessor在采样前将请求属性注入span:
- 自定义SpanProcessor拦截span创建,填充请求URL:
import io.opentelemetry.context.Context import io.opentelemetry.sdk.trace.ReadWriteSpan import io.opentelemetry.sdk.trace.SpanProcessor import jakarta.servlet.http.HttpServletRequest import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.context.request.ServletRequestAttributes class RequestAttributesSpanProcessor : SpanProcessor { override fun onStart(parentContext: Context, span: ReadWriteSpan) { val requestAttributes = RequestContextHolder.getRequestAttributes() as? ServletRequestAttributes requestAttributes?.request?.let { request: HttpServletRequest -> // 注入请求URL属性 span.setAttribute("http.url", request.requestURL.toString()) // 提前设置span名称 span.updateName("${request.method} ${request.servletPath}") } } override fun onEnd(span: ReadWriteSpan) {} override fun isStartRequired() = true override fun isEndRequired() = false }
- 注册SpanProcessor和自定义Sampler:
import io.opentelemetry.sdk.trace.SdkTracerProvider import io.opentelemetry.sdk.trace.SpanProcessor import io.opentelemetry.sdk.trace.samplers.Sampler import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class OtelConfig { @Bean fun customSampler(): Sampler = CustomOtlpSampler() @Bean fun requestAttributesSpanProcessor(): SpanProcessor = RequestAttributesSpanProcessor() @Bean fun sdkTracerProvider( sampler: Sampler, spanProcessor: SpanProcessor ): SdkTracerProvider { return SdkTracerProvider.builder() .setSampler(sampler) .addSpanProcessor(spanProcessor) .build() } }
- 修改Sampler从attributes中获取URL:
class CustomOtlpSampler : Sampler { override fun shouldSample( parentContext: Context, traceId: String, name: String, spanKind: SpanKind, attributes: Attributes, parentLinks: List<LinkData> ): SamplingResult { val url = attributes["http.url"] as? String ?: "" if (url.contains("health") || url.contains("apidoc") || name.contains("health") || name.contains("apidoc")) { return SamplingResult.create(SamplingDecision.DROP) } return SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE); } override fun getDescription(): String = "CustomSampler" }
内容的提问来源于stack exchange,提问作者Clemens
相关产品推荐
相关产品推荐

