You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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级过滤:

  1. 自定义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")
    }
}
  1. 注册为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:

  1. 自定义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
}
  1. 注册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()
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 04:02:02