跳到主内容
创建时间: 2026-09-16最后更新: 2026-09-16

日志能够还原具体事件,但逐行阅读日志很难回答「最近五分钟错误率是否升高」或「哪个接口的延迟正在恶化」。Actuator 已经引入 Micrometer,它会为 Web、JVM、连接池等组件收集可聚合指标,也能把一次操作表示为 Observation 并连接到链路追踪。

本章先区分日志、指标和 Trace,再读取 Spring Boot 自动采集的数据,为心跳检查增加自定义 Observation,最后说明 Prometheus 导出、OpenTelemetry Trace、采样和标签基数。

1. 三类信号

可观测性是从系统外部推断内部状态的能力,常用信号有三类:

信号数据形态适合回答的问题
日志带时间和上下文的离散事件某次失败具体发生了什么
指标按名称与标签聚合的数值序列请求率、错误率和延迟趋势如何
Trace一次调用中的 Span 树请求经过哪些组件,时间花在哪里

它们不是三份重复数据。一次请求可以产生一条 Trace、多个指标样本和若干日志:

observability-signals.txt
1
HTTP 请求
2
-> Trace:Controller -> Service -> PostgreSQL
3
-> Metrics:请求次数、错误次数、耗时分布
4
-> Logs:业务事件和异常详情

指标适合告警发现异常,Trace 帮助缩小慢点范围,日志再提供具体错误上下文。只有日志而没有聚合指标,通常要等用户报告后才开始排查;只有指标而没有日志和 Trace,又难以解释异常原因。

2. 内置指标

Actuator 自动配置 MeterRegistry,并为 classpath 中支持的组件注册指标。当前项目常见的名称包括:

指标前缀内容
http.server.requestsHTTP 请求次数、状态和耗时
jvm.*堆内存、GC、线程和类加载
process.*进程 CPU 与运行时间
system.*系统 CPU 等运行环境数据
jdbc.connections.*JDBC 连接状态
hikaricp.*HikariCP 活跃、空闲与等待连接

先把 metrics 加入已有 Actuator 暴露白名单。下面只展示发生变化的配置路径,应合并进现有 management,不要在同一文件中重复创建根键:

src/main/resources/application.yaml
1
management:
2
endpoints:
3
web:
4
exposure:
5
include: "health,info,metrics"

查看已注册指标和某一项详情:

terminal.bash
1
curl -u monitor:change-me http://localhost:8080/actuator/metrics
2
curl -u monitor:change-me http://localhost:8080/actuator/metrics/http.server.requests

第二个响应会列出计数、总耗时、最大耗时和可用标签。还可以按标签进一步筛选:

terminal.bash
1
curl -u monitor:change-me \
2
'http://localhost:8080/actuator/metrics/http.server.requests?tag=uri:/api/v1/health&tag=status:200'

metrics 端点适合确认本地究竟注册了哪些指标,不是生产时序数据库。应用重启后内存数据会消失,多实例数据也不会自动聚合,生产环境需要把指标交给外部监控系统。

3. 自定义 Observation

Spring Boot 使用 Micrometer Observation 统一描述一段被观察的操作。给 HealthService 注入 ObservationRegistry:

src/main/java/cn/usehook/spring/health/HealthService.java
01
import io.micrometer.observation.Observation;
02
import io.micrometer.observation.ObservationRegistry;
03
04
private final ObservationRegistry observationRegistry;
05
06
public HealthService(
07
HealthProperties properties,
08
HealthStatusRepository repository,
09
HealthCheckHistoryRepository historyRepository,
10
ObservationRegistry observationRegistry
11
) {
12
this.properties = properties;
13
this.repository = repository;
14
this.historyRepository = historyRepository;
15
this.observationRegistry = observationRegistry;
16
}

两个公开检查入口都进入同一个私有观察方法,先得到规范化服务名,再观察真正的业务操作:

HealthService.java
01
@Transactional
02
public HealthStatus check() {
03
return observeCheck(properties.service());
04
}
05
06
@Transactional
07
public HealthStatus check(String service) {
08
return observeCheck(service);
09
}
10
11
private HealthStatus observeCheck(String service) {
12
String normalizedService = normalizeService(service);
13
14
return Observation.createNotStarted("health.check", observationRegistry)
15
.lowCardinalityKeyValue("operation", "check")
16
.highCardinalityKeyValue("health.service", normalizedService)
17
.observe(() -> performCheck(normalizedService));
18
}
19
20
private HealthStatus performCheck(String service) {
21
HealthStatus status = new HealthStatus(properties.status(), service);
22
HealthStatus savedStatus = repository.save(status);
23
historyRepository.append(savedStatus);
24
return savedStatus;
25
}

