Java 语言 OpenTelemetry 接入指南

本文档提供 Java 应用接入 OpenTelemetry 的完整指南,重点介绍使用 Java Agent 进行零代码接入,以及 SDK 手动接入方式。

目录


概述

Java 语言特性

Java 语言的 OpenTelemetry 支持具有以下特点:

  • 成熟稳定: Java Agent 和 SDK 已达到 1.0 稳定版本
  • 零代码接入: 通过 Java Agent 实现完全零侵入
  • 广泛支持: 支持 100+ 主流 Java 框架和库
  • 生产就绪: 在大量生产环境中验证
  • Spring Boot 集成: 官方提供 Spring Boot Starter

版本要求

  • Java 版本: Java 8+ (推荐 Java 17+)
  • OpenTelemetry Java: v1.30.0+
  • Java Agent: v2.0.0+
  • 支持架构: x86_64, ARM64

支持的信号

  • ✅ Traces: 完全支持
  • ✅ Metrics: 完全支持
  • ✅ Logs: 完全支持(Java 9+)

适用场景

  • Spring Boot 应用
  • 微服务架构
  • 传统 Java EE 应用
  • 批处理作业
  • 消息队列应用

零代码接入(推荐)

Java Agent 是 Java 零代码接入的最佳方式,通过字节码增强技术实现完全零侵入的自动埋点。

下载 Java Agent

方法 1: 从 Maven Central 下载

# 下载最新版本
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
# 或指定版本
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.0.0/opentelemetry-javaagent.jar

方法 2: 使用 Maven/Gradle 依赖

Maven:

<dependency>
<groupId>io.opentelemetry.javaagent</groupId>
<artifactId>opentelemetry-javaagent</artifactId>
<version>2.0.0</version>
<type>pom</type>
</dependency>

Gradle:

dependencies {
implementation platform('io.opentelemetry.javaagent:opentelemetry-javaagent-bom:2.0.0')
}

快速开始

1. 配置环境变量

# 服务信息
export OTEL_SERVICE_NAME=my-java-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
# OTLP Exporter
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Metrics 配置
export OTEL_METRICS_EXPORTER=otlp
export OTEL_METRIC_EXPORT_INTERVAL=60000
# Logs 配置(Java 9+)
export OTEL_LOGS_EXPORTER=otlp

2. 启动应用

# 使用 -javaagent 参数
java -javaagent:opentelemetry-javaagent.jar \
-jar my-application.jar
# 或指定 Agent 路径
java -javaagent:/path/to/opentelemetry-javaagent.jar \
-jar my-application.jar

就这么简单!您的应用现在已经自动埋点并开始发送遥测数据了。

支持的框架和库

Java Agent 自动支持 100+ 框架和库,包括:

Web 框架

  • Spring Boot: 2.0+, 3.0+
  • Spring MVC: 3.1+
  • Spring WebFlux: 5.0+
  • JAX-RS: Jersey, RESTEasy, Apache CXF
  • Servlet: 2.3+
  • Vert.x: 3.0+, 4.0+
  • Quarkus: 1.0+
  • Micronaut: 1.0+

数据库

  • JDBC: 所有实现
  • Hibernate: 3.3+
  • JPA: 所有实现
  • MongoDB: 3.0+, 4.0+
  • Redis: Jedis, Lettuce
  • Elasticsearch: 5.0+, 6.0+, 7.0+, 8.0+
  • Cassandra: 3.0+, 4.0+

RPC 框架

  • gRPC: 1.0+
  • Apache Dubbo: 2.7+
  • Apache Thrift: 0.8+
  • Spring Cloud OpenFeign: 2.0+

消息队列

  • Kafka: 0.11+, 1.0+, 2.0+, 3.0+
  • RabbitMQ: 5.0+
  • RocketMQ: 4.0+, 5.0+
  • Apache Pulsar: 2.0+
  • JMS: 1.1+, 2.0+

HTTP 客户端

  • Apache HttpClient: 4.0+, 5.0+
  • OkHttp: 3.0+, 4.0+
  • Spring RestTemplate: 3.0+
  • Spring WebClient: 5.0+
  • Netty: 4.0+, 4.1+

其他

  • Logback: 1.0+
  • Log4j2: 2.0+
  • SLF4J: 1.0+
  • Quartz: 2.0+
  • Akka: 2.5+, 2.6+

完整列表: https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md

环境变量配置

必需配置

# 服务名称(必需)
export OTEL_SERVICE_NAME=my-java-service
# OTLP Endpoint(必需)
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317

