Spring Micrometer:如何在所有子Span中标记Baggage?首个观测未生效求助
问题:HTTP请求首个观测未携带Baggage中的truckId
我们正在构建一款卡车管理应用,许多请求包含truckId字段,希望该字段能标记在所有服务的相关子Span中。我们尝试在请求到达REST控制器时立即添加Baggage,并使用ObservationFilter将Baggage作为高基数值附加到每个Trace中。该方案对大部分观测有效,但接收HTTP请求时的首个观测未包含Baggage,似乎ObservationFilter未被调用。附上代码示例及Jaeger追踪图,恳请提供帮助!
客户端控制器代码
@PostMapping("/trucks/{truckId}") public ResponseEntity<Void> PostRequestTruckSelfCheck(@PathVariable("truckId") String truckId) { // 获取当前Span并添加Baggage try (BaggageInScope baggageInScope = tracer.createBaggageInScope("truckId", truckId)) { HttpStatusCode code = Observation.createNotStarted("request.selfcheck.truck", observationRegistry) .contextualName("handle-truck-selfcheck-request") .observe(() -> selfcheckService.requestTruckSelfCheck(truckId)); return new ResponseEntity<>(code); } }
观测配置代码
@Configuration public class ObservabilityConfigs { // 监控相关Bean @Bean ObservationRegistry observationRegistry(Tracer tracer) { ObservationRegistry observationRegistry = ObservationRegistry.create(); observationRegistry.observationConfig().observationFilter(new BaggageObservationFilter(tracer)); return observationRegistry; } // 注册切面以支持@Observed注解 @Bean ObservedAspect observedAspect(ObservationRegistry observationRegistry) { return new ObservedAspect(observationRegistry); } @RequiredArgsConstructor public static class BaggageObservationFilter implements ObservationFilter { private final Tracer tracer; @Override @NonNull public Observation.Context map(Observation.Context context) { // 将Baggage添加到观测上下文 tracer.getAllBaggage().entrySet().stream() .map(entry -> KeyValue.of(entry.getKey(), entry.getValue())) .forEach(context::addHighCardinalityKeyValue); return context; } }
Jaeger追踪图

解决方案
问题根源在于HTTP请求对应的观测(即首个Span)是在请求到达控制器方法之前就创建的——Spring MVC的观测逻辑会在DispatcherServlet处理请求阶段初始化观测,而你是在控制器方法内部才添加Baggage,此时首个观测已经完成初始化,ObservationFilter无法对它生效。
有两种可行的修复方式:
方式一:在请求进入控制器前添加Baggage
使用Spring MVC的HandlerInterceptor,在请求到达控制器方法之前就将truckId添加到Baggage中:
@Component public class TruckIdBaggageInterceptor implements HandlerInterceptor { private final Tracer tracer; public TruckIdBaggageInterceptor(Tracer tracer) { this.tracer = tracer; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { @SuppressWarnings("unchecked") Map<String, String> uriVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String truckId = uriVariables.get("truckId"); if (truckId != null) { tracer.createBaggageInScope("truckId", truckId); } return true; } }
注册拦截器:
@Configuration public class WebConfig implements WebMvcConfigurer { private final TruckIdBaggageInterceptor truckIdBaggageInterceptor; public WebConfig(TruckIdBaggageInterceptor truckIdBaggageInterceptor) { this.truckIdBaggageInterceptor = truckIdBaggageInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(truckIdBaggageInterceptor) .addPathPatterns("/trucks/{truckId}"); } }
这样HTTP请求观测初始化时,Baggage已存在,ObservationFilter就能将truckId附加到首个Span上。
方式二:自定义ServerHttpRequestObservationConvention
直接修改HTTP请求观测的元数据,从路径变量提取truckId并添加到高基数标签,无需依赖Baggage:
@Component public class CustomHttpRequestObservationConvention extends DefaultServerHttpRequestObservationConvention { @Override public KeyValues getHighCardinalityKeyValues(ServerHttpRequestObservationContext context) { KeyValues defaultValues = super.getHighCardinalityKeyValues(context); String truckId = context.getPathPatternMatch().getUriVariables().get("truckId"); if (truckId != null) { return defaultValues.and("truckId", truckId); } return defaultValues; } }
这种方式更直接,专门针对HTTP请求的首个Span添加标签。
内容的提问来源于stack exchange,提问作者BladesV
相关产品推荐
相关产品推荐

