Spring Boot 3 可观测性实战:Micrometer Tracing 与 OpenTelemetry 一、可观测性不是“多打日志”可观测性的三根支柱是Logs某个时间点发生了什么Metrics系统整体是否异常例如错误率、延迟分位数、线程池队列长度Traces一次请求经过了哪些组件时间消耗在哪里。三者各自有价值但关联后才真正高效告警orders.create 的 P95 延迟超过 800ms ↓ 按 service、uri、status 缩小范围 Trace找到一个 2.4s 的请求 ↓ 查看 Span 瀑布图 发现 payment-service HTTP 调用耗时 2.1s ↓ 使用 traceId 搜索日志 定位到下游连接池等待和超时重试Spring Boot 3 使用 Micrometer Observation 统一表达一次“被观测的操作”。一个 Observation 可以同时产生指标和 Trace SpanMicrometer Tracing 再把追踪数据桥接到 OpenTelemetry。二、组件之间是什么关系Spring MVC / RestClient / DataSource / 自定义业务代码 ↓ Micrometer Observation ↙ ↘ Micrometer Metrics Micrometer Tracing ↓ OpenTelemetry Bridge ↓ OTLP OpenTelemetry Collector ↓ Tempo / Jaeger / 其他后端应用代码优先使用 Micrometer 的ObservationRegistry或Tracer不要同时手工维护另一套 OpenTelemetry SDK。Spring Boot 会负责自动配置和生命周期管理OTLP 只是导出协议。三、添加依赖dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependencygroupIdio.micrometer/groupIdartifactIdmicrometer-tracing-bridge-otel/artifactId/dependencydependencygroupIdio.opentelemetry/groupIdartifactIdopentelemetry-exporter-otlp/artifactId/dependencydependencygroupIdio.micrometer/groupIdartifactIdmicrometer-registry-prometheus/artifactIdscoperuntime/scope/dependency/dependencies不要在使用 Spring Boot BOM 时手工拼凑这些依赖的版本。版本不一致很容易引发自动配置缺失、方法找不到或上下文传播失败。如果只需要 Trace 导出可以去掉 Prometheus Registry它在本文中用于演示指标端点。四、配置 OTLP、采样和 Actuatorspring:application:name:order-servicemanagement:endpoints:web:exposure:include:health,info,prometheusendpoint:health:probes:enabled:truetracing:sampling:probability:0.1propagation:type:w3cbaggage:remote-fields:tenant-idcorrelation:fields:tenant-idotlp:tracing:endpoint:http://otel-collector:4318/v1/tracestimeout:10sobservations:key-values:region:cn-east-1stack:prod关键点spring.application.name会成为服务识别的重要维度probability: 0.1表示约 10% 的请求创建可导出的 Tracew3c使用traceparent、tracestate与baggage标准 Headermanagement.otlp.tracing.endpoint指向 Collector 的 HTTP Trace 接收端点region、stack这类低基数标签适合作为公共维度。开发环境可以暂时把采样率设为1.0生产环境不要默认全量采样。采样率应根据吞吐、故障定位要求、存储成本和后端限额决定。Actuator 端点也不应全部公开。env、configprops、heapdump等端点可能泄露配置或占用大量资源必须通过独立管理端口、网络策略和鉴权保护。五、让日志自动带上 TraceId 和 SpanIdSpring Boot 在有效追踪上下文中会把traceId、spanId放入 MDC。可以自定义控制台日志格式logging:pattern:console:-%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level[${spring.application.name:unknown},trace%X{traceId:-},span%X{spanId:-}][%thread]%logger{36}-%msg%n示例输出2026-07-05 20:16:32.117 INFO [order-service,trace7a9f31d8a2f0d9138e1eae74d9c74421,span0a4fd37b9953a12c] [http-nio-8080-exec-4] c.e.order.OrderController - create order, skuId10001TraceId 是跨系统关联键SpanId 是当前操作的局部标识。不要自己为每个服务生成新的 TraceId否则链路会在服务边界断开。结构化日志系统中应把traceId、spanId保存为独立字段而不是只嵌入 message后续检索和聚合会更可靠。六、跨服务调用必须使用自动配置的 BuilderSpring Boot 会给RestClient.Builder、WebClient.Builder和RestTemplateBuilder安装观测拦截器。必须注入这些 Builder 创建客户端packagecom.example.order.client;importorg.springframework.stereotype.Component;importorg.springframework.web.client.RestClient;ComponentpublicclassInventoryClient{privatefinalRestClientrestClient;publicInventoryClient(RestClient.Builderbuilder){this.restClientbuilder.baseUrl(http://inventory-service:8080).build();}publicInventoryViewfindBySku(longskuId){returnrestClient.get().uri(/api/inventory/{skuId},skuId).retrieve().body(InventoryView.class);}}不要这样做RestClientrestClientRestClient.create();直接创建的客户端没有 Spring Boot 定制器通常不会自动注入traceparent于是上游和下游会显示成两条不相关的 Trace。还要保证代理、网关和服务网格允许追踪 Header 通过。Header 在应用内正确生成并不代表一定能穿过所有中间层。七、为关键业务阶段创建自定义 Observation框架能自动观测 HTTP 请求但它不知道“库存预占”和“价格计算”哪个阶段更关键。可以围绕业务操作创建 Observationpackagecom.example.order.application;importio.micrometer.observation.Observation;importio.micrometer.observation.ObservationRegistry;importorg.springframework.stereotype.Service;ServicepublicclassOrderApplicationService{privatefinalObservationRegistryobservationRegistry;privatefinalInventoryClientinventoryClient;publicOrderApplicationService(ObservationRegistryobservationRegistry,InventoryClientinventoryClient){this.observationRegistryobservationRegistry;this.inventoryClientinventoryClient;}publicOrderViewcreate(CreateOrderCommandcommand){returnObservation.createNotStarted(order.create,observationRegistry).contextualName(create order).lowCardinalityKeyValue(channel,command.channel()).highCardinalityKeyValue(customer.id,command.customerId()).observe(()-doCreate(command));}privateOrderViewdoCreate(CreateOrderCommandcommand){inventoryClient.findBySku(command.skuId());// 校验价格、保存订单、发送领域事件returnnewOrderView(1001L,CREATED);}}低基数与高基数必须分清低基数取值集合有限如channelAPP|WEB、resultsuccess|failure高基数取值几乎无限如用户 ID、订单号、请求 ID。低基数键会进入指标和 Trace高基数键只应进入 Trace。把订单号放进 Metrics 标签会制造海量时间序列显著增加 Prometheus 和后端存储压力。同一个控制器或 Repository 已经被框架自动观测时不要再无差别加一层同名 Observation否则会产生重复 Span。自定义 Observation 应表达框架不知道的业务阶段。八、什么时候使用注解如果团队偏好注解可以启用 Micrometer Observation 注解扫描dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencymanagement:observations:annotations:enabled:true然后使用Observed或NewSpanimportio.micrometer.observation.annotation.Observed;Observed(nameprice.calculate,contextualNamecalculate price)publicMoneycalculatePrice(PricingCommandcommand){returnpricingEngine.calculate(command);}注解适合稳定的方法边界编程式 Observation 更适合动态标签、只观测某个代码片段或精确控制错误记录。两种方式不要在同一边界重复使用。九、Baggage少量传播业务上下文Baggage 可以把租户号等少量字段随 Trace 传播management:tracing:baggage:remote-fields:tenant-idcorrelation:fields:tenant-idremote-fields允许字段跨网络传播correlation.fields把字段放入 MDC方便日志检索。但 Baggage 会进入请求 Header 并跨多个服务复制应遵循三个限制不放密码、Token、手机号等敏感信息不放大对象或数量不受控的键值只传播确实用于诊断或路由的字段。Baggage 不是业务参数传输协议。下游业务逻辑需要的关键数据仍应在 API 契约中明确声明。十、异步线程为什么容易丢 Trace追踪上下文通常绑定在当前执行上下文。把任务提交给自建线程池后如果没有上下文传播日志里的 TraceId 会消失。Spring Framework 提供了上下文传播 TaskDecoratorpackagecom.example.order.config;importjava.util.concurrent.Executor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.task.support.ContextPropagatingTaskDecorator;importorg.springframework.scheduling.annotation.EnableAsync;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;ConfigurationEnableAsyncpublicclassAsyncConfig{Bean(nameapplicationTaskExecutor)ExecutorapplicationTaskExecutor(){ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor.setCorePoolSize(8);executor.setMaxPoolSize(32);executor.setQueueCapacity(500);executor.setThreadNamePrefix(order-async-);executor.setTaskDecorator(newContextPropagatingTaskDecorator());executor.initialize();returnexecutor;}}使用CompletableFuture.supplyAsync(task)时会进入公共线程池也可能丢失上下文。应显式传入由应用管理、已配置上下文传播的 Executor。异步消息还需要在生产端注入上下文、消费端提取上下文。若使用的消息组件没有自动埋点需要在消息 Header 与 Observation 之间建立清晰的传播约定。十一、采样不是简单地“保留 10% 日志”management.tracing.sampling.probability是头部采样请求进入系统时决定是否采样。它实现简单、开销可控但随机 10% 可能刚好丢掉稀有错误。实践中可分三层开发环境短期 100%方便验证链路普通生产流量按成本设置 1%20% 的头部采样关键错误或高延迟在 Collector 侧评估尾部采样等待 Trace 完整后再决定保留。尾部采样需要 Collector 暂存更多数据资源成本和配置复杂度更高。不要在没有容量评估时直接开启全量 Trace。另外采样只影响 Trace 是否导出不应影响业务日志和核心指标的正确性。十二、验证链路是否真的打通按以下顺序验证比直接打开 Trace UI 更快请求服务后日志是否出现非空traceId和spanId下游服务日志是否出现相同 TraceId抓取或临时记录请求 Header确认存在traceparentCollector 是否监听正确协议和端口应用到 Collector 的网络是否畅通Collector 是否成功向后端导出Trace 后端的时间范围、服务名筛选是否正确。测试请求curl-ihttp://localhost:8080/api/orders/1001指标端点curlhttp://localhost:8080/actuator/prometheus如果应用日志有 TraceId但后端没有 Trace问题通常在采样、Exporter、Collector 或网络如果第一个服务有 TraceId、下游换了新的 TraceId问题通常在客户端创建方式或 Header 传播。十三、常见问题排查表现象原因与处理日志中 TraceId 始终为空检查 Actuator、Tracing Bridge 是否存在请求是否经过被观测的入口下游生成新的 TraceId使用自动配置的 HTTP Client Builder检查网关是否保留traceparentCollector 收不到数据检查 OTLP HTTP/gRPC 协议、端口、路径和容器网络Trace 数量远低于请求量检查采样率这不一定是故障指标时间序列暴涨高基数字段被错误放入低基数标签Async中 TraceId 消失为线程池配置上下文传播 TaskDecorator一个请求出现重复 Span同一框架边界同时存在自动埋点和自定义注解/ObservationTrace 后端能看到链路但日志搜不到日志格式未输出 MDC 字段或日志采集器没有解析独立字段十四、生产治理清单为每个服务设置稳定且唯一的spring.application.name使用 W3C Trace Context 作为跨语言默认传播格式HTTP 客户端统一由 Spring Bean 管理禁止业务代码随意create()只对关键业务阶段添加自定义 ObservationMetrics 标签严格限制为低基数用户 ID、订单号只进入 Trace 或日志生产采样率由吞吐、存储成本和排障目标共同决定Collector 部署高可用并监控自身队列、丢弃量和导出失败TraceId、SpanId 进入结构化日志字段Baggage 不携带凭证和个人敏感信息Actuator 端点实施最小暴露、鉴权和网络隔离对跨服务、异步线程和消息消费分别做链路连续性测试。总结Spring Boot 3 的可观测性主线并不复杂框架和业务代码通过 Micrometer Observation 产生观测数据Micrometer Tracing 建立追踪语义OpenTelemetry Bridge 与 OTLP 负责把 Trace 发送给 Collector。真正决定方案质量的是工程约束统一创建 HTTP 客户端、控制标签基数、正确传播异步上下文、合理采样并把 TraceId 与结构化日志打通。完成这些工作后排障过程才能从“逐台机器猜问题”变成“从指标定位异常再沿 Trace 和日志找到根因”。参考资料Spring Boot 3.5ObservabilitySpring Boot 3.5TracingSpring Boot 3.5MetricsOpenTelemetryOTLP Exporter Configuration