推荐配置

# 服务信息
export OTEL_SERVICE_NAME=my-java-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production,service.namespace=backend
# OTLP 配置
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
export OTEL_EXPORTER_OTLP_TIMEOUT=10000
# Traces 配置
export OTEL_TRACES_EXPORTER=otlp
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
# Metrics 配置
export OTEL_METRICS_EXPORTER=otlp
export OTEL_METRIC_EXPORT_INTERVAL=60000
# Logs 配置(Java 9+)
export OTEL_LOGS_EXPORTER=otlp
# 日志级别
export OTEL_LOG_LEVEL=info
# 禁用特定埋点库(可选)
export OTEL_INSTRUMENTATION_KAFKA_ENABLED=false
export OTEL_INSTRUMENTATION_REDIS_ENABLED=true

高级配置

# 批量导出配置
export OTEL_BSP_SCHEDULE_DELAY=5000
export OTEL_BSP_MAX_QUEUE_SIZE=2048
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
export OTEL_BSP_EXPORT_TIMEOUT=30000
# Span 限制
export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=128
export OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT=1024
export OTEL_SPAN_EVENT_COUNT_LIMIT=128
# Propagators
export OTEL_PROPAGATORS=tracecontext,baggage
# 禁用 SDK(用于测试)
export OTEL_SDK_DISABLED=false

启动方式

方式 1: 命令行参数

java -javaagent:opentelemetry-javaagent.jar \
-jar my-application.jar

方式 2: 系统属性

java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=my-service \
-Dotel.exporter.otlp.endpoint=uptrace-grpc.yodao.cn:4317 \
-Dotel.exporter.otlp.headers=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project \
-jar my-application.jar

方式 3: 配置文件

创建 otel.properties 文件:

otel.service.name=my-service
otel.exporter.otlp.endpoint=uptrace-grpc.yodao.cn:4317
otel.exporter.otlp.headers=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
otel.traces.sampler=parentbased_traceidratio
otel.traces.sampler.arg=0.1

使用配置文件:

java -javaagent:opentelemetry-javaagent.jar \
-Dotel.configuration.file=otel.properties \
-jar my-application.jar

方式 4: Spring Boot

application.properties:

otel.service.name=my-service
otel.exporter.otlp.endpoint=uptrace-grpc.yodao.cn:4317
otel.exporter.otlp.headers=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project

启动脚本:

java -javaagent:opentelemetry-javaagent.jar \
-jar my-spring-boot-app.jar

方式 5: Docker

FROM openjdk:17-jre-slim
# 复制 Agent
COPY opentelemetry-javaagent.jar /app/
# 复制应用
COPY my-application.jar /app/
# 设置环境变量
ENV OTEL_SERVICE_NAME=my-service
ENV OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
ENV OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
# 启动应用
ENTRYPOINT ["java", "-javaagent:/app/opentelemetry-javaagent.jar", "-jar", "/app/my-application.jar"]

方式 6: Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: app
image: my-java-app:latest
env:
- name: OTEL_SERVICE_NAME
value: my-service
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: uptrace-grpc.yodao.cn:4317
- name: OTEL_EXPORTER_OTLP_HEADERS
valueFrom:
secretKeyRef:
name: apm-credentials
key: otlp-headers
volumeMounts:
- name: javaagent
mountPath: /app/javaagent.jar
subPath: opentelemetry-javaagent.jar
command:
- java
- -javaagent:/app/javaagent.jar
- -jar
- /app/my-application.jar
volumes:
- name: javaagent
configMap:
name: javaagent
---
apiVersion: v1
kind: ConfigMap
metadata:
name: javaagent
data:
opentelemetry-javaagent.jar: |
# 二进制内容(从文件读取)

使用 OpenTelemetry Operator

如果使用 Kubernetes,推荐使用 OpenTelemetry Operator 自动注入 Agent。

安装 Operator

kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml

创建 Instrumentation 资源

apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
name: my-instrumentation
namespace: default
spec:
exporter:
endpoint: uptrace-grpc.yodao.cn:4317
env:
- name: OTEL_EXPORTER_OTLP_HEADERS
value: uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
propagators:
- tracecontext
- baggage
sampler:
type: parentbased_traceidratio
argument: "0.1"
java:
image: ghcr.io/open-telemetry/opentelemetry-java-instrumentation:latest

部署应用

在 Pod 上添加 annotation:

apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: app
image: my-java-app:latest

