OpenTelemetry Java Agent 实战:零侵入接入 Spring Boot 调用链追踪并上报 OTLP Collector
摘要
很多 Spring Boot 项目已经接入了日志、接口耗时和基础 JVM 指标,但一到跨服务调用、下游接口变慢、偶发超时,就很难快速回答:这次请求经过了哪些服务?哪个环节最慢?TraceId 怎么串起来?
本文用 OpenTelemetry Java Agent 演示一种"零侵入"接入方式:不改业务代码,通过 -javaagent 挂载 Agent,将 Spring Boot 应用的调用链数据通过 OTLP 上报到 OpenTelemetry Collector。文章重点放在工程落地:启动命令、Collector 配置、Spring Boot 示例接口、TraceId 日志关联、采样和生产边界。
验证说明:本文命令和配置基于 OpenTelemetry 官方 Java Agent、OpenTelemetry Collector、Spring Boot Observability 文档整理。当前机器没有 Java/Maven,未做本地编译运行;代码为工程骨架,读者落地时请以当前依赖版本和官方文档为准执行验证。
1. 为什么不用改代码也能拿到调用链?
OpenTelemetry Java Agent 的核心价值是自动插桩。
对 Spring Boot 应用来说,常见 HTTP Server、HTTP Client、JDBC、Redis、Kafka、日志框架等组件都可以被 Agent 在运行期增强。也就是说,很多场景不需要你在每个 Controller、Service 方法里手写 Span。
典型链路如下:
Browser / curl
-> Spring Boot 服务
-> OpenTelemetry Java Agent 自动插桩
-> OTLP Exporter
-> OpenTelemetry Collector
-> 后端存储 / Trace 系统(Jaeger、Tempo、APM 平台等)
这套方式适合先补齐可观测性基础设施,尤其是:
- 老项目不方便大改代码;
- 想先验证调用链效果,再决定是否手写业务 Span;
- 服务数量较多,希望统一通过启动参数接入;
- 已经有 Collector 或可观测性平台,缺的是 Java 应用侧上报。
2. 环境版本和前置条件
建议使用下面的基础环境:
| 组件 | 建议版本 / 说明 |
|---|---|
| JDK | 17 或 21,实际以项目运行版本为准 |
| Spring Boot | 3.x 示例;2.x 项目也可接入,但配置和依赖需按实际版本调整 |
| OpenTelemetry Java Agent | 使用官方 release 下载的 opentelemetry-javaagent.jar |
| OpenTelemetry Collector | 使用官方 Collector 镜像或二进制 |
| 协议 | OTLP gRPC 默认端口 4317,OTLP HTTP 默认端口 4318 |
本文用到的端口规划:
| 端口 | 用途 |
|---|---|
| 8080 | Spring Boot 示例服务 |
| 4317 | Collector 接收 OTLP gRPC |
| 4318 | Collector 接收 OTLP HTTP |
| 8889 | Collector 暴露 Prometheus 指标示例,可选 |
3. 准备一个最小 Spring Boot 示例服务
如果你只是验证 Java Agent,不需要一开始就搭复杂微服务。一个最小 Controller 就够了。
3.1 Maven 依赖示例
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
注意:Java Agent 自动插桩本身不要求你额外引入 OpenTelemetry SDK 依赖。
spring-boot-starter-actuator主要用于后续健康检查、指标和可观测性扩展。
3.2 示例 Controller
java
`package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
@RestController
public class OrderController {
@GetMapping("/orders")
public String listOrders(@RequestParam(defaultValue = "100") long sleep) throws InterruptedException {
Thread.sleep(sleep);
return "orders ok, sleep=" + sleep + ", time=" + LocalDateTime.now();
}
}
`
这个接口故意加了一个 sleep 参数,方便你在 Trace 系统里观察不同耗时请求。
3.3 application.yml
server:
port: 8080
spring:
application:
name: otel-demo-service
management:
endpoints:
web:
exposure:
include: health,info,metrics
这里的 spring.application.name 很重要。Java Agent 上报时你也可以通过 OTEL_SERVICE_NAME 或 otel.service.name 指定服务名,生产环境建议统一规范。
4. 启动 OpenTelemetry Collector
Collector 是接收、处理、转发遥测数据的中间层。Java 应用不建议直接散乱上报到多个后端,统一先打到 Collector 更利于后续治理。
4.1 collector-config.yaml
下面是一个最小可用的 Collector 配置:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
这个配置使用 debug exporter,适合本地验证:Collector 会把收到的 trace / metrics / logs 打到控制台。
生产环境一般会换成 Jaeger、Tempo、Prometheus、Loki、商业 APM 或公司内部可观测性平台对应的 exporter。
4.2 Docker 启动 Collector
docker run --rm \
--name otel-collector \
-p 4317:4317 \
-p 4318:4318 \
-v $(pwd)/collector-config.yaml:/etc/otelcol/config.yaml \
otel/opentelemetry-collector:latest \
--config=/etc/otelcol/config.yaml
启动后,如果配置正常,Collector 会监听 4317 和 4318。
可以另开一个终端检查端口:
curl -v http://localhost:4318/
OTLP HTTP 端口不是普通业务接口,返回 404 或特定错误并不一定代表 Collector 不可用,关键是进程监听和后续遥测数据能被接收。
5. 下载并挂载 OpenTelemetry Java Agent
5.1 下载 Agent
建议从 OpenTelemetry Java Instrumentation 官方 release 下载:
mkdir -p agents
curl -L -o agents/opentelemetry-javaagent.jar \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
生产环境不要每次启动临时下载,应该把 Agent 版本固定到镜像或制品仓库中,例如:
/opt/otel/opentelemetry-javaagent-2.x.x.jar
5.2 用 javaagent 启动 Spring Boot
假设你的应用 jar 是:
target/otel-demo-service.jar
可以这样启动:
java \
-javaagent:agents/opentelemetry-javaagent.jar \
-Dotel.service.name=otel-demo-service \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=none \
-Dotel.exporter.otlp.endpoint=http://localhost:4317 \
-Dotel.exporter.otlp.protocol=grpc \
-jar target/otel-demo-service.jar
也可以用环境变量方式:
export OTEL_SERVICE_NAME=otel-demo-service
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=none
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
java -javaagent:agents/opentelemetry-javaagent.jar \
-jar target/otel-demo-service.jar
两种方式都可以。团队里更推荐环境变量或启动脚本统一注入,避免每个服务复制一大串 JVM 参数。
6. 验证 Trace 是否上报成功
6.1 请求接口
curl "http://localhost:8080/orders?sleep=200"
curl "http://localhost:8080/orders?sleep=800"
6.2 查看 Collector 输出
如果使用上面的 debug exporter,Collector 控制台应该能看到类似结构:
ResourceSpans #0
Resource SchemaURL:
Resource attributes:
-> service.name: Str(otel-demo-service)
ScopeSpans #0
Span #0
Trace ID : 4bf92f3577b34da6a3ce929d0e0e4736
Parent ID :
ID : 00f067aa0ba902b7
Name : GET /orders
Kind : Server
Start time : ...
End time : ...
Status code : Unset
重点看:
service.name是否正确;Trace ID是否生成;- Span 名称是否能识别接口;
- 请求耗时是否和
sleep参数接近; - 是否有异常状态和错误标签。
如果你接入的是 Jaeger 或 Tempo,就可以在 UI 中按 service、trace id、接口名查询。
7. TraceId 如何和日志关联?
调用链系统好用的前提之一,是日志里能看到 TraceId。否则 Trace UI 和业务日志还是割裂的。
Java Agent 对常见日志框架有自动注入能力,但具体效果会受日志框架、版本和 pattern 配置影响。
一个常见的 Logback pattern 示例:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] trace_id=%X{trace_id} span_id=%X{span_id} %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
请求接口时,期望日志里能看到:
2026-06-16 10:00:00.123 INFO [http-nio-8080-exec-1] trace_id=4bf92f3577b34da6a3ce929d0e0e4736 span_id=00f067aa0ba902b7 com.example.demo.OrderController - list orders
如果 trace_id 为空,可以按这个顺序排查:
- 应用是否真的通过
-javaagent启动; - 日志框架是否被 Agent 支持;
- pattern 中 MDC key 是否写对;
- 当前日志是否发生在 Span 上下文内;
- 是否禁用了相关 instrumentation。
8. 常见配置项:服务名、环境、采样率
生产环境不要只配置一个 service.name。至少建议补齐环境、版本和采样策略。
export OTEL_SERVICE_NAME=order-service
export OTEL_RESOURCE_ATTRIBUTES=deployment.environment=prod,service.version=1.3.7,service.namespace=mall
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
含义:
| 配置 | 作用 |
|---|---|
OTEL_SERVICE_NAME |
服务名,Trace UI 查询的核心维度 |
OTEL_RESOURCE_ATTRIBUTES |
环境、版本、命名空间等资源属性 |
OTEL_TRACES_SAMPLER |
采样器类型 |
OTEL_TRACES_SAMPLER_ARG |
采样比例,例如 0.1 表示约 10% |
不建议生产环境一上来全量采样。高 QPS 服务要评估:
- Agent CPU 和内存开销;
- Collector 吞吐;
- Trace 后端存储成本;
- 采样后排查问题是否仍然够用;
- 关键接口是否需要单独提高采样率。
9. Spring Boot 项目接入时的推荐落地步骤
不要一上来全公司所有服务一起挂 Agent。更稳的顺序是:
单服务本地验证
-> 测试环境 1~2 个低风险服务
-> 接入 Collector 和 Trace 后端
-> 打通 TraceId 日志关联
-> 调整采样率和资源标签
-> 灰度到核心链路
-> 建立告警和排查 SOP
对应检查表:
| 阶段 | 必查项 |
|---|---|
| 本地验证 | Agent 能启动,Collector 能收到 Span |
| 测试环境 | 服务名、环境标签、TraceId 日志关联正确 |
| 灰度 | CPU、内存、RT、错误率无明显异常 |
| 生产接入 | 采样率、Collector 高可用、后端存储容量确认 |
| 排障 SOP | TraceId 能从网关、日志、链路 UI 串起来 |
10. 常见问题排查
10.1 Collector 收不到数据
先检查启动参数:
ps -ef | grep opentelemetry-javaagent
确认应用进程里有 -javaagent。
再检查 OTLP endpoint:
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_EXPORTER_OTLP_PROTOCOL
如果你使用的是 gRPC,通常是:
http://collector-host:4317
grpc
如果使用 HTTP/protobuf,通常是:
http://collector-host:4318
http/protobuf
协议和端口不要混用。
10.2 服务名显示成 unknown_service
通常是没有设置服务名。补上:
-Dotel.service.name=your-service-name
或:
export OTEL_SERVICE_NAME=your-service-name
10.3 日志里没有 trace_id
排查顺序:
- Logback / Log4j2 pattern 是否包含
%X{trace_id}和%X{span_id}; - 日志是否发生在请求处理链路内;
- Agent 版本是否支持当前日志框架版本;
- 是否有自定义异步线程导致上下文没有传播;
- 是否禁用了日志 MDC instrumentation。
异步线程池场景要重点验证。自动插桩覆盖不了所有自定义封装,必要时需要手写上下文传递或业务 Span。
10.4 接入后 RT 变慢怎么办?
先不要直接下结论是 Agent 的锅,建议按顺序看:
- 是否全量采样;
- Collector 是否阻塞或资源不足;
- Exporter 超时是否过长;
- Trace 后端是否写入慢;
- 是否开启了过多 instrumentation;
- 应用自身是否同时发生了流量变化。
可临时降低采样率验证:
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.01
11. 生产环境边界
OpenTelemetry Java Agent 很适合快速接入,但不要把它理解成"挂上就完成可观测性建设"。
生产环境至少要补齐这些边界:
| 边界 | 建议 |
|---|---|
| Agent 版本 | 固定版本,灰度升级,不要永远 latest |
| Collector | 至少测试环境和生产环境分开,生产考虑高可用 |
| 采样 | 高 QPS 服务不要默认全量采样 |
| 标签规范 | service.name、environment、version、namespace 统一 |
| 日志关联 | TraceId 必须能在日志中检索 |
| 异步场景 | 线程池、MQ、定时任务单独验证上下文传播 |
| 安全 | 不要把敏感 header、参数、SQL 明文无限制上报 |
| 回滚 | JVM 启动参数可快速移除 Agent |
如果是容器化部署,可以把 Agent 做成基础镜像层,或者通过 Sidecar / initContainer 下载到固定路径,再由启动脚本注入:
JAVA_TOOL_OPTIONS="-javaagent:/opt/otel/opentelemetry-javaagent.jar"
这种方式对已有应用侵入更低,但也要做好版本固定和灰度控制。
12. 小结
OpenTelemetry Java Agent 适合解决一个很具体的问题:在尽量少改业务代码的前提下,把 Spring Boot 服务的 HTTP 请求、下游调用和关键组件操作串成 Trace,并通过 OTLP 统一上报到 Collector。
落地时不要只关注"能不能看到一条链路",更要关注:服务名是否规范、TraceId 能否和日志关联、采样率是否合理、Collector 是否稳定、生产环境能否灰度和回滚。
建议的最小落地路径是:先在一个低风险 Spring Boot 服务上挂 Agent,确认 Collector 能收到 Span,再打通日志 TraceId,最后再逐步扩展到核心链路。
如果你关注 Java 后端、Spring Boot、微服务治理、可观测性和线上问题排查,可以继续关注我的 CSDN 技术文章。