如何在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
相关产品推荐
相关产品推荐

