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 是否结束并导出
这条链路也适合反向使用:如果已经知道问题发生在某一层,就不要继续在其他层盲目修改。
本文来自博客园,作者:01o00o10,转载请注明原文链接:https://www.cnblogs.com/01o00o10/articles/23135183

浙公网安备 33010602011771号