OpenTelemetry Java Agent 实战:零侵入接入 Spring Boot 调用链追踪并上报 OTLP Collector

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_NAMEotel.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 会监听 43174318

可以另开一个终端检查端口:

复制代码
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 为空,可以按这个顺序排查:

  1. 应用是否真的通过 -javaagent 启动;
  2. 日志框架是否被 Agent 支持;
  3. pattern 中 MDC key 是否写对;
  4. 当前日志是否发生在 Span 上下文内;
  5. 是否禁用了相关 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 的锅,建议按顺序看:

  1. 是否全量采样;
  2. Collector 是否阻塞或资源不足;
  3. Exporter 超时是否过长;
  4. Trace 后端是否写入慢;
  5. 是否开启了过多 instrumentation;
  6. 应用自身是否同时发生了流量变化。

可临时降低采样率验证:

复制代码
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 技术文章。