效果展示
概述
在现代云原生架构中,可观测性不再是可选项,而是一项基本要求。你需要通过指标了解应用程序的运行情况,通过跟踪了解请求的流向,并通过日志了解应用程序的运行状态。
什么是 OpenTelemetry
OpenTelemetry(简称 OTel)是一个开源的、厂商中立的 可观测性(Observability)框架和工具集,旨在统一和标准化应用程序的 指标(Metrics)、日志(Logs)和分布式追踪(Traces) 的生成、收集、处理与导出。 它由 Cloud Native Computing Foundation(CNCF)托管,是目前云原生生态中最主流、最推荐的可观测性标准,被广泛用于微服务、Kubernetes、Serverless 等现代架构中。 官方文档:https://opentelemetry.io
为什么使用 Jaeger, Prometheus, Loki
Jaeger 官方描述与安装:https://www.jaegertracing.io/ Prometheus 官方描述与安装:https://prometheus.io/ Loki 官方描述与安装:https://grafana.com/docs/loki/latest/
使用 Jaeger + Prometheus + Loki 的组合,是构建现代云原生应用可观测性体系中一种非常经典且高效的方案。它们分别专注于可观测性的不同维度,互补性强,尤其适合微服务、Kubernetes 等分布式系统环境。
Jaeger, Prometheus, Loki 原生支持 OTLP 协议,大大简化了配置流程,不需要中间转换,减少性能消耗
| 组件 | 聚焦领域 | 功能 |
|---|---|---|
| Prometheus | 指标(Metrics) | 采集、存储、查询时间序列数据(如 CPU 使用率、请求速率、错误率、延迟分位数等) |
| Jaeger | 分布式追踪(Traces) | 记录单次请求在多个服务间的完整调用链路,分析性能瓶颈与依赖关系 |
| Loki | 日志聚合(Logs) | 高效存储和查询日志,但不索引日志内容,而是基于标签(labels)进行索引,节省资源 |
并且加入 Grafana + Loki,形成'PLGJ'栈(Prometheus + Loki + Grafana + Jaeger),成为 CNCF 推荐的云原生可观测性黄金组合。
使用 Spring 团队提供的 OpenTelemetry Spring Boot Starter
官方描述和文档:https://spring.io/blog/2025/11/18/opentelemetry-with-spring-boot Spring Boot 4.0 通过官方 spring-boot-starter-opentelemetry 提供了开箱即用、协议优先(OTLP)、与 Micrometer 深度集成的 OpenTelemetry 支持。 开发者只需少量配置,即可实现 Metrics、Traces、Logs 三位一体的可观测性,且能灵活对接任意 OTel 兼容后端。 虽然日志集成仍需手动步骤,但整体体验已非常接近'无缝'。
代码实现
1. Maven 依赖
只需要 spring-boot-starter-opentelemetry 就可以实现 Metrics + Traces + Logs 三合一。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-logback-appender-1.0</artifactId>
<version>2.21.0-alpha</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
2. 配置文件
使用 Prometheus + Jaeger otlp 协议来收集数据,可以配置 Loki 用来日志收集。 Prometheus 开启 otlp 协议需要额外配置一个参数。 配置地址:https://prometheus.io/docs/guides/opentelemetry/#enable-the-otlp-receiver
server:
port: 8090
spring:
application:
name: test-service
management:
otlp:
metrics:
export:
url: http://localhost:9090/api/v1/otlp/v1/metrics #Prometheus otlp 协议 http 地址
step: 30s
opentelemetry:
tracing:
export:
otlp:
endpoint: http://localhost:4317/v1/traces #Jaeger otlp 协议 grpc 地址
transport: grpc
# endpoint: http://localhost:4318/v1/traces
# transport: http
logging:
export:
otlp:
endpoint: http://localhost:3100/otlp/v1/logs #Loki otlp 协议 http 地址
3. 采集配置
手动配置一个采集的参数。
@Configuration(proxyBeanMethods = false)
public class OpenTelemetryConfiguration {
@Bean
public OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() {
return new OpenTelemetryServerRequestObservationConvention();
}
@Bean
public OpenTelemetryJvmCpuMeterConventions openTelemetryJvmCpuMeterConventions() {
return new OpenTelemetryJvmCpuMeterConventions(Tags.empty());
}
@Bean
public ProcessorMetrics processorMetrics() {
return new ProcessorMetrics(List.of(), new OpenTelemetryJvmCpuMeterConventions(Tags.empty()));
}
@Bean
public JvmMemoryMetrics jvmMemoryMetrics() {
return new JvmMemoryMetrics(List.of(), new OpenTelemetryJvmMemoryMeterConventions(Tags.empty()));
}
@Bean
public JvmThreadMetrics jvmThreadMetrics() {
return new JvmThreadMetrics(List.of(), new OpenTelemetryJvmThreadMeterConventions(Tags.empty()));
}
@Bean
public ClassLoaderMetrics classLoaderMetrics() {
return new ClassLoaderMetrics(new OpenTelemetryJvmClassLoadingMeterConventions());
}
}
4. 日志配置
- 创建 src/main/resources/logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"></appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="OTEL"/>
</root>
</configuration>
- 配置 Appender 初始化:
@Component
class InstallOpenTelemetryAppender implements InitializingBean {
private final OpenTelemetry openTelemetry;
InstallOpenTelemetryAppender(OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}
@Override
public void afterPropertiesSet() {
OpenTelemetryAppender.install(this.openTelemetry);
}
}
- 配置 TraceId 过滤器:
@Component
class TraceIdFilter implements WebFilter {
private final Tracer tracer;
TraceIdFilter(Tracer tracer) {
this.tracer = tracer;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String traceId = getTraceId();
if (traceId != null) {
exchange.getRequest().mutate().header("X-Trace-Id", traceId).build();
}
return chain.filter(exchange);
}
private String getTraceId() {
TraceContext context = this.tracer.currentTraceContext().context();
return context != null ? context.traceId() : null;
}
}
5. 测试接口
@RestController
public class TestController {
@GetMapping("/test")
public Mono<String> test() {
return Mono.just("Hello World!");
}
}
多运行几次接口查看效果。


