业务流水号必须在span创建前、上下文初始化阶段注入contextcarrier,才能自动透传至所有span;需通过自定义插件在入口类(如controller或filter)的beforemethod中调用tag()方法注入,而非日志占位符或aftermethod。

理解 ContextCarrier 和业务流水号的绑定时机
业务流水号(比如订单号、支付单号)通常在请求入口生成,但 SkyWalking 的默认链路上下文不自动携带它。你不能靠改 %tid 占位符来实现——那只是日志格式层面的展示,和链路元数据无关。真正要让这个号出现在所有 Span 里、能被跨进程/跨线程传递,必须把它注入到 SkyWalking 的上下文载体中,也就是 ContextCarrier 或其等价物(如 ContextSnapshot)。
关键点在于:只有在 Span 创建前、上下文初始化阶段完成注入,后续所有拦截器(HTTP、RPC、DB 插件)才能自动把该字段作为 tag 写入 span 数据。否则,你只能在每个业务方法里手动 Tags.put("biz_id", xxx),既重复又易漏。
通过自定义插件在入口类增强时注入业务 ID
最稳定的做法是写一个轻量插件,在 Spring Controller、Dubbo Provider、或网关 Filter 这类统一入口处做字节码增强,从请求参数/HTTP header/ThreadLocal 中提取业务流水号,并调用 SkyWalking 提供的 API 注入上下文。
- 在插件的
Instrumentation类中,用getEnhanceClass()指向你的入口类(如org.springframework.web.servlet.DispatcherServlet或自定义GlobalFilter) - 拦截其核心方法(如
doDispatch或filter),在beforeMethod阶段获取请求对象 - 调用
ContextManager.createEntrySpan(...)后,立刻执行:ContextManager.getSpan().tag("biz_id", extractedBizId); - 注意:不要在
afterMethod里加 tag,此时 span 可能已结束;也不要用ContextManager.capture(),那是为跨线程准备的快照,不是为打 tag
避免直接修改 apm-toolkit-logback 的陷阱
有人尝试仿照 LogbackPatternConverter 写一个 %biz_id,再在 convert() 里从 MDC 取值——这看似简单,但存在两个硬伤:
-
MDC不跨线程,异步任务里取不到;而 SkyWalking 的ContextManager是线程绑定且支持跨线程传播的 - 日志占位符只影响输出格式,不会让该字段进入 trace 数据流,你在 UI 的「Trace Detail」页里搜不到
biz_id,也无法按它过滤链路 - 如果业务流水号来自下游服务透传(如 header 中的
X-Biz-Id),必须在入口拦截器里解析并塞进ContextManager,而不是等日志渲染时才去查
验证是否生效的三个检查点
部署插件后,别急着看日志,先确认链路数据本身是否带字段:
- 在 SkyWalking UI 的「Trace Detail」页打开任意一条链路,展开每个 Span,检查
tags区域是否存在biz_id键值对 - 用 SkyWalking 的 GraphQL 查询接口(
/graphql)发一个query { getTrace(traceId: "...") },看返回 JSON 中 span 的tags字段是否包含你设的值 - 在跨进程调用(如 Feign 调用下游服务)后的子 Span 中,确认
biz_id是否仍存在——若丢失,说明你没触发ContextCarrier的透传逻辑,需检查插件是否覆盖了 HTTP client 插件的增强点
真正难的不是写几行代码,而是判断该字段该在哪个类、哪个方法、哪个执行阶段注入——早了上下文未建,晚了 span 已封,中间还隔着线程切换和异步回调。盯住 ContextManager 的生命周期,比堆配置更重要。










