OpenTelemetry Instrumentation 与 Java Agent
本文最后更新于 2024年7月13日 下午
在之前的 blog: 分布式可观测性,链路追踪与OpenTelemetry 主要介绍了分布式链路追踪的概念和 OpenTelemetry (下文以 Otel 简称) 的起源。从本节开始,我会分享一些 OpenTelemetry 的基本概念,语言主要基于 Java(当然,Otel 本身的 SDK 支持多种语言,可以在 Otel Doc: Language APIs & SDKs 查看)。
本文介绍的内容主要涉及 OpenTelemetry[1] 的 Instrumentation 概念,以及如何将已有的代码接入 OpenTelemetry 以获得可观测性。
1 - OpenTelemetry Instrumentation Startup
如果你想把你的一个项目接入 OpenTelemetry,肯定要接触一个概念:”Instrumentation”,这是一个少有的我感觉没什么准确的一个中文词汇能表达出的意思,OpenTelemetry 将其翻译成「仪表化」,但我感觉仍然不太恰当。
这个词实际上表达的是:
向应用程序中注入跟踪和监控代码的过程,目的是收集有关应用程序运行时性能和行为的监控数据 [2]
OpenTelemetry 对 Instrumentation 主要提供了两种方案:
- 一种是手动配置的方案,也叫 Manual Instrumentation,该方案需要侵入式的手动更改代码,同样的提供的可配置项也更多。
- 一种是自动配置的方案,也叫 Auto Instrumentation / Zero Code Instrumentation (doc),该方案可以无须侵入性的修改代码,而是自动进行代码插桩和注入,实现方式根据语言而异,例如:
- 在
Java
上利用Java Agent
字节码修改技术 (https://github.com/open-telemetry/opentelemetry-java-instrumentation) - 在
Go
中利用eBPF 技术
(https://github.com/open-telemetry/opentelemetry-go-instrumentation) - 在
.Net
中利用monkey-patching
技术 (https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation) - 更多的
Auto Instrumentation
技术可以参考: https://github.com/orgs/open-telemetry/repositories?q=-instrumentation
- 在
2 - Java Agent
下文都以 Java 为例,介绍 Otel 如何利用 Java Agent 机制实现 Auto Instrumentation。该部分主要参考官网文档:Doc: Java Agent
本章首先介绍 Java Agent 机制。
2.1 - 什么是 Java Agent
Java代理是一种特殊类型的类,通过使用Java Instrumentation API ,它可以拦截运行在JVM上的应用程序,修改它们的字节码。 (注意:区别于 Otel Instrumentation,虽然用的是一个词,表达的也都是「代码注入」这个概念)
Java代理并不是一项新技术,相反,它们从Java 5开始就存在了。但是即使过了这么长时间,还是有许多开发者对这个概念鲜有接触。
通过 Java Agent,可以轻松实现如下几类应用场景:
- IDE 的调试功能,例如 Eclipse、IntelliJ IDEA;
- 热部署功能,例如 JRebel、XRebel、spring-loaded;
- 各种线上诊断工具,例如 Btrace、Greys,国内阿里的 Arthas;
- 各种性能分析工具,例如 Visual VM、JConsole 等;
- 全链路性能检测工具,例如 OpenTelemetry、Skywalking、Pinpoint 等。
2.2 - Java Agent 的运行机制
Java Agent 主要可以通过两种方式启动:Premain Agent 和 Agentmain,分别在 JVM 启动前和启动后加载 Agent,达到的都是动态修改字节码的效果:
- Premain Agent
Java Agent 在 Java 程序运行前:在Main
方法执行之前,通过一个叫 premain
方法来执行。
启动时需要在目标程序的启动参数中添加 -javaagent
参数,Java Agent 内部通过注册 ClassFileTransformer ,这个转化器在 Java 程序 Main
方法前加了一层拦截器。在类加载之前,完成对字节码修改。
1 |
|
其工作流程大致如下:[4]
- Agentmain
该模式和 Premain 模式相似,主要区别在进行字节码增强前,拦截入口不同。一个叫Premain
,一个叫Agentmain
。 运行时加载,当前 JVM 进程已经启动了。这时借助另一个 JVM 进程通信,调用 Attach API 再把 Agent 启动起来。后面的字节码修改和重加载的过程那就是一样的。
3 - Otel Java Auto Instrumentation
Otel Java Auto Instrumentation 主要通过 Java agent 机制实现。
Java Auto Instrumentation 实现都在 github: https://github.com/open-telemetry/opentelemetry-java-instrumentation/ 这个仓库中。
3.1 - 快速开始
可以参考 doc 快速在一个 java 项目中进行 instrumentation,只需要如下几步:
- Download opentelemetry-javaagent.jar from Releases of the
opentelemetry-java-instrumentation
- 通过
java -javaagent:path/to/opentelemetry-javaagent.jar -Dotel.service.name=your-service-name -jar myapp.jar
启动你的 project appmyapp.jar
通过这两步,就已经可以在这些 otel 支持的 library 上运行 opentelemetry 了,可以通过在环境变量或者 properties 中修改 这些配置 来进行更多自定义配置,包括但不限于:
- 数据导出配置:导出数据的目标类型,如 Jaeger、Zipkin、Prometheus等。
- 日志输出模式
- 采样策略
- 等等
下面通过一个我自己的示例来快速了解一下如何利用 Otel 提供的 @WithSpan
和 @SpanAttribute
Annotation 进行 Auto Instrumentation 的 Tracing。[6]
3.2 - Code 示例
首先创建一个 java 项目,并导入 opentelemetry-instrumentation-annotations
这个依赖,可以在 这里 查看 Maven 或者 Gradle 的导入方式。
这里以 gradle 为例,添加如下 dependencies 即可:
1 |
|
全部的 build.gradle.kts
:
1 |
|
然后写一个简单的测试类:
1 |
|
使用 -Dotel.traces.exporter=logging-otlp
将输出指定为 otlp 的 json 格式(otlp 是 opentelemetry 定义的传输协议),通过 log 输出在控制台,然后把 logs 和 metrics 的 exporter 禁用后启动:
1 |
|
也可以通过环境变量来启动:
1 |
|
输出如下:
1 |
|
3.3 - Span 的结构分析
输出的 json 实际上的结构可以在 trace.proto
中查看: https://github.com/open-telemetry/opentelemetry-proto/blob/v1.3.0/opentelemetry/proto/trace/v1/trace.proto
- 比如
main
方法对应的 span 对应包含如下信息
1 |
|
- 对于非 root span,还会额外包含一个
parentSpanId
的信息, 以sampleMethodB
为例:
1 |
|
关于其中字段的详细解释可以参考:https://opentelemetry.io/docs/concepts/signals/traces/
除了在上一篇文章提到的 Tracing 通用的 spanId, traceId 这些通用的 Span Attributes 外, OpenTelemetry 还提供了两种新的称为 Span Events 和 Span Links 的东西:
Span Events
: 用于标记一个 Span 内部的一个关键事件, 例如:
1 |
|
也可以在 events
中添加 Attributes:
1 |
|
Span Links
: 用于标记一个 Span 与其他 Span 之间的关联关系,例如:
1 |
|
这些 API 可以简单的在 Agent 的基础上使用,例如我们修改上面的示例代码:
1 |
|
然后重新运行,就可以得到如下的 payload:
1 |
|
可以看到里面已经包含了刚才添加的自定义 attributes, events 和 links 信息。
Conclusion
这一章还处于比较浅的阶段,后面会再写写 otel 一些内部 code 结构的分析,以及如何通过 agent extensions 等机制来自定义 agent 行为等。
Reference
- https://opentelemetry.io/docs ↩
- https://stackify.com/what-are-java-agents-and-how-to-profile-with-them/ ↩
- https://en.wikipedia.org/wiki/Instrumentation_(computer_programming) ↩
- 深入 OpenTelemetry 源代码:Java 探针的实现和二次开发 ↩
- Java 自动化探针技术的核心原理和实践 ↩
- https://opentelemetry.io/docs/zero-code/java/agent/annotations/ ↩
- https://github.com/open-telemetry/opentelemetry-proto/blob/v1.3.0/opentelemetry/proto/trace/v1/trace.proto ↩
- https://opentelemetry.io/docs/concepts/signals/traces/ ↩