Java OpenTelemetry 监控实战:Spring Boot 指标接入完整教程
为 Java/Spring Boot 应用接入 OpenTelemetry 指标:依赖引入、Counter/Gauge/Histogram 实现、用属性为指标添加上下文维度、外部 API 调用监控,以及导出到观测云实现看板与告警。附 Filter 与 Service 完整代码。
Java 的 OpenTelemetry 指标接入有两种路线:零代码的 Java Agent(自动埋点主流框架)与显式的 SDK 编程(完全掌控业务指标)——生产实践通常是两者结合:Agent 兜底框架层,手写代码覆盖核心业务指标。
核心要点速览
- Spring Boot 场景用 Filter/拦截器统一埋点 HTTP 指标;
- Counter 记总量、Gauge 记水位、Histogram 记延迟分布;
- Attributes 为指标加维度(端点/方法/状态码),但要防高基数;
- 导出后核对指标名称、单位和标签,再配置图表与检测规则。
引入依赖
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
或者更简单——直接用 Java Agent(-javaagent:opentelemetry-javaagent.jar),通过环境变量配置 OTLP 端点,具体库及其版本是否产出指标应查支持表,不能把链路自动埋点等同于所有框架指标。本文以下按 Agent 已初始化 SDK 的方式使用 API;仅加入依赖不会自动创建 MeterProvider Bean。依赖版本应通过兼容的 BOM 或版本属性对齐。
为 Agent 已初始化的 provider 注册 Spring Bean(无 Agent/SDK 时全局 API 可能是 no-op):
@Configuration
public class TelemetryConfiguration {
@Bean
public io.opentelemetry.api.metrics.MeterProvider meterProvider() {
return io.opentelemetry.api.GlobalOpenTelemetry.getMeterProvider();
}
}
@Configuration、@Bean 来自 Spring 的 org.springframework.context.annotation。以下各片段需导入对应 OTel API、Spring 与 Servlet 类型。
封装指标 Service
@Service
public class MetricsService {
private final LongCounter requestCounter;
private final LongUpDownCounter activeRequests;
private final DoubleHistogram requestDuration;
public MetricsService(MeterProvider meterProvider) {
Meter meter = meterProvider.get("shop-service");
this.requestCounter = meter.counterBuilder("shop.http.requests")
.setDescription("Total HTTP requests").setUnit("{request}").build();
this.activeRequests = meter.upDownCounterBuilder("shop.http.active_requests")
.setDescription("Active HTTP requests").build();
this.requestDuration = meter.histogramBuilder("shop.http.request.duration")
.setDescription("Request latency").setUnit("s").build();
}
public void incrementRequestCounter(Attributes attrs) { requestCounter.add(1, attrs); }
public void incrementActiveRequests(Attributes attrs) { activeRequests.add(1, attrs); }
public void decrementActiveRequests(Attributes attrs) { activeRequests.add(-1, attrs); }
public void recordDuration(double seconds, Attributes attrs) { requestDuration.record(seconds, attrs); }
}
用 Filter 统一埋点并附加属性
@Component
public class MetricsFilter extends OncePerRequestFilter {
private final MetricsService metricsService;
public MetricsFilter(MetricsService metricsService) {
this.metricsService = metricsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
Attributes attrs = Attributes.builder()
.put("method", request.getMethod())
.build();
metricsService.incrementRequestCounter(attrs);
metricsService.incrementActiveRequests(attrs);
long start = System.nanoTime();
try {
chain.doFilter(request, response);
} finally {
metricsService.decrementActiveRequests(attrs);
metricsService.recordDuration(
(System.nanoTime() - start) / 1e9,
attrs.toBuilder().put("status", response.getStatus()).build());
}
}
}
基数警告:request.getRequestURI() 若含路径参数(/user/12345)会让每个用户一个标签组合——务必改用路由模板(Spring 的 HandlerMapping 最佳匹配模式),按实际路由数和标签组合控制基数;本例移除原始 URI 标签。路由模板通常需在路由匹配后读取;此 Filter 仅测同步调用耗时,不覆盖完整异步请求生命周期。
监控外部 API 调用
对下游依赖单独建指标,出问题时分得清"是我慢还是它慢":
public Product fetchProduct(String id) {
long start = System.nanoTime();
try {
Product p = restTemplate.getForObject("/products/" + id, Product.class);
productApiCalls.add(1, Attributes.builder()
.put("api", "product-service").put("result", "success").build());
return p;
} catch (Exception e) {
productApiCalls.add(1, Attributes.builder()
.put("api", "product-service").put("result", "failure").build());
throw e;
} finally {
productApiDuration.record((System.nanoTime() - start) / 1e9,
Attributes.of(AttributeKey.stringKey("api"), "product-service"));
}
}
Java Agent 与手写指标的分工
| 层 | 推荐手段 |
|---|---|
| HTTP/JDBC/Redis/MQ | 按 Agent 支持表分别核对链路与指标覆盖 |
| JVM 运行时(GC/堆/线程) | Agent 自带 |
| 业务指标(订单量、转化率) | 手写 SDK 代码 |
Agent 的 OTLP 端点用环境变量配置:例如已配置的 Collector HTTP 接收端使用 OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf 与 OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318;DataKit 需按其实际监听和路由设置,不默认是 4318。
验证指标接收与查询
使用 DataKit 接收指标时,先启用 OpenTelemetry 采集器,并按实际协议、端口和路由配置 exporter [1]。发送少量测试请求,检查请求计数、耗时单位与路由标签;再按服务建立查询,对外部 API 失败次数或活动请求数设置检测窗口和阈值 [2]。指标与链路分别上报,不能从指标聚合结果推断每次请求的链路都被保留。
常见问题(FAQ)
Q:有了 Micrometer 还需要 OTel 指标吗? Micrometer 是指标门面,可通过支持的 OTLP registry/export 路径导出,不必为接入 OTel 重写现有指标。
Q:Agent 和 SDK 同时用会重复上报吗? 框架层指标由 Agent 产出、业务指标由手写代码产出,名称空间不同不冲突;注意别让手写代码重复统计 Agent 已覆盖的 HTTP 指标。
Q:Histogram 桶边界怎么定? 按 SLO 定:如接口要求 P99 < 1s,桶边界围绕 0.1/0.3/0.5/1/2s 分布,让关键区间分辨率足够。
Q:多实例部署指标怎么区分? service.instance.id 是否自动设置依实现和资源检测配置而定,需检查并必要时显式设置,观测云中按实例标签过滤或聚合即可。
系列阅读
- 上一篇:《PHP OpenTelemetry 指标》
- 相关:《Go OpenTelemetry 指标》 · 《OpenTelemetry 指标详解》 · 《OpenTelemetry 最佳实践》 · 《返回索引》
[1]: 观测云文档《OpenTelemetry》https://docs.guance.com/integrations/opentelemetry/
[2]: 观测云文档《监控》https://docs.guance.com/monitoring/monitor/monitor-rule/