验证零代码接入

启动应用后,访问您的服务端点,然后在 APM 平台查看数据:

  1. Traces: 查看调用链路
  2. Metrics: 查看请求量、延迟等指标
  3. Service Map: 查看服务拓扑图

快速验证脚本

#!/bin/bash
# verify-java-instrumentation.sh
echo "Checking service..."
curl -s http://localhost:8080/actuator/health > /dev/null
echo "Waiting for traces to be exported..."
sleep 10
echo "Check your APM platform for service: $OTEL_SERVICE_NAME"
echo "Endpoint: $OTEL_EXPORTER_OTLP_ENDPOINT"

SDK 手动接入

如果需要更细粒度的控制或自定义埋点,可以使用 SDK 手动接入。

添加依赖

Maven

<dependencies>
<!-- OpenTelemetry API -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.30.0</version>
</dependency>
<!-- OpenTelemetry SDK -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.30.0</version>
</dependency>
<!-- OTLP Exporter -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.30.0</version>
</dependency>
<!-- 埋点库(可选) -->
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>

Gradle

dependencies {
// OpenTelemetry API
implementation 'io.opentelemetry:opentelemetry-api:1.30.0'
// OpenTelemetry SDK
implementation 'io.opentelemetry:opentelemetry-sdk:1.30.0'
// OTLP Exporter
implementation 'io.opentelemetry:opentelemetry-exporter-otlp:1.30.0'
// 埋点库(可选)
implementation 'io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter:2.0.0'
}

初始化 OpenTelemetry

基础初始化

package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.semconv.ResourceAttributes;
import java.util.concurrent.TimeUnit;
public class OpenTelemetryConfig {
public static OpenTelemetry initializeOpenTelemetry() {
// 1. 创建 Resource
Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.of(
ResourceAttributes.SERVICE_NAME, "my-java-service",
ResourceAttributes.SERVICE_VERSION, "1.0.0",
ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "production"
)));
// 2. 创建 OTLP Exporter
OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("uptrace-grpc.yodao.cn:4317")
.addHeader("uptrace-dsn", "http://token@uptrace-grpc.yodao.cn/project")
.setTimeout(10, TimeUnit.SECONDS)
.build();
// 3. 创建 Tracer Provider
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setResource(resource)
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter)
.setScheduleDelay(5, TimeUnit.SECONDS)
.setMaxExportBatchSize(512)
.setExportTimeout(30, TimeUnit.SECONDS)
.build())
.build();
// 4. 创建 OpenTelemetry 实例
OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.setPropagators(ContextPropagators.create(
W3CTraceContextPropagator.getInstance()
))
.buildAndRegisterGlobal();
// 5. 注册 Shutdown Hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
tracerProvider.shutdown();
}));
return openTelemetry;
}
}

在 main 方法中初始化

package com.example;
import io.opentelemetry.api.OpenTelemetry;
public class Application {
public static void main(String[] args) {
// 初始化 OpenTelemetry
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
// 启动应用
SpringApplication.run(Application.class, args);
}
}

Traces - 手动埋点

创建和使用 Span

package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;
public class OrderService {
private static final Tracer tracer =
OpenTelemetry.getGlobalTracerProvider()
.get("my-java-service");
public void processOrder(String orderId) {
// 创建 Span
Span span = tracer.spanBuilder("process_order")
.setAttribute("order.id", orderId)
.setAttribute("operation", "process")
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 添加事件
span.addEvent("order_validation_started");
// 验证订单
validateOrder(orderId);
span.addEvent("order_validated");
// 处理支付
processPayment(orderId);
span.addEvent("payment_processed");
// 设置成功状态
span.setStatus(StatusCode.OK);
span.setAttribute("order.success", true);
} catch (Exception e) {
// 记录错误
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
private void validateOrder(String orderId) {
Span span = tracer.spanBuilder("validate_order")
.setAttribute("order.id", orderId)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 业务逻辑
} finally {
span.end();
}
}
private void processPayment(String orderId) {
Span span = tracer.spanBuilder("process_payment")
.setAttribute("order.id", orderId)
.setAttribute("payment.method", "credit_card")
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 支付逻辑
} finally {
span.end();
}
}
}

使用 @WithSpan 注解(推荐)

