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

如何在Vertx中为头部采样添加自定义OpenTelemetry Span属性

优化Vertx中OpenTelemetry Span头部采样的自定义属性注入方案

要给Span属性用于头部采样,必须在startSpan()前完成设置,但Vertx底层创建Span时,属性注入受固定TagExtractor实现的限制。目前临时方案是自定义OpenTelemetryOptions和OpenTelemetryTracer插入自定义TagExtractor,但这只是权宜之计,而且Span.current()满足不了需求——不仅因为头部采样的时机要求,还因为自定义属性只能在VertxsendRequest之前获取。

下面是两种更优的解决思路:

方案1:基于Vertx Tracing接口做装饰器扩展(合规性更强)

不用完全重写OpenTelemetryTracer,而是通过装饰器模式包装原有Tracer,在sendRequest环节替换或合并TagExtractor,既符合Vertx的扩展规范,又能灵活注入自定义属性。

代码示例:自定义Tracer装饰器

public class CustomOpenTelemetryTracer implements Tracer {
    private final OpenTelemetryTracer delegate;
    private final TagExtractor customTagExtractor;

    public CustomOpenTelemetryTracer(OpenTelemetryTracer delegate, TagExtractor customTagExtractor) {
        this.delegate = delegate;
        this.customTagExtractor = customTagExtractor;
    }

    @Override
    public <R> Span sendRequest(Context context, SpanKind kind, TracingPolicy policy, R request, String operation, BiConsumer<String, String> headers, TagExtractor<R> tagExtractor) {
        // 合并原有TagExtractor和自定义的,保留原有标签同时添加自定义属性
        TagExtractor<R> mergedExtractor = mergeTagExtractors(tagExtractor, customTagExtractor);
        return delegate.sendRequest(context, kind, policy, request, operation, headers, mergedExtractor);
    }

    // 实现标签合并逻辑
    private <R> TagExtractor<R> mergeTagExtractors(TagExtractor<R> original, TagExtractor<R> custom) {
        return new TagExtractor<>() {
            @Override
            public int len(R req) {
                return original.len(req) + custom.len(req);
            }

            @Override
            public String name(R req, int index) {
                if (index < original.len(req)) {
                    return original.name(req, index);
                }
                return custom.name(req, index - original.len(req));
            }

            @Override
            public String value(R req, int index) {
                if (index < original.len(req)) {
                    return original.value(req, index);
                }
                return custom.value(req, index - original.len(req));
            }
        };
    }

    // 其他Tracer方法直接委托给原有实现
    @Override
    public Span createSpan(Context context, String operationName, TracingPolicy policy) {
        return delegate.createSpan(context, operationName, policy);
    }

    // 补充实现Tracer接口的其他方法...
}

初始化Vertx时配置自定义Tracer

// 初始化OpenTelemetry SDK
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
        // 按需配置你的OTel参数
        .buildAndRegisterGlobal();

// 创建原有OpenTelemetryTracer实例
OpenTelemetryOptions originalOpts = new OpenTelemetryOptions().setOpenTelemetry(openTelemetry);
OpenTelemetryTracer originalTracer = new OpenTelemetryTracer(vertx, originalOpts);

// 你的自定义TagExtractor实现
TagExtractor<HttpRequestHead> customTagExtractor = new TagExtractor<>() {
    @Override
    public int len(HttpRequestHead req) {
        return 1;
    }

    @Override
    public String name(HttpRequestHead req, int index) {
        return index == 0 ? "filter" : throw new IndexOutOfBoundsException();
    }

    @Override
    public String value(HttpRequestHead req, int index) {
        return index == 0 ? req.headers().get("filter") : throw new IndexOutOfBoundsException();
    }
};

// 包装成自定义Tracer并配置给Vertx
CustomOpenTelemetryTracer customTracer = new CustomOpenTelemetryTracer(originalTracer, customTagExtractor);
Vertx vertx = Vertx.vertx(new VertxOptions().setTracer(customTracer));

方案2:直接自定义OpenTelemetry Sampler(从根源解决采样需求)

如果核心目标是基于自定义属性做头部采样,跳过Vertx的TagExtractor限制,直接在OpenTelemetry的Sampler里实现逻辑更高效。

代码示例:自定义采样器

public class CustomAttributeSampler implements Sampler {
    private final Sampler delegate;

    public CustomAttributeSampler(Sampler delegate) {
        this.delegate = delegate;
    }

    @Override
    public SamplingResult shouldSample(Context parentContext, String traceId, String name, SpanKind spanKind, Attributes attributes, List<Link> links) {
        // 从OTel Context中取出Vertx请求对象
        HttpRequestHead request = parentContext.get(HttpRequestHead.class);
        if (request != null) {
            String filterVal = request.headers().get("filter");
            // 根据自定义属性值做采样决策,比如仅当filter为"special"时采样
            if ("special".equals(filterVal)) {
                return SamplingResult.recordAndSample();
            }
        }
        // 其他情况使用默认采样规则
        return delegate.shouldSample(parentContext, traceId, name, spanKind, attributes, links);
    }

    @Override
    public String getDescription() {
        return "CustomAttributeSampler[" + delegate.getDescription() + "]";
    }
}

配置Sampler并传递请求上下文

// 初始化OTel时配置自定义采样器
OpenTelemetrySdk openTelemetry = OpenTelemetrySdk.builder()
        .setTracerProvider(TracerProviderSdk.builder()
                .setSampler(new CustomAttributeSampler(Samplers.alwaysOn()))
                .build())
        .buildAndRegisterGlobal();

// 在sendRequest前,把请求对象放入OTel Context
HttpRequestHead request = ...;
Context otelCtx = Context.current().with(HttpRequestHead.class, request);
Context.current().with(otelCtx);

方案对比

  • 方案1适合需要同时添加自定义Span标签+满足采样时机的场景,完全贴合Vertx扩展机制,对原有代码侵入性低。
  • 方案2更适合核心需求是基于自定义属性做采样的场景,绕过Vertx的TagExtractor限制,采样逻辑更直接高效。

内容的提问来源于stack exchange,提问作者tboon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:27:10