如何实现Spring Boot请求/响应日志拦截?一个生产级LoggingFilter详解
在开发Web应用时,请求和响应的日志记录对于调试、监控和审计至关重要。今天我将分享一个在生产环境中经过验证的请求日志拦截方案,基于Spring Boot的OncePerRequestFilter实现。
为什么需要请求日志拦截?
在Web应用中,记录请求和响应信息可以帮助我们:
- 快速定位和排查问题
- 监控API性能
- 进行安全审计
- 分析用户行为
核心实现解析
下面是我们今天要讨论的LoggingFilter实现代码:
@Slf4j
@Component
public class LoggingFilter extends OncePerRequestFilter {
private String getStringValue(byte[] contentAsByteArray) {
return new String(contentAsByteArray, StandardCharsets.UTF_8);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 请求头处理
Enumeration<String> enumHeaderNames = request.getHeaderNames();
TreeMap<String, String> headerTreeMap = new TreeMap<>();
while (enumHeaderNames.hasMoreElements()) {
String name = enumHeaderNames.nextElement();
String value = request.getHeader(name);
// 筛选出自定义头
if (name.toLowerCase().startsWith("xxxx") || name.equalsIgnoreCase(IdeConstant.AUTHORIZATION_HEADER)) {
headerTreeMap.put(name, value);
}
}
// 请求参数
Map<String, String> requestParams = ServletUtils.getParamMap(request);
// 客户端IP
String clientIp = IpUtils.getIpAddr(request);
// 包装请求和响应以支持多次读取
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
long startTime = System.currentTimeMillis();
filterChain.doFilter(requestWrapper, responseWrapper);
long timeTaken = System.currentTimeMillis() - startTime;
// 获取请求和响应体
String requestBody = getStringValue(requestWrapper.getContentAsByteArray());
String responseBody = getStringValue(responseWrapper.getContentAsByteArray());
log.info(
"请求拦截: 耗时={}ms; 方法={}; IP={}; 地址={}; 请求参={}; 请求体={}; 请求头={}; 返回码={}; 返回体={}",
timeTaken, request.getMethod(), clientIp, request.getRequestURI(),
requestParams, requestBody, headerTreeMap, response.getStatus(), responseBody);
responseWrapper.copyBodyToResponse();
}
}关键实现细节
1. 继承OncePerRequestFilter
我们选择继承OncePerRequestFilter而不是直接实现Filter接口,因为:
- 确保每个请求只被过滤一次
- Spring提供了更好的集成支持
- 简化了异步请求处理
2. 使用内容缓存包装器
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);这两个包装器类允许我们多次读取请求和响应体内容,而标准的HttpServletRequest和HttpServletResponse只能读取一次。
3. 性能监控
long startTime = System.currentTimeMillis();
filterChain.doFilter(requestWrapper, responseWrapper);
long timeTaken = System.currentTimeMillis() - startTime;通过记录过滤器前后的时间戳,我们可以精确计算请求处理耗时。
4. 选择性记录请求头
if (name.toLowerCase().startsWith("xxxx") || name.equalsIgnoreCase(IdeConstant.AUTHORIZATION_HEADER)) {
headerTreeMap.put(name, value);
}这里我们只记录以"xxxx"开头的自定义头和授权头,避免记录不必要的标准头信息,减少日志体积。
5. 响应体复制
responseWrapper.copyBodyToResponse();这一步至关重要,确保响应内容被正确写回客户端。
生产环境优化建议
- 敏感信息过滤:添加对敏感数据(如密码、token)的脱敏处理
- 日志级别控制:根据环境动态调整日志级别
- 大文件处理:添加对文件上传请求的特殊处理
- 异步记录:考虑使用异步方式记录日志以减少对主流程的影响
- 采样率控制:在高流量环境下,可以按比例采样记录
完整日志示例
启用这个过滤器后,你将会看到类似以下的日志输出:
请求拦截: 耗时=45ms; 方法=POST; IP=192.168.1.100; 地址=/api/users;
请求参={name=张三, age=30};
请求体={"email":"zhangsan@example.com","phone":"13800138000"};
请求头={Authorization=Bearer xyz123, X-Reqm-ID=req123};
返回码=200;
返回体={"code":0,"data":{"userId":123}}总结
这个LoggingFilter实现提供了全面的请求/响应日志记录功能,具有以下优点:
- 完整的请求信息捕获
- 精确的性能监控
- 灵活的头部过滤
- 对应用无侵入性
- 易于扩展和定制
你可以直接在生产环境中使用这个过滤器,或者根据具体需求进行进一步定制。希望这篇文章对你的开发工作有所帮助!
评论已关闭