package com.example;
import io.opentelemetry.extension.annotations.WithSpan;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@WithSpan("process_order")
public void processOrder(@SpanAttribute("order.id") String orderId) {
validateOrder(orderId);
processPayment(orderId);
}
@WithSpan("validate_order")
private void validateOrder(@SpanAttribute("order.id") String orderId) {
// 业务逻辑
}
@WithSpan("process_payment")
private void processPayment(@SpanAttribute("order.id") String orderId) {
// 支付逻辑
}
}

Metrics - 指标采集

Counter(计数器)

package com.example;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
public class MetricsExample {
private static final Meter meter =
OpenTelemetry.getGlobalMeterProvider()
.meterBuilder("my-java-service")
.build();
private static final LongCounter requestCounter =
meter.counterBuilder("http.server.requests")
.setDescription("Total number of HTTP requests")
.setUnit("1")
.build();
public void handleRequest(String method, String route, int statusCode) {
requestCounter.add(1, Attributes.of(
AttributeKey.stringKey("http.method"), method,
AttributeKey.stringKey("http.route"), route,
AttributeKey.longKey("http.status_code"), statusCode
));
}
}

Histogram(直方图)

package com.example;
import io.opentelemetry.api.metrics.DoubleHistogram;
public class MetricsExample {
private static final DoubleHistogram requestDuration =
meter.histogramBuilder("http.server.duration")
.setDescription("HTTP request duration")
.setUnit("ms")
.build();
public void recordDuration(double durationMs, String method, String route) {
requestDuration.record(durationMs, Attributes.of(
AttributeKey.stringKey("http.method"), method,
AttributeKey.stringKey("http.route"), route
));
}
}

Logs - 日志集成

集成 Logback

<!-- logback-spring.xml -->
<configuration>
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
<captureExperimentalAttributes>true</captureExperimentalAttributes>
<captureKeyValuePairAttributes>true</captureKeyValuePairAttributes>
</appender>
<root level="INFO">
<appender-ref ref="OTEL" />
</root>
</configuration>

集成 Log4j2

<!-- log4j2.xml -->
<Configuration>
<Appenders>
<OpenTelemetryAppender name="OTEL">
<captureExperimentalAttributes>true</captureExperimentalAttributes>
</OpenTelemetryAppender>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="OTEL" />
</Root>
</Loggers>
</Configuration>

框架集成示例

Spring Boot

使用 Spring Boot Starter(推荐)

添加依赖:

<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>

application.properties:

otel.service.name=my-spring-boot-service
otel.exporter.otlp.endpoint=uptrace-grpc.yodao.cn:4317
otel.exporter.otlp.headers=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
otel.traces.sampler=parentbased_traceidratio
otel.traces.sampler.arg=0.1

Controller 示例:

package com.example;
import org.springframework.web.bind.annotation.*;
import io.opentelemetry.extension.annotations.WithSpan;
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
@WithSpan("get_user")
public User getUser(@PathVariable String id) {
return userService.findById(id);
}
@PostMapping
@WithSpan("create_user")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}

gRPC Server

package com.example;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.instrumentation.grpc.v1_6.GrpcTelemetry;
public class GrpcServer {
public static void main(String[] args) throws Exception {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
GrpcTelemetry grpcTelemetry = GrpcTelemetry.create(openTelemetry);
Server server = ServerBuilder.forPort(50051)
.intercept(grpcTelemetry.newServerInterceptor())
.addService(new UserServiceImpl())
.build();
server.start();
server.awaitTermination();
}
}

Kafka Consumer

package com.example;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import io.opentelemetry.instrumentation.kafkaclients.v2_6.KafkaTelemetry;
public class KafkaConsumerExample {
public static void main(String[] args) {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
KafkaTelemetry kafkaTelemetry = KafkaTelemetry.create(openTelemetry);
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "my-group");
KafkaConsumer<String, String> consumer =
kafkaTelemetry.wrap(new KafkaConsumer<>(props));
consumer.subscribe(Collections.singletonList("my-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
processMessage(record);
}
}
}
}

Database - JDBC

使用 Java Agent 时,JDBC 调用会自动追踪。手动接入示例:

package com.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
public class DatabaseExample {
private static final Tracer tracer =
OpenTelemetry.getGlobalTracerProvider()
.get("my-java-service");
public User getUser(String id) throws Exception {
Span span = tracer.spanBuilder("database.query")
.setAttribute("db.system", "postgresql")
.setAttribute("db.operation", "SELECT")
.startSpan();
try (Scope scope = span.makeCurrent()) {
Connection conn = DriverManager.getConnection(
"jdbc:postgresql://localhost:5432/mydb",
"user", "password"
);
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE id = ?"
);
stmt.setString(1, id);
span.setAttribute("db.statement", "SELECT * FROM users WHERE id = ?");
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
User user = new User();
user.setId(rs.getString("id"));
user.setName(rs.getString("name"));
span.setStatus(StatusCode.OK);
return user;
}
span.setStatus(StatusCode.OK);
return null;
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
} finally {
span.end();
}
}
}

