OpenTelemetry JAVA¶
OpenTelemetry Java Agent 通过 JVM -javaagent 参数在运行时注入字节码,无需修改业务代码,即可采集常见 Java Web 框架、HTTP 客户端、JDBC、RPC、消息队列及 JVM Runtime 等遥测数据。
本文使用 DataKit 的 OpenTelemetry 采集器接收 Trace 和 Metric;应用日志写入本地文件,由 DataKit 的 logging 采集器读取并转发到观测云:
Java 应用 + OpenTelemetry Java Agent -- OTLP Trace/Metric --> DataKit --> 观测云
Java 应用 -- 含 trace_id/span_id 的日志文件 --> DataKit logging --> 观测云
前置条件¶
- Java 8 或更高版本;
- 已安装 DataKit,且 DataKit 已连接到目标观测云工作空间;
- Java 应用到 DataKit 的网络可达:OTLP/HTTP 使用 DataKit HTTP 端口
9529,OTLP/gRPC 默认使用4317; - Java Agent 对目标框架或组件提供自动插桩支持。
一、开启 OpenTelemetry 采集器¶
主机安装的 DataKit¶
进入 DataKit 安装目录下的 conf.d/opentelemetry。如果尚未创建采集器配置,复制示例文件:
确认 opentelemetry.conf 至少包含以下接收配置:
[[inputs.opentelemetry]]
# 如需在观测云中将自定义属性作为标签保留,请在此加入白名单。
# 属性名中的点会被转换为下划线,例如 team.name -> team_name。
customer_tags = ["team", "project"]
[inputs.opentelemetry.http]
http_status_ok = 200
trace_api = "/otel/v1/traces"
metric_api = "/otel/v1/metrics"
[inputs.opentelemetry.grpc]
addr = "127.0.0.1:4317"
max_payload = 16777216
以上配置同时开启以下接收地址:
| 协议 | 数据类型 | DataKit 接收地址 |
|---|---|---|
| OTLP/HTTP + Protobuf | Trace | http://<DataKit-IP>:9529/otel/v1/traces |
| OTLP/HTTP + Protobuf | Metric | http://<DataKit-IP>:9529/otel/v1/metrics |
| OTLP/gRPC | Trace、Metric | http://<DataKit-IP>:4317 |
本文不通过 OTLP 上报应用日志,因此无需配置 logs_api。日志文件采集将在三、日志采集与链路关联中配置。
如果应用与 DataKit 不在同一主机,需将 gRPC 的 addr 改为可被应用访问的监听地址,例如 0.0.0.0:4317。同时按实际部署调整 DataKit HTTP 监听地址、防火墙或其他网络访问控制。不要将 OTLP 接收端口直接暴露到公网。
重启 DataKit 使配置生效:
检查 DataKit HTTP 服务是否可达:
二、应用接入 OpenTelemetry¶
下载 Java Agent¶
从 OpenTelemetry 官方仓库下载最新版 Java Agent:
sudo mkdir -p /opt/opentelemetry
sudo curl -fL \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar \
-o /opt/opentelemetry/opentelemetry-javaagent.jar
可在 OpenTelemetry Java Instrumentation Release 页面 查看可用版本。生产环境建议固定已经验证的 Agent 版本,并在升级前检查 Release Notes 和执行回归测试。
通过环境变量启动¶
下面以 OTLP/HTTP + Protobuf 为例。OTEL_EXPORTER_OTLP_ENDPOINT 是基础地址,Java Agent 会为 Trace 和 Metric 自动追加 /v1/traces、/v1/metrics,最终对应 DataKit 的 /otel/v1/* 路由。日志通过文件采集,因此必须保持 OTEL_LOGS_EXPORTER=none,避免同一份日志重复上报。
export JAVA_TOOL_OPTIONS="-javaagent:/opt/opentelemetry/opentelemetry-javaagent.jar"
export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="env=prod,version=1.0.0,team=backend"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="none"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:9529/otel"
export OTEL_PROPAGATORS="tracecontext,baggage"
java -jar app.jar
JAVA_TOOL_OPTIONS 会作用于该环境中启动的所有 JVM。如果同一启动环境中还有其他 Java 进程,建议改用下一节的显式 -javaagent 参数,避免误注入。
通过 JVM 参数启动¶
系统属性必须放在 -jar 之前:
java -javaagent:/opt/opentelemetry/opentelemetry-javaagent.jar \
-Dotel.service.name=order-service \
-Dotel.resource.attributes=env=prod,version=1.0.0,team=backend \
-Dotel.traces.exporter=otlp \
-Dotel.metrics.exporter=otlp \
-Dotel.logs.exporter=none \
-Dotel.exporter.otlp.protocol=http/protobuf \
-Dotel.exporter.otlp.endpoint=http://127.0.0.1:9529/otel \
-Dotel.propagators=tracecontext,baggage \
-jar app.jar
使用 OTLP/gRPC¶
如需改用 OTLP/gRPC,只需替换协议和地址;gRPC 地址不能追加 /v1/traces 等 HTTP 路径:
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
Tomcat¶
在 <CATALINA_BASE>/bin/setenv.sh 中设置环境变量和 Agent 参数:
export CATALINA_OPTS="$CATALINA_OPTS -javaagent:/opt/opentelemetry/opentelemetry-javaagent.jar"
export OTEL_SERVICE_NAME="order-service"
export OTEL_RESOURCE_ATTRIBUTES="env=prod,version=1.0.0"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="none"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:9529/otel"
保存后重启 Tomcat。其他应用服务器同样需要把 -javaagent 加到实际启动 JVM 的参数中,并确保同一个 JVM 只加载一次 Agent。
三、日志采集与链路关联¶
如果不使用 OpenTelemetry Log 方式上报应用日志,可以让应用将日志写入本地文件,再由 DataKit 的 logging 采集器进行文件日志采集。使用该方式时,需要将 OTEL_LOGS_EXPORTER 设置为 none(或通过 JVM 参数设置 -Dotel.logs.exporter=none),关闭 OpenTelemetry Log Exporter,避免同一份日志通过不同链路重复上报。
OpenTelemetry Java Agent 会把当前 Span 的以下字段自动注入 Logback 或 Log4j 日志事件的 MDC(Mapped Diagnostic Context)副本:
trace_id:当前 Trace ID;span_id:当前 Span ID;trace_flags:W3C Trace Flags。
应用不需要增加 OpenTelemetry 日志依赖或修改业务代码,只需在日志格式中引用这些 MDC 字段。DataKit 从文件采集日志后,再通过 Pipeline 将 trace_id 和 span_id 提取为日志字段,即可在观测云中实现日志与链路关联。
!!! note
只有在有效 Span 的上下文中产生的日志才会包含链路字段。应用启动日志、定时任务日志或未被插桩覆盖的异步任务日志可能没有 `trace_id` 和 `span_id`,这属于正常情况。字段名必须使用 Java Agent 注入的 `trace_id`、`span_id` 和 `trace_flags`,不要改成 `traceId` 或手动生成 ID。
Logback 写入文件¶
Logback 1.0 及以上版本受 Java Agent 的 MDC 自动插桩支持。将以下内容加入 logback.xml 或 logback-spring.xml,并按实际情况修改日志目录:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_DIR" value="/opt/order-service/logs"/>
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags}] - %msg%n"/>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</configuration>
如果 Spring Boot 应用已经使用默认 Logback 配置,也可以只在 application.properties 中启用文件输出并覆盖文件日志格式:
logging.file.name=/opt/order-service/logs/application.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [trace_id=%mdc{trace_id} span_id=%mdc{span_id} trace_flags=%mdc{trace_flags}] - %msg%n
Log4j 2 写入文件¶
Log4j 2.7 及以上版本受 Java Agent 的上下文数据自动插桩支持。将以下内容加入 log4j2.xml 或 log4j2-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_DIR">/opt/order-service/logs</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{36} [trace_id=%X{trace_id} span_id=%X{span_id} trace_flags=%X{trace_flags}] - %m%n%throwable</Property>
</Properties>
<Appenders>
<RollingFile name="FILE"
fileName="${LOG_DIR}/application.log"
filePattern="${LOG_DIR}/application.%d{yyyy-MM-dd}.%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<DefaultRolloverStrategy max="7"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="FILE"/>
</Root>
</Loggers>
</Configuration>
以上两种配置生成的日志首行格式一致,例如:
2026-08-12 10:20:30.123 [http-nio-8080-exec-1] INFO c.e.OrderController [trace_id=4bf92f3577b34da6a3ce929d0e0e4736 span_id=00f067aa0ba902b7 trace_flags=01] - order created
配置 DataKit 文件采集¶
在应用所在主机创建 /usr/local/datakit/conf.d/logging/java_otel.conf:
[[inputs.logging]]
logfiles = ["/opt/order-service/logs/*.log"]
source = "java"
service = "order-service"
pipeline = "java_otel.p"
character_encoding = "utf-8"
# 将 Java 异常堆栈合并到对应日志;以日期开头的行视为一条新日志。
enable_multiline = true
multiline_match = '''^\d{4}-\d{2}-\d{2}'''
remove_ansi_escape_codes = true
from_beginning = false
[inputs.logging.tags]
env = "prod"
version = "1.0.0"
service、env、version 应与 Java Agent 的 OTEL_SERVICE_NAME 和 OTEL_RESOURCE_ATTRIBUTES 保持一致。DataKit 运行用户还必须对日志目录具有遍历权限,并对日志文件具有读取权限。
然后创建 /usr/local/datakit/pipeline/java_otel.p:
grok(_, "%{TIMESTAMP_ISO8601:time} \\[%{DATA:thread_name}\\] %{LOGLEVEL:status}%{SPACE}%{NOTSPACE:class_name} \\[trace_id=%{DATA:trace_id} span_id=%{DATA:span_id} trace_flags=%{DATA:trace_flags}\\] - %{GREEDYDATA:msg}")
default_time(time)
该 Pipeline 会将 MDC 中的值提取为日志顶层字段 trace_id、span_id 和 trace_flags。其中 trace_id、span_id 是观测云关联日志与链路所需的关键字段。
重启 DataKit,使日志采集配置和 Pipeline 生效:
日志文件路径、日志格式或字段顺序发生变化时,需要同步修改 logfiles、multiline_match 和 Pipeline 规则。更多文件采集选项参见 DataKit 日志采集。
四、数据上报参数¶
Java Agent 支持环境变量、JVM 系统属性和配置文件。优先级从高到低为:JVM 系统属性、环境变量、配置文件。系统属性转为环境变量时,将名称转为大写,并把 . 和 - 替换为 _,例如 otel.service.name 对应 OTEL_SERVICE_NAME。
基础参数¶
| 环境变量 | JVM 系统属性 | 说明 | 建议值或示例 |
|---|---|---|---|
OTEL_SERVICE_NAME |
otel.service.name |
服务名;未设置时默认为 unknown_service:java。 |
order-service,生产环境必须显式设置。 |
OTEL_RESOURCE_ATTRIBUTES |
otel.resource.attributes |
资源属性,格式为逗号分隔的 key=value。 |
env=prod,version=1.0.0,team=backend |
OTEL_TRACES_EXPORTER |
otel.traces.exporter |
Trace 导出器。 | 上报到 DataKit 时设为 otlp;关闭时设为 none。 |
OTEL_METRICS_EXPORTER |
otel.metrics.exporter |
Metric 导出器。 | 上报 JVM/Runtime 指标时设为 otlp;关闭时设为 none。 |
OTEL_LOGS_EXPORTER |
otel.logs.exporter |
Log 导出器。 | 本文由 DataKit 采集日志文件,固定设为 none,避免重复上报。 |
OTEL_PROPAGATORS |
otel.propagators |
跨服务上下文传播格式。 | 默认 tracecontext,baggage;全链路需保持兼容。 |
OTEL_SDK_DISABLED |
otel.sdk.disabled |
禁用 OpenTelemetry SDK。 | 默认 false。应急关闭时设为 true。 |
OTEL_JAVAAGENT_ENABLED |
otel.javaagent.enabled |
禁用或启用 Java Agent。 | 默认 true。 |
OTEL_JAVAAGENT_CONFIGURATION_FILE |
otel.javaagent.configuration-file |
Java Agent properties 配置文件路径。 | /etc/otel/otel.properties |
service.name 用于观测云中的服务归属;建议同时设置 env 和 version,用于按环境及版本筛选。其他自定义资源属性需要加入 DataKit customer_tags 白名单后才会作为标签保留,属性名中的 . 会转换为 _。
OTLP 参数¶
| 环境变量 | JVM 系统属性 | 说明 | 建议值或示例 |
|---|---|---|---|
OTEL_EXPORTER_OTLP_PROTOCOL |
otel.exporter.otlp.protocol |
所有信号的 OTLP 协议。 | http/protobuf 或 grpc。Java Agent 2.x 默认使用 http/protobuf,建议仍显式设置。 |
OTEL_EXPORTER_OTLP_ENDPOINT |
otel.exporter.otlp.endpoint |
所有信号共用的基础地址。 | HTTP:http://datakit-host:9529/otel;gRPC:http://datakit-host:4317。 |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
otel.exporter.otlp.traces.endpoint |
仅 Trace 使用的地址,优先于共用地址。 | HTTP:http://datakit-host:9529/otel/v1/traces。 |
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
otel.exporter.otlp.metrics.endpoint |
仅 Metric 使用的地址,优先于共用地址。 | HTTP:http://datakit-host:9529/otel/v1/metrics。 |
OTEL_EXPORTER_OTLP_HEADERS |
otel.exporter.otlp.headers |
所有 OTLP 请求携带的请求头,多个值用逗号分隔。 | x-tenant=tenant-a;需与 DataKit expected_headers 一致。 |
OTEL_EXPORTER_OTLP_COMPRESSION |
otel.exporter.otlp.compression |
OTLP 请求压缩方式。 | 跨主机上报可设为 gzip。 |
OTEL_EXPORTER_OTLP_TIMEOUT |
otel.exporter.otlp.timeout |
单次导出超时,单位毫秒。 | 默认 10000。 |
统一 endpoint 与某类数据的 endpoint 同时存在时,某类数据的配置优先。例如设置了 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT 后,Trace 不再使用 OTEL_EXPORTER_OTLP_ENDPOINT。
DataKit 的 OTLP/HTTP 采集仅支持 Protobuf,请使用 http/protobuf,不要使用 http/json。使用 HTTP 的统一 endpoint 时应设置为 http://<DataKit-IP>:9529/otel;如果设置某类数据的独立 endpoint,则必须包含完整的 /otel/v1/<signal> 路径。
采样和批量上报参数¶
| 环境变量 | JVM 系统属性 | 说明 | 默认值或示例 |
|---|---|---|---|
OTEL_TRACES_SAMPLER |
otel.traces.sampler |
Trace 头部采样器。 | 默认 parentbased_always_on;按比例采样可用 parentbased_traceidratio。 |
OTEL_TRACES_SAMPLER_ARG |
otel.traces.sampler.arg |
采样器参数。 | 0.1 表示根 Trace 采样 10%。 |
OTEL_BSP_SCHEDULE_DELAY |
otel.bsp.schedule.delay |
Span 批量导出间隔,单位毫秒。 | 默认 5000。 |
OTEL_BSP_MAX_QUEUE_SIZE |
otel.bsp.max.queue.size |
待导出的 Span 队列上限。 | 默认 2048。 |
OTEL_BSP_MAX_EXPORT_BATCH_SIZE |
otel.bsp.max.export.batch.size |
每批最多导出的 Span 数。 | 默认 512,不能大于队列上限。 |
OTEL_BSP_EXPORT_TIMEOUT |
otel.bsp.export.timeout |
Span 批量导出超时,单位毫秒。 | 默认 30000。 |
OTEL_METRIC_EXPORT_INTERVAL |
otel.metric.export.interval |
Metric 导出间隔,单位毫秒。 | 默认 60000。 |
生产环境应根据流量和数据预算设置采样率。应用侧头部采样与 DataKit 侧采样同时开启时,最终保留率会叠加降低,应统一规划采样位置。
日志和调试参数¶
| 环境变量 | JVM 系统属性 | 说明 | 默认值或示例 |
|---|---|---|---|
OTEL_JAVAAGENT_LOGGING |
otel.javaagent.logging |
Agent 自身日志输出方式。 | 默认 simple;可选 none、application。 |
OTEL_JAVAAGENT_DEBUG |
otel.javaagent.debug |
输出详细的 Agent 调试日志。 | 默认 false;仅排障时临时设为 true。 |
OTEL_JAVAAGENT_LOGGING 控制的是 Java Agent 自身的诊断日志,与应用日志采集方式无关。本文的应用日志由 DataKit 从文件采集,OTEL_LOGS_EXPORTER 应保持为 none。
验证接入¶
- 启动应用,确认标准错误中出现
opentelemetry-javaagent - version,且没有 OTLP export error; - 请求一个会经过 Web 框架、HTTP 客户端或数据库的应用接口,同时在请求处理代码中产生一条应用日志;
- 检查日志文件,确认请求范围内的日志含有非空的
trace_id和span_id:
- 使用 HTTP 上报时,在 DataKit 主机检查 OTLP 接收日志:
出现 /otel/v1/traces 或 /otel/v1/metrics 的 POST 请求且响应码为 200,表示 DataKit 已接收数据。
- 进入观测云的「日志」查看器,按
service:order-service查询,打开一条请求日志并确认trace_id、span_id已被提取为字段;再通过日志详情中的关联链路入口查看对应 Trace。也可以进入「应用性能监测 > 链路」按service:order-service查询,再查看链路关联日志。
如果 Trace 没有产生,可临时增加 OTEL_JAVAAGENT_DEBUG=true 后重启应用,检查 Agent 加载、插桩匹配、endpoint、协议和网络错误。如果日志存在但没有链路字段,应确认日志是在有效 Span 范围内产生、实际使用的日志配置文件包含 MDC 格式,并且应用没有禁用对应日志框架的自动插桩。如果文件中有链路字段但观测云中没有,应检查 DataKit 文件读取权限、日志路径和 Pipeline 匹配结果。排障结束后应关闭 debug,避免大量日志和额外性能开销。