Otel Java Agent 1.15 深度分析(十七):一个 instrumentation 不生效时怎么查

OpenTelemetry Java Agent 1.15 深度分析(十七):一个 instrumentation 不生效时怎么查

源码分析最终要回到运行问题:为什么某个请求没有 Span?为什么 Span 重复?为什么升级依赖后突然失效?这一篇整理一套从入口到字节码的排查路径。

一、先确认 Agent 是否真的启动

检查启动参数、Agent 版本、启动日志和配置读取结果。Agent 没有启动时,继续看具体 instrumentation 没有意义。

二、再确认模块是否加载

检查模块开关、默认启用策略和 Muzzle 结果。如果模块被禁用或版本不兼容,目标类自然不会被转换。

三、确认目标类是否匹配

需要核对实际运行时类名、实现类和 ClassLoader。框架经常使用代理类、包装类和动态生成类,源码中的类名不一定是运行时最终类名。

四、确认方法是否匹配

重点检查方法名、参数签名、继承关系、桥接方法和重载方法。匹配太窄会漏采集,匹配太宽会重复增强。

五、确认 Span 生命周期

即使方法被增强,也可能因为异常路径、异步回调或提前返回造成 Span 没有正常结束。应分别验证正常返回、异常、取消和超时。

六、推荐的排查顺序

Agent 启动
  → 模块启用
  → Muzzle 通过
  → 类命中
  → 方法命中
  → Advice 执行
  → Context 正确
  → Span 正常结束

七、小结

不要一上来就修改 Advice。先沿着这条链路逐层排除,通常能很快确定问题属于配置、兼容性、类加载、匹配规则还是生命周期。

八、第一步:确认 Agent 版本和启动方式

先确认实际启动的 Agent JAR,而不是项目目录里正在阅读的源码版本。常见问题包括:

  • 运行时使用了旧 Agent
  • 同时配置了两个 -javaagent
  • 容器启动脚本覆盖了本地参数
  • Agent JAR 被打进业务 fat JAR
  • 使用了不同环境的配置文件

可以先打印 Agent 版本、启动命令和 JVM System Properties,确认排查对象没有错。

九、第二步:确认配置最终取值

不要只检查一处配置。按源码顺序检查:

SPI 默认值
  → configuration file
  → environment variable
  → System Property
  → ConfigCustomizer

重点关注:

otel.javaagent.enabled
otel.javaagent.debug
otel.instrumentation.<name>.enabled
otel.javaagent.configuration-file

“环境变量已经设置”不代表它最终生效,因为 System Property 或配置文件可能覆盖它。

十、第三步:确认模块有没有被发现

看 Agent 启动日志中是否出现 instrumentation 名称和实现类。如果没有,可能是:

  • ServiceLoader 文件没有被打进 Agent
  • shading 后类名或资源路径不正确
  • 模块 JAR 没有进入测试/运行时包
  • 模块加载过程中发生 LinkageError

如果模块被发现但安装失败,要重点查看模块加载异常,而不是继续检查目标方法。

十一、第四步:确认 Muzzle 是否通过

如果日志出现:

Instrumentation skipped, mismatched references were found

就先处理 Muzzle。常见原因:

  • 运行时库版本低于支持范围
  • 同一个库被不同 ClassLoader 加载
  • 代理或容器替换了实际实现类
  • 方法参数或返回值签名不同
  • Helper 的父类或接口不完整

Muzzle 失败时不会继续进入 Advice,所以修改 Advice 本身没有意义。

十二、第五步:确认实际运行类名

源码中的目标类名和运行时类名可能不同:

原始类
  → CGLIB 代理
  → JDK 动态代理
  → 容器包装类
  → 框架生成类

同时打印:

object.getClass().getName();
object.getClass().getSuperclass();
object.getClass().getInterfaces();
object.getClass().getClassLoader();

确认对象真正使用的类型和 ClassLoader,再回头对照 TypeMatcher。

十三、第六步:确认方法签名