Database - Hibernate/JPA

使用 Java Agent 时,Hibernate 调用会自动追踪。手动接入示例:

package com.example;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import io.opentelemetry.extension.annotations.WithSpan;
@Repository
public interface UserRepository extends JpaRepository<User, String> {
@WithSpan("find_user_by_email")
User findByEmail(String email);
}

HTTP Client - Apache HttpClient

package com.example;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import io.opentelemetry.instrumentation.apachehttpclient.v4_3.ApacheHttpClientTelemetry;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
ApacheHttpClientTelemetry telemetry =
ApacheHttpClientTelemetry.create(openTelemetry);
CloseableHttpClient httpClient =
telemetry.newHttpClient(HttpClients.createDefault());
HttpGet request = new HttpGet("https://api.example.com/users/123");
try (CloseableHttpResponse response = httpClient.execute(request)) {
// 处理响应
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("Status: " + statusCode);
}
}
}

HTTP Client - OkHttp

package com.example;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import io.opentelemetry.instrumentation.okhttp.v3_0.OkHttpTelemetry;
public class OkHttpExample {
public static void main(String[] args) throws Exception {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
OkHttpTelemetry telemetry = OkHttpTelemetry.create(openTelemetry);
OkHttpClient client = telemetry.newCallFactory(new OkHttpClient());
Request request = new Request.Builder()
.url("https://api.example.com/users/123")
.build();
try (Response response = client.newCall(request).execute()) {
// 处理响应
System.out.println("Status: " + response.code());
}
}
}

Redis - Jedis

package com.example;
import redis.clients.jedis.Jedis;
import io.opentelemetry.instrumentation.jedis.v4_0.JedisTelemetry;
public class RedisExample {
public static void main(String[] args) {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
JedisTelemetry telemetry = JedisTelemetry.create(openTelemetry);
Jedis jedis = new Jedis("localhost", 6379);
Jedis instrumentedJedis = telemetry.wrap(jedis);
// 自动追踪 Redis 操作
instrumentedJedis.set("key", "value");
String value = instrumentedJedis.get("key");
System.out.println("Value: " + value);
}
}

Redis - Lettuce

package com.example;
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import io.opentelemetry.instrumentation.lettuce.v5_1.LettuceTelemetry;
public class LettuceExample {
public static void main(String[] args) {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
LettuceTelemetry telemetry = LettuceTelemetry.create(openTelemetry);
RedisClient client = RedisClient.create("redis://localhost:6379");
StatefulRedisConnection<String, String> connection =
telemetry.wrap(client.connect());
RedisCommands<String, String> commands = connection.sync();
// 自动追踪 Redis 操作
commands.set("key", "value");
String value = commands.get("key");
System.out.println("Value: " + value);
}
}

MongoDB

package com.example;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import io.opentelemetry.instrumentation.mongo.v3_1.MongoTelemetry;
public class MongoExample {
public static void main(String[] args) {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
MongoTelemetry telemetry = MongoTelemetry.create(openTelemetry);
MongoClient mongoClient = telemetry.wrap(
MongoClients.create("mongodb://localhost:27017")
);
MongoDatabase database = mongoClient.getDatabase("mydb");
MongoCollection<Document> collection = database.getCollection("users");
// 自动追踪 MongoDB 操作
Document user = collection.find(eq("id", "123")).first();
System.out.println("User: " + user);
}
}

RabbitMQ

package com.example;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import io.opentelemetry.instrumentation.rabbitmq.v2_7.RabbitMQTelemetry;
public class RabbitMQExample {
public static void main(String[] args) throws Exception {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
RabbitMQTelemetry telemetry = RabbitMQTelemetry.create(openTelemetry);
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = telemetry.wrap(connection.createChannel());
// 自动追踪 RabbitMQ 操作
channel.queueDeclare("my-queue", false, false, false, null);
channel.basicPublish("", "my-queue", null, "Hello World".getBytes());
System.out.println("Message sent");
}
}

WebFlux (Reactive)

