Spring Boot 4使用Opentelemetry OTLP协议快速集成Jaeger + Prometheus + loki 实现指标监控,链路追踪,日志聚合
效果图:
描述
在现代云原生架构中,可观测性不再是可选项,而是一项基本要求。你需要通过指标了解应用程序的运行情况,通过跟踪了解请求的流向,并通过日志了解应用程序的运行状态。
什么是Opentelemetry
官方:https://opentelemetry.io
OpenTelemetry(简称 OTel) 是一个开源的、厂商中立的 可观测性(Observability)框架和工具集,旨在统一和标准化应用程序的 指标(Metrics)、日志(Logs)和分布式追踪(Traces) 的生成、收集、处理与导出。
它由 Cloud Native Computing Foundation(CNCF) 托管,是目前云原生生态中最主流、最推荐的可观测性标准,被广泛用于微服务、Kubernetes、Serverless 等现代架构中。
为什么使用Jaeger,Prometheus,Loki
Jaeger官方描述与安装:https://www.jaegertracing.io/
prometheus官方描述与安装:https://prometheus.io/
Loki官方描述与安装:https://grafana.com/docs/loki/latest/
使用 Jaeger + Prometheus + Loki的组合,是构建现代云原生应用可观测性(Observability)体系中一种非常经典且高效的方案。它们分别专注于可观测性的不同维度,互补性强,尤其适合微服务、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 兼容后端。
虽然日志集成仍需手动步骤,但整体体验已非常接近“无缝”
代码
pom
只需要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>配置
使用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地址 采集配置
手动配置一个采集的参数
@Configuration(proxyBeanMethods =false)publicclassOpenTelemetryConfiguration{@BeanOpenTelemetryServerRequestObservationConventionopenTelemetryServerRequestObservationConvention(){returnnewOpenTelemetryServerRequestObservationConvention();}@BeanOpenTelemetryJvmCpuMeterConventionsopenTelemetryJvmCpuMeterConventions(){returnnewOpenTelemetryJvmCpuMeterConventions(Tags.empty());}@BeanProcessorMetricsprocessorMetrics(){returnnewProcessorMetrics(List.of(),newOpenTelemetryJvmCpuMeterConventions(Tags.empty()));}@BeanJvmMemoryMetricsjvmMemoryMetrics(){returnnewJvmMemoryMetrics(List.of(),newOpenTelemetryJvmMemoryMeterConventions(Tags.empty()));}@BeanJvmThreadMetricsjvmThreadMetrics(){returnnewJvmThreadMetrics(List.of(),newOpenTelemetryJvmThreadMeterConventions(Tags.empty()));}@BeanClassLoaderMetricsclassLoaderMetrics(){returnnewClassLoaderMetrics(newOpenTelemetryJvmClassLoadingMeterConventions());}}日志配置
- 创建 src/main/resources/logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?><configuration><includeresource="org/springframework/boot/logging/logback/base.xml"/><appendername="OTEL"class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender"></appender><rootlevel="INFO"><appender-refref="CONSOLE"/><appender-refref="OTEL"/></root></configuration>- 配置
@ComponentclassInstallOpenTelemetryAppenderimplementsInitializingBean{privatefinalOpenTelemetry openTelemetry;InstallOpenTelemetryAppender(OpenTelemetry openTelemetry){this.openTelemetry = openTelemetry;}@OverridepublicvoidafterPropertiesSet(){OpenTelemetryAppender.install(this.openTelemetry);}}@ComponentclassTraceIdFilterimplementsWebFilter{privatefinalTracer tracer;TraceIdFilter(Tracer tracer){this.tracer = tracer;}@OverridepublicMono<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@NullableStringgetTraceId(){TraceContext context =this.tracer.currentTraceContext().context();return context !=null? context.traceId():null;}}实现一个接口
@RestControllerpublicclassTestController{@GetMapping("/test")publicMono<String>test(){returnMono.just("Hello World!");}}多运行几次接口
效果