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
相关产品推荐
相关产品推荐

