Java中应使用Micrometer的Counter按异常类型和业务上下文多维打标上报异常次数,辅以Timer记录失败耗时,并注入traceId等业务标签,避免高基数问题,确保指标可观测、可下钻、可报警。

在 Java 中,想让 catch 块真正“说话”,不是只打日志或抛异常,而是把异常变成可观测数据——最直接的方式就是结合 Metrics 组件(如 Micrometer)上报异常指标,再接入 Prometheus + Grafana 做大盘展示。核心思路是:**在捕获异常的瞬间,记录异常类型、频次、业务上下文等维度,并暴露为可聚合的监控指标**。
用 Counter 记录异常发生次数(最常用)
推荐使用 Micrometer 的 Counter,按异常类名或业务场景做多维标记,便于后续按图谱下钻分析:
- 在 Spring Boot 项目中,自动装配
MeterRegistry(如 PrometheusMeterRegistry) - 定义带标签的 Counter:比如
counter = meterRegistry.counter("app.exception.count", "type", "NullPointerException", "endpoint", "user.save") - 在 catch 块里直接递增:
counter.increment();更推荐封装成工具方法,传入异常和业务标签,避免重复写标签逻辑
用 Timer 记录异常发生前的耗时分布(辅助根因定位)
有些异常和执行时间强相关(如超时引发的 TimeoutException)。可在 try 块外启动 Timer,异常发生时以失败状态结束:
- 用
Timer.builder("app.service.duration").tag("result", "error").register(meterRegistry)定义失败耗时指标 - try 块内用
timer.record(() -> { /* 业务逻辑 */ }),但注意:一旦异常抛出,record 会自动标记为失败并记录耗时 - 更灵活的做法是手动控制:用
Timer.Sample.start(meterRegistry)获取采样器,在 catch 中调用sample.stop(timer)并补上 tag 如"exception", e.getClass().getSimpleName()
补充业务维度标签,让大盘真正有用
光记“发生了 N 次 NullPointerException”意义有限。关键是在 catch 时注入上下文信息:
立即学习“Java免费学习笔记(深入)”;
- 从当前线程绑定的 MDC(如 Logback 的
MDC.get("traceId"))提取链路 ID,作为 tag 上报 - 结合 Spring 的
@RequestMapping或拦截器,把 controller 方法名、HTTP 状态码、用户角色等动态加入指标 tag - 对高频异常(如数据库连接失败),额外记录下游服务名、SQL 模板 hash、重试次数等,方便关联分析
避免指标爆炸与性能陷阱
异常指标若不加约束,极易产生高基数(high cardinality)问题,拖垮 Prometheus:
- 禁止将异常消息(
e.getMessage())或堆栈全量作为 tag 值——改用预定义错误码或哈希摘要 - 对低优先级异常(如用户输入校验失败),可降级为 Histogram 分桶统计,或只上报总量+分位数,不打明细 tag
- catch 块内指标操作尽量轻量:Micrometer 的
Counter.increment()是无锁原子操作,安全;但避免在其中调用远程服务或复杂计算
不复杂但容易忽略:上线前务必在 Grafana 中验证指标是否真实采集、标签是否可筛选、报警规则是否触发。一次配置正确的异常指标,比十次翻日志更快定位线上火情。


