package com.example;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import io.opentelemetry.instrumentation.spring.webflux.v5_3.SpringWebfluxTelemetry;
@RestController
@RequestMapping("/api")
public class WebFluxController {
private final WebClient webClient;
public WebFluxController(WebClient.Builder webClientBuilder) {
OpenTelemetry openTelemetry = OpenTelemetryConfig.initializeOpenTelemetry();
SpringWebfluxTelemetry telemetry =
SpringWebfluxTelemetry.create(openTelemetry);
this.webClient = telemetry.wrap(webClientBuilder.build());
}
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable String id) {
// 自动追踪 WebFlux 调用
return webClient.get()
.uri("https://api.example.com/users/{id}", id)
.retrieve()
.bodyToMono(User.class);
}
}

高级配置

自定义采样策略

import io.opentelemetry.sdk.trace.samplers.Sampler;
public class CustomSampler implements Sampler {
@Override
public SamplingResult shouldSample(
Context parentContext,
String traceId,
String name,
SpanKind spanKind,
Attributes attributes,
List<LinkData> parentLinks) {
// 自定义采样逻辑
if (name.contains("/health")) {
return SamplingResult.create(Decision.DROP);
}
if (attributes.get(AttributeKey.stringKey("important")) != null) {
return SamplingResult.create(Decision.RECORD_AND_SAMPLE);
}
return SamplingResult.create(Decision.RECORD_ONLY);
}
@Override
public String getDescription() {
return "CustomSampler";
}
}

使用自定义采样器

SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.setSampler(new CustomSampler())
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();

多 Exporter 配置

import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
// 创建多个 Exporter
OtlpGrpcSpanExporter otlpExporter = OtlpGrpcSpanExporter.builder()
.setEndpoint("uptrace-grpc.yodao.cn:4317")
.addHeader("uptrace-dsn", "http://token@uptrace-grpc.yodao.cn/project")
.build();
JaegerGrpcSpanExporter jaegerExporter = JaegerGrpcSpanExporter.builder()
.setEndpoint("http://localhost:14250")
.build();
LoggingSpanExporter consoleExporter = LoggingSpanExporter.create();
// 添加多个处理器
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(otlpExporter).build())
.addSpanProcessor(BatchSpanProcessor.builder(jaegerExporter).build())
.addSpanProcessor(BatchSpanProcessor.builder(consoleExporter).build())
.build();

性能优化配置

SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(
BatchSpanProcessor.builder(spanExporter)
.setMaxExportBatchSize(512) // 批量大小
.setScheduleDelay(5, TimeUnit.SECONDS) // 批量延迟
.setExportTimeout(30, TimeUnit.SECONDS) // 导出超时
.setMaxQueueSize(2048) // 队列大小
.build()
)
.setSampler(Sampler.parentBased(
Sampler.traceIdRatioBased(0.1) // 10% 采样
))
.build();

资源检测

Resource resource = Resource.getDefault()
.merge(Resource.create(Attributes.of(
ResourceAttributes.SERVICE_NAME, "my-service",
ResourceAttributes.SERVICE_VERSION, "1.0.0",
ResourceAttributes.DEPLOYMENT_ENVIRONMENT, "production"
)))
.merge(Resource.getDefault()
.merge(Resource.create(Attributes.empty()))
.merge(Resource.create(Attributes.of(
ResourceAttributes.HOST_NAME, System.getenv("HOSTNAME"),
ResourceAttributes.OS_TYPE, System.getProperty("os.name")
)))
);

禁用特定埋点库

# 通过环境变量禁用
export OTEL_INSTRUMENTATION_KAFKA_ENABLED=false
export OTEL_INSTRUMENTATION_REDIS_ENABLED=false
export OTEL_INSTRUMENTATION_JDBC_ENABLED=true

自定义属性添加

import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.data.SpanData;
public class AttributeAddingProcessor implements SpanProcessor {
@Override
public void onStart(Context parentContext, ReadWriteSpan span) {
span.setAttribute("custom.attribute", "value");
span.setAttribute("environment", System.getenv("ENV"));
}
@Override
public boolean isStartRequired() {
return true;
}
@Override
public void onEnd(ReadableSpan span) {
// 处理结束的 Span
}
@Override
public boolean isEndRequired() {
return false;
}
@Override
public CompletableResultCode shutdown() {
return CompletableResultCode.ofSuccess();
}
@Override
public CompletableResultCode forceFlush() {
return CompletableResultCode.ofSuccess();
}
}
// 使用
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(new AttributeAddingProcessor())
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
.build();

最佳实践