只看方法名很容易误判。需要同时确认:

  • 参数数量
  • 参数类型
  • 返回类型
  • 是否是桥接方法
  • 是否是接口默认方法
  • 是否是父类继承方法
  • 是否存在重载

例如 execute() 和 execute(String) 在 JDBC 中属于两条不同的匹配路径,不能用一个 Advice 假设覆盖全部情况。

十四、第七步:打开转换日志

Debug 模式下,AgentBuilder 会安装转换和重新定义相关监听器。日志应当回答:

目标类是否被发现
目标类是否被忽略
哪个 instrumentation 命中
转换是否成功
是否发生重新转换

如果目标类从未出现在转换日志中,优先检查类加载时机和忽略规则;如果出现但转换失败,再看 Advice 或 Helper。

十五、类已经加载怎么办

如果业务类在 Agent Transformer 注册前就加载了,后续普通类加载回调不会再次触发。Agent 1.15 使用 retransformation 策略处理部分已加载类,但前提是目标类可被重新转换,并且 AgentBuilder 配置允许这样做。

类已加载
  → 是否支持 retransformation
  → 是否被重新发现
  → 是否重新执行 Matcher
  → 是否重新应用 Advice

最稳妥的方式仍然是让 Agent 在应用启动前通过 -javaagent 加载。

十六、Advice 执行但没有 Span

这时问题已经从字节码层进入埋点逻辑,重点检查:

  • Instrumenter.shouldStart 是否返回 false
  • 当前 Context 是否被错误抑制
  • SpanKind 或请求对象是否为空
  • 采样或 noop 配置是否生效
  • Advice 是否因为 suppress = Throwable.class 隐藏了异常
  • Span 是否创建但没有被测试 Exporter 读取

尤其要注意,Advice 的 suppress 会保护业务,但也可能让埋点内部异常只表现为“没有数据”。调试时需要额外打开 Agent 内部日志。

十七、Span 有但父子关系不对

按顺序检查:

远端 Context 是否成功提取
  → 当前 Context 是否激活
  → 异步切换时是否传播
  → 下游 Span 创建时读取的是哪个 Context
  → 是否存在重复 instrumentation

跨进程场景检查消息头或 HTTP Header;跨线程场景检查任务对象、Scope 和线程池复用。

十八、Span 没有结束

同步方法通常检查 @Advice.OnMethodExit;异步方法则要继续找:

  • Future 完成回调
  • Reactive onComplete/onError/onCancel
  • Kafka Producer callback
  • Servlet async dispatch
  • 超时和取消分支

如果创建点和结束点不在同一个 Advice,必须把状态保存到合适的对象或 Context 中。

十九、Span 重复生成

常见来源:

  • Servlet 和 Spring MVC 同时创建 HTTP Server Span
  • Kafka Client 和 Spring Kafka 同时创建消息 Span
  • 方法重入没有使用 CallDepth
  • 同一个类被重复转换
  • 包装类和真实实现类都被匹配

处理时先画出 Span 层级,再定位每个 Span 对应的 instrumentation 和方法入口,不要只凭 Span 名称猜测。

二十、排障时的最小证据集

建议每次问题都收集下面这些信息:

JDK 版本
Agent 版本
目标库版本
实际目标类名
实际 ClassLoader
相关配置最终值
模块加载日志
Muzzle mismatch
转换日志
Span/Context 结果

有了这组信息,通常可以把问题明确归到启动、配置、兼容性、匹配、类加载或生命周期中的某一类。

二十一、本篇结论

一个 instrumentation 不生效时,正确的排查顺序是:

Agent 是否启动
  → 配置是否正确
  → 模块是否发现
  → Muzzle 是否通过
  → 类是否命中
  → 方法是否命中
  → Advice 是否执行
  → Context 是否正确
  → Span 是否结束并导出

这条链路也适合反向使用:如果已经知道问题发生在某一层,就不要继续在其他层盲目修改。

posted @ 2026-10-02 10:33  01o00o10  阅读(3)  评论(0)    收藏  举报