@LogTrace注解用于声明式追踪分布式系统中APM工具未覆盖的方法,通过AOP统一埋点、注入标签、联动MDC日志,并需注意版本兼容、异步传递及代理限制。

为什么需要 @LogTrace 注解
在分布式系统中,不是所有方法都值得被 SkyWalking 或 Zipkin 等 APM 工具自动捕获——比如内部工具类、异步任务、定时调度、MQ 消费逻辑,或某些被框架插件遗漏的 RPC 调用点。此时,硬编码 ActiveSpan.tag() 或手动创建 Tracer.createEntrySpan() 会污染业务逻辑,且难以统一管理。一个通用的 @LogTrace 注解,能以声明式方式标记“我要追踪这个方法”,把埋点逻辑从代码里抽离,交由 AOP 统一处理。
注解定义与元注解设计
定义简洁、可复用、支持扩展:
- 用
@Target({METHOD})限定仅作用于方法,避免误用 - 加
@Retention(RUNTIME)确保运行时可通过反射读取 - 提供
value()字段用于自定义跨度名称(默认取方法签名) - 添加
tags()支持键值对注入,如@LogTrace(tags = {"bizType", "order_create"}) - 可选
includeParams()控制是否记录入参(生产环境建议关闭)
示例代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTrace {
String value() default "";
String[] tags() default {};
boolean includeParams() default false;
}
基于 Spring AOP 的切面实现
核心是拦截带注解的方法,自动创建 EntrySpan 或 ExitSpan,并完成标签写入与异常捕获:
- 使用
@Around("@annotation(logTrace)")拦截,确保能控制执行前后 - 通过
Tracer.createEntrySpan()创建入口跨度(若当前无活跃 Span,则新建;否则作为子 Span) - 解析
@LogTrace的value()作为操作名(operationName),替代默认的class#method - 遍历
tags()数组,按偶数索引为 key、奇数为 value 写入 Span 标签(需校验长度为偶数) - 若
includeParams == true,将参数 JSON 序列化后写入log或自定义 tag(注意脱敏) - 方法抛异常时调用
span.error(...)并span.finish();正常结束也必须 finish,避免内存泄漏
与 MDC 和日志联动(关键增强)
仅上报到 SkyWalking 不够——开发者日常最依赖的是日志。要让 @LogTrace 同时激活日志链路,需桥接 Span 上下文与 MDC:
- 在 Span 创建后,立即从
Tracer.getActiveSpan()获取 traceId 和 spanId - 调用
MDC.put("traceId", traceId)和MDC.put("spanId", spanId) - 确保日志配置(如 Logback 的
%X{traceId})能自动渲染,实现“一行日志=一次追踪上下文” - 注意:AOP 切面需声明为
@Order(Ordered.HIGHEST_PRECEDENCE),确保早于其他拦截器执行
兼容性与生产注意事项
真实项目中容易踩坑的细节:
-
版本强绑定:SkyWalking 的
apm-toolkit-trace必须与 Agent 版本一致,否则ActiveSpan可能为空或 tag 不生效 -
异步线程失效:若方法内启用了新线程(如
CompletableFuture),需显式传递 SpanContext,或改用Tracer.inject()/extract() -
泛型/桥接方法干扰:Spring AOP 默认不代理 final 方法和 private 方法,确保目标方法可被代理;必要时启用
@EnableAspectJAutoProxy(proxyTargetClass = true) -
性能兜底:在切面中加入快速失败判断,例如
if (!Tracer.isRunning()) return proceed();,避免非可观测环境额外开销

