observe() 会启动 Observation,执行 Lambda,并在正常返回或抛出异常时正确结束。已有 @Transactional 仍然位于两个公开方法上;Controller 通过代理调用后先建立事务,再进入方法中的 Observation,因此两个入口都会记录相同名称和标签,当前耗时包含两次 Repository 操作。

operation=check 的值集合很小,可以成为指标标签。服务名称可能由请求输入并持续增加,只作为高基数 Trace 属性,不进入普通指标标签。如果把用户 ID、请求 ID、完整 URL 或任意服务名放进指标标签,每一种值组合都会产生新的时间序列,最终消耗大量内存与存储。

当指标与追踪处理器都存在时,同一个 Observation 可以同时产生指标和 Span。业务代码只描述被观察操作,不需要分别维护两套计时逻辑。

4. Prometheus

Prometheus 使用拉取方式定期读取每个应用实例。添加 Micrometer Prometheus Registry:

pom.xml
1
<dependency>
2
<groupId>io.micrometer</groupId>
3
<artifactId>micrometer-registry-prometheus</artifactId>
4
</dependency>

再把 prometheus 加入生产 Profile 的暴露白名单。它与公共配置会按 Profile 规则合并,最终值需要包含前面仍要访问的端点:

src/main/resources/application-prod.yaml
1
management:
2
endpoints:
3
web:
4
exposure:
5
include: "health,info,metrics,prometheus"

启动后确认抓取内容:

terminal.bash
1
curl -u monitor:change-me http://localhost:8080/actuator/prometheus

Prometheus 配置需要指向实际实例和管理端口:

prometheus.yml
1
scrape_configs:
2
- job_name: spring
3
metrics_path: /actuator/prometheus
4
basic_auth:
5
username: monitor
6
password_file: /run/secrets/spring-monitor-password
7
static_configs:
8
- targets:
9
- localhost:8080

生产服务发现通常不会手写静态地址,但拉取路径和目标端口含义相同。这里从 Secret 文件读取监控密码,不把它直接写入 Prometheus 配置;应用侧凭据也应来自同一套受控 Secret 分发。管理端点仍需要网络访问控制,不能因为 Prometheus 要抓取就向公网开放所有 Actuator 端点。

5. Trace

Trace 由多个 Span 组成,每个 Span 表示一次有开始、结束和上下文的操作。HTTP 请求是根调用时,Spring MVC 会产生服务器 Span;自定义 health.check Observation 可以成为它的子 Span,受支持的数据库或客户端组件还会继续形成下游 Span。

使用 OpenTelemetry 并通过 OTLP 导出 Trace,可以添加 Spring Boot 4.1 提供的 Starter:

pom.xml
1
<dependency>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-opentelemetry</artifactId>
4
</dependency>

具体 OTLP 地址和认证信息应通过 management.opentelemetry.tracing.export.otlp.* 配置交给部署环境。采样比例可以单独设置:

src/main/resources/application-prod.yaml
1
management:
2
tracing:
3
sampling:
4
probability: "${TRACING_SAMPLING_PROBABILITY:0.1}"

默认只采样部分请求可以限制生产开销。排查本地案例时可以临时设为 1.0,生产环境则应结合请求量、存储成本和错误保留策略调整,不能默认全量采集。

启用 Micrometer Tracing 后,Spring Boot 日志相关 ID 会包含 Trace 上下文。上一章的 requestId 仍可作为客户端可见的请求编号;Trace ID 则由追踪系统生成,用于连接 Span。两者可以同时存在,但不要在代码中把它们假设为同一个值。

以后调用其他 HTTP 服务时,应使用 Spring Boot 自动配置的 RestClient.Builder、WebClient.Builder 或 RestTemplateBuilder 创建客户端,自动传播 Trace 上下文。直接手动创建客户端可能绕过框架的传播定制。

6. 指标设计

仪表盘和告警应先围绕用户能感受到的信号,而不是展示所有可用数字。对于当前接口,可以从四组指标开始:

目标指标
流量每秒请求数、心跳检查次数
错误5xx 比例、业务失败次数
延迟HTTP 与 health.check 的分位数
饱和度活跃连接、等待连接、线程和堆内存

平均耗时会掩盖少量极慢请求,延迟告警通常还要观察分位数或直方图。错误率要与请求量一起判断,低流量下一个错误可能产生很高百分比。

指标标签必须来自有界集合,例如 HTTP 方法、归一化状态码和固定操作类型。日志与 Trace 可以承载请求 ID、异常消息和具体服务名称,但仍要考虑敏感数据与保留成本。

可观测性代码已经建立运行反馈,但应用还只以本地 JAR 运行。下一篇把同一个构建产物放进 Docker 镜像,说明构建阶段、运行阶段、镜像分层、非 root 用户和外部配置怎样配合。