1. 使用 Java Agent(推荐)

对于大多数场景,推荐使用 Java Agent 进行零代码接入:

java -javaagent:opentelemetry-javaagent.jar -jar my-app.jar

优点:

  • 零代码修改
  • 自动支持 100+ 框架
  • 统一配置管理
  • 生产就绪

2. Context 传播

确保在异步操作中正确传播 Context:

// ✅ 正确:传递 Context
CompletableFuture.supplyAsync(() -> {
try (Scope scope = span.makeCurrent()) {
return processOrder(orderId);
}
}, executor);
// ❌ 错误:创建新的 Context
CompletableFuture.supplyAsync(() -> {
Span newSpan = tracer.spanBuilder("operation").startSpan();
// 丢失了父 Span 的关联
return processOrder(orderId);
}, executor);

3. 使用 try-with-resources

确保 Span 正确结束:

// ✅ 正确:使用 try-with-resources
Span span = tracer.spanBuilder("operation").startSpan();
try (Scope scope = span.makeCurrent()) {
// 业务逻辑
} finally {
span.end();
}
// ✅ 更简洁:使用 @WithSpan 注解
@WithSpan("operation")
public void operation() {
// 业务逻辑
}

4. 错误处理

// ✅ 正确:记录异常并设置状态
try {
processOrder(orderId);
span.setStatus(StatusCode.OK);
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getMessage());
throw e;
}

5. 属性命名

使用语义约定:

// ✅ 正确:使用语义约定
span.setAttribute(SemanticAttributes.HTTP_METHOD, "GET");
span.setAttribute(SemanticAttributes.HTTP_URL, "https://api.example.com/users");
span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, 200L);
// ❌ 错误:使用自定义键名
span.setAttribute("method", "GET");
span.setAttribute("url", "https://api.example.com/users");

6. 避免过度埋点

// ✅ 正确:只在关键路径埋点
@WithSpan("process_order")
public void processOrder(String orderId) {
validateOrder(orderId);
chargePayment(orderId);
}
// ❌ 错误:对每个小方法都埋点
@WithSpan("validate_id")
private void validateId(String id) { } // 太细粒度
@WithSpan("check_format")
private void checkFormat(String id) { } // 太细粒度

7. 资源管理

确保在应用关闭时正确关闭 OpenTelemetry:

@PreDestroy
public void shutdown() {
SdkTracerProvider tracerProvider =
(SdkTracerProvider) OpenTelemetry.getGlobalTracerProvider();
tracerProvider.shutdown();
}

8. Spring Boot 集成

使用 Spring Boot Starter 简化配置:

<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
# application.properties
otel.service.name=my-service
otel.exporter.otlp.endpoint=uptrace-grpc.yodao.cn:4317
otel.exporter.otlp.headers=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project

故障排查

常见问题

1. 数据未发送到后端

症状: APM 平台看不到数据

排查步骤:

  1. 检查环境变量:
echo $OTEL_SERVICE_NAME
echo $OTEL_EXPORTER_OTLP_ENDPOINT
echo $OTEL_EXPORTER_OTLP_HEADERS
  1. 检查 Java Agent 是否正确加载:
# 启动时应该看到类似日志
# [otel.javaagent] Starting OpenTelemetry Javaagent
  1. 启用调试日志:
export OTEL_LOG_LEVEL=debug
  1. 使用 Console Exporter 验证:
export OTEL_TRACES_EXPORTER=logging
  1. 检查采样率:
# 临时设置为 100% 采样
export OTEL_TRACES_SAMPLER_ARG=1.0

解决方案:

  • 确认 endpoint 地址正确
  • 确认 headers 包含正确的认证信息
  • 检查防火墙规则
  • 验证 DSN 是否有效

2. Span 未关联

症状: Trace 中的 Span 没有父子关系

原因: Context 传递不正确

解决方案:

// ✅ 正确:使用 try-with-resources 传递 Context
Span parentSpan = tracer.spanBuilder("parent").startSpan();
try (Scope parentScope = parentSpan.makeCurrent()) {
Span childSpan = tracer.spanBuilder("child").startSpan();
try (Scope childScope = childSpan.makeCurrent()) {
// 业务逻辑
} finally {
childSpan.end();
}
} finally {
parentSpan.end();
}

3. 内存泄漏

症状: 内存使用持续增长

可能原因:

  • Span 未正确结束
  • 队列积压
  • 资源未释放

解决方案:

  1. 确保 Span 正确结束:
