全局异常处理中记录接口入参快照的核心是预缓存请求体:1. 用contentcachingrequestwrapper在filter中包装request实现多次读取;2. 在globalexceptionhandler中提取querystring和body内容并结构化记录;3. 结合handlermethod获取@requestparam/pathvariable等绑定参数;4. 生产环境需脱敏、限长、按需开关并异步落库。

在全局异常处理中记录接口入参快照,核心思路是:**把请求体(RequestBody)或查询参数(RequestParam)在异常发生时“抓取”下来,并随日志一并输出或落库**。Spring Boot 默认不缓存请求体,所以需主动读取并保留——但要注意不能重复读取(HTTP InputStream 只能读一次),因此推荐在请求进入时就做预处理。
1. 用 HttpServletRequestWrapper 包装请求,支持多次读取
这是最关键的一步。Spring 的 HttpServletRequest 原生不支持重复读取 body,直接调用 getInputStream() 或 getReader() 会导致后续 Controller 拿不到参数。解决方案是自定义一个可重放的包装类:
- 继承
ContentCachingRequestWrapper(Spring 已提供),它会将请求体缓存到内存缓冲区 - 在 Filter 中提前包装原始 request,确保后续所有组件(Controller、Validator、ExceptionHandler)都能安全读取 body
示例 Filter:
(放在所有业务 Filter 之前,如配置为 Order = -100)@Component
@Order(-100)
public class CacheRequestBodyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 仅对 POST/PUT/PATCH 等含 body 的请求缓存
if (HttpMethod.hasBody(httpRequest.getMethod())) {
ContentCachingRequestWrapper wrappedRequest =
new ContentCachingRequestWrapper(httpRequest);
chain.doFilter(wrappedRequest, response);
return;
}
chain.doFilter(request, response);
}
}
2. 在 GlobalExceptionHandler 中提取并记录入参
有了可重放的 request,就能在异常处理器里安全读取原始 body 和 query 参数:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 用
request.getQueryString()获取 URL 参数字符串 - 用
ContentCachingRequestWrapper.getContentAsByteArray()转为字符串(注意字符编码,建议 UTF-8) - 若已知是 JSON,可尝试反序列化为 Map 或 JsonNode,便于结构化打印;若失败则原样记录 raw body
- 记录时带上 requestURI、method、timestamp、traceId(如有)等上下文信息
示例片段:
@ExceptionHandler(Exception.class)
public Result> handleException(HttpServletRequest request, Exception e) {
String uri = request.getRequestURI();
String method = request.getMethod();
String queryString = request.getQueryString();
String body = "";
if (request instanceof ContentCachingRequestWrapper) {
byte[] content = ((ContentCachingRequestWrapper) request).getContentAsByteArray();
body = new String(content, StandardCharsets.UTF_8).trim();
}
log.error("【异常快照】URI={}, METHOD={}, QUERY={}, BODY={}",
uri, method, queryString, StringUtils.defaultString(body), e);
return Result.fail(500, "系统异常,请稍后重试");
}
3. 补充:对 @RequestParam / @PathVariable 的友好支持
上面方法能捕获 body 和 query string,但 @RequestParam(表单提交)、@PathVariable 不一定出现在 query 或 body 中。更稳妥的方式是结合 Spring 的 HandlerMethod 获取实际绑定的参数值:
- 在
@RestControllerAdvice中注入HandlerMethod(需开启@EnableWebMvc或确保 WebMvcConfiguration 支持) - 通过
handlerMethod.getMethod().getParameters()和handlerMethod.getArguments()获取运行时传入的参数对象 - 对简单类型(String、Long 等)直接 toString;对复杂对象可用 Jackson
ObjectMapper.writeValueAsString()序列化
⚠️ 注意:该方式需在异常抛出前完成参数绑定,适用于 Controller 层抛出的异常;若异常发生在参数解析阶段(如 JSON 解析失败),仍需依赖上一步的 raw body 快照。
4. 生产建议:控制日志体积与敏感信息
入参快照虽利于排查,但可能含密码、token、手机号等敏感字段:
- 日志中对已知敏感字段(如 password、idCard、token)做自动脱敏(如替换为 ***)
- 限制 body 日志长度(如截取前 2048 字符),避免 OOM 或日志爆炸
- 高敏感接口(如支付、实名认证)可单独配置开关,按需启用完整快照
- 考虑异步写入审计表或 ELK,而非仅依赖 console/logfile










