rest接口耗时统计应嵌入请求生命周期并绑定日志上下文:用filter统一拦截,system.nanotime()计时,mdc注入traceid与cost,结合结构化日志实现可定位、可聚合、可关联的链路级追踪。

在 REST 接口开发中做耗时统计日志,关键不是“记下时间”,而是让耗时数据可定位、可聚合、可关联。单靠 System.currentTimeMillis() 手动打点容易遗漏、难维护、跨线程失效,也不便于和 TraceId 对齐。真正实用的做法,是把耗时统计嵌入请求生命周期,并与日志上下文自动绑定。
用 Filter 统一拦截并记录请求耗时
这是最轻量也最可控的方式。在 Spring Boot 中,写一个实现 Filter 的类,在 doFilter 中:
- 进入时记录起始时间(推荐用
System.nanoTime(),精度更高且不受系统时间调整影响) - 放行请求,执行业务逻辑
- 结束时计算耗时,通过 MDC 注入到当前线程日志上下文(如
MDC.put("cost", String.valueOf(durationMs))) - 确保无论是否异常都执行耗时记录(放在
finally块里)
结合 TraceId 实现链路级耗时追踪
单纯知道“这个接口花了 320ms”意义有限;如果同时知道“这 320ms 里,调 DB 花了 280ms,远程调用花了 40ms”,问题就清晰了。因此耗时统计要和全链路 TraceId 对齐:
- Filter 中生成或提取 TraceId 后,立即将其存入 MDC(
MDC.put("traceId", traceId)) - 耗时字段也写入 MDC(
MDC.put("cost", durationMs)),这样每条日志自动带这两个字段 - 下游 HTTP 调用时,通过拦截器把当前 MDC 中的
traceId和cost(可选)透传到 Header
避免手动埋点,用 AOP 或 Spring Boot Actuator 补位
对已有项目,改 Filter 是全局生效的;但如果想更细粒度(比如只统计某几个 Controller 方法),可用 AOP:
- 定义切点匹配
@RestController下的 public 方法 - 在环绕通知中计时,捕获异常,最终统一记录日志或发往监控系统
- Spring Boot Actuator 的
/actuator/metrics/http.server.requests默认提供按路径、状态码、耗时分位数(p50/p90/p99)的指标,适合快速接入 Prometheus
日志格式要支持结构化与快速检索
耗时日志没被查到,等于没记。建议在 logback.xml 或 log4j2.xml 中配置 pattern,确保每行包含:
-
%X{traceId}—— 当前请求唯一标识 -
%X{cost}—— 本次请求总耗时(毫秒) -
%X{path}、%X{method}、%X{status}—— 接口基础信息 - 使用 JSON layout(如 LogstashEncoder),方便 ELK 或 Loki 直接解析字段
不复杂但容易忽略:耗时统计本身不该成为性能瓶颈。避免在日志中拼接大量字符串、避免同步写磁盘、不要在高并发路径上做复杂计算。MDC + Filter + 结构化日志,三者配齐,一次配置,全量接口自动覆盖。