// ✅ 正确:使用 try-with-resources
Span span = tracer.spanBuilder("operation").startSpan();
try (Scope scope = span.makeCurrent()) {
// 业务逻辑
} finally {
span.end();
}
  1. 调整队列大小:
export OTEL_BSP_MAX_QUEUE_SIZE=1024
export OTEL_BSP_SCHEDULE_DELAY=2000
  1. 检查资源释放:
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
SdkTracerProvider tracerProvider =
(SdkTracerProvider) OpenTelemetry.getGlobalTracerProvider();
tracerProvider.shutdown();
}));

4. 高 CPU 使用率

症状: CPU 使用率异常高

可能原因:

  • 采样率过高
  • 同步导出
  • 属性处理过多

解决方案:

  1. 降低采样率:
export OTEL_TRACES_SAMPLER_ARG=0.01 # 1%
  1. 确保异步导出:
// ✅ 正确:使用 BatchSpanProcessor
BatchSpanProcessor.builder(spanExporter).build()
// ❌ 错误:使用 SimpleSpanProcessor
SimpleSpanProcessor.create(spanExporter)
  1. 限制属性数量:
export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT=64

5. 框架未自动埋点

症状: 某些框架的调用没有被追踪

可能原因:

  • 框架版本不支持
  • 埋点库未启用
  • 配置不正确

解决方案:

  1. 检查框架支持:

  2. 启用埋点库:

# 检查是否被禁用
echo $OTEL_INSTRUMENTATION_SPRING_WEB_ENABLED
# 确保未禁用
export OTEL_INSTRUMENTATION_SPRING_WEB_ENABLED=true
  1. 检查 Java Agent 版本:
# 确保使用最新版本
java -javaagent:opentelemetry-javaagent.jar -version

6. Spring Boot 集成问题

症状: Spring Boot 应用未自动埋点

解决方案:

  1. 检查依赖:
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
  1. 检查配置:
# application.properties
otel.service.name=my-service
otel.exporter.otlp.endpoint=uptrace-grpc.yodao.cn:4317
  1. 检查 Java Agent:
# 确保使用 -javaagent 参数
java -javaagent:opentelemetry-javaagent.jar -jar app.jar

7. 认证失败

症状: 收到认证错误

错误信息:

authentication failed: invalid DSN

解决方案:

  1. 检查 DSN 格式:
echo $OTEL_EXPORTER_OTLP_HEADERS
# 应该包含: uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
  1. 验证 Token 有效性:

    • 联系管理员确认 Token 是否有效
    • 检查 Token 是否过期
  2. 检查 Headers 格式:

# ✅ 正确格式
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
# ❌ 错误格式
export OTEL_EXPORTER_OTLP_HEADERS=api-key=token

调试技巧

1. 启用详细日志

export OTEL_LOG_LEVEL=debug
export OTEL_JAVAAGENT_DEBUG=true

2. 使用 Console Exporter

export OTEL_TRACES_EXPORTER=logging
export OTEL_METRICS_EXPORTER=logging
export OTEL_LOGS_EXPORTER=logging

3. 检查 Java Agent 加载

启动应用时应该看到:

[otel.javaagent] Starting OpenTelemetry Javaagent 2.0.0
[otel.javaagent] OpenTelemetry Javaagent initialized

4. 使用 JVM 参数

java -javaagent:opentelemetry-javaagent.jar \
-Dotel.javaagent.debug=true \
-Dotel.log.level=debug \
-jar my-app.jar

5. 检查埋点库状态

# 查看启用的埋点库
export OTEL_JAVAAGENT_DEBUG=true
# 启动应用,查看日志中的 "Instrumenting" 信息

环境变量完整示例

# 开发环境 - 完整调试
export OTEL_LOG_LEVEL=debug
export OTEL_SERVICE_NAME=my-service-dev
export OTEL_TRACES_SAMPLER=always_on
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_TRACES_EXPORTER=otlp,logging
# 生产环境 - 优化性能
export OTEL_LOG_LEVEL=warn
export OTEL_SERVICE_NAME=my-service
export OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
export OTEL_EXPORTER_OTLP_ENDPOINT=uptrace-grpc.yodao.cn:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS=uptrace-dsn=http://token@uptrace-grpc.yodao.cn/project
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
export OTEL_BSP_MAX_QUEUE_SIZE=2048
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
export OTEL_BSP_SCHEDULE_DELAY=5000

下一步

参考资源

在Gitlab上编辑本文档