You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot服务按异常类型统计4xx/5xx指标异常排查

问题原因

核心问题在于:控制器内部捕获了所有异常并返回ResponseEntity,导致异常没有向上传递到Spring MVC的异常处理链,Micrometer无法捕获到异常类型,因此exception标签始终为None。

默认情况下,Spring Boot集成的Micrometer指标(http_server_requests_seconds_*)通过WebMvcMetricsFilter收集,只有当请求处理过程中抛出未被控制器捕获的异常时,Micrometer才会将异常类型写入exception标签。如果控制器自行处理异常并返回响应,Spring MVC会判定请求已正常完成,不会标记为异常请求,自然不会记录异常类型。

解决方案

方案1:使用全局异常处理器(推荐)

移除控制器内的try-catch,让异常自然抛出,通过@ControllerAdvice定义全局异常处理器统一处理。这种方式既保持代码整洁,又能让Micrometer正确捕获异常类型。

步骤1:修改控制器代码,移除内部异常捕获

@PostMapping(APIPath.Endpoint)
public ResponseEntity<Response> predictRTO(@RequestBody @Valid Request request, Errors errors) {
    if (errors.hasErrors()) {
        throw new RequestParamsException(errors.getAllErrors());
    }
    Response response = service.getResponse(request);
    return ResponseEntity.ok(response);
}

步骤2:实现全局异常处理器

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RequestParamsException.class)
    public ResponseEntity<Response> handleRequestParamsException(RequestParamsException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(Response.builder().errors(e.getErrors()).build());
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Response> handleGeneralException(Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(Response.builder().errors(Collections.singletonList(CommonConstants.INTERNAL_SERVER_ERROR)).build());
    }
}

修改后,异常抛出时会由全局处理器处理并返回对应响应,同时Micrometer会捕获到异常类型,exception标签将显示RequestParamsException或具体的异常类名。

方案2:手动在控制器中记录异常指标(适合无法修改全局逻辑的场景)

如果必须保留控制器内的try-catch,可以手动使用Micrometer的MeterRegistry记录带异常标签的指标。

代码示例

@RestController
public class YourController {

    @Autowired
    private MeterRegistry meterRegistry;

    @PostMapping(APIPath.Endpoint)
    public ResponseEntity<Response> predictRTO(@RequestBody @Valid Request request, Errors errors) {
        long start = System.currentTimeMillis();
        try {
            if (errors.hasErrors()) {
                throw new RequestParamsException(errors.getAllErrors());
            }
            Response response = service.getResponse(request);
            // 记录正常请求指标
            recordRequestMetric("200", "None", start);
            return ResponseEntity.ok(response); 
        } catch (RequestParamsException e) {
            recordRequestMetric("400", e.getClass().getSimpleName(), start);
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Response.builder().errors(e.getErrors()).build());
        } catch (Exception e) {             
            recordRequestMetric("500", e.getClass().getSimpleName(), start);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Response.builder().errors(Collections.singletonList(
                    CommonConstants.INTERNAL_SERVER_ERROR)).build());
        }
    }

    private void recordRequestMetric(String status, String exception, long startTime) {
        Timer.builder("http_server_requests_seconds")
                .tag("method", "POST")
                .tag("uri", APIPath.Endpoint)
                .tag("status", status)
                .tag("exception", exception)
                .register(meterRegistry)
                .record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS);
    }
}

注意:这种方式需要手动管理指标的所有标签,可能会和默认的Micrometer指标重复,需根据实际情况调整。

验证修改

修改完成后,访问actuator/prometheus端点,应该能看到类似以下的指标:

http_server_requests_seconds_bucket{exception="RequestParamsException",method="POST",outcome="CLIENT_ERROR",status="400",uri="/endpoint",le="0.111848106",} 1.0
http_server_requests_seconds_bucket{exception="Exception",method="POST",outcome="SERVER_ERROR",status="500",uri="/endpoint",le="3.22122547",} 1.0

内容的提问来源于stack exchange,提问作者tusharRawat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 19:23:17