当前位置: 首页 > java >正文

深入解析:实现一个详细的日志过滤器(LogFilter)

文章目录

    • 前言
    • 技术积累
      • 过滤器(Filter)
      • 过滤器的工作原理
      • 过滤器的生命周期
      • 缓存请求之 ContentCachingRequestWrappe
      • 缓存响应之ContentCachingResponseWrapper
    • 实战演示
      • 添加依赖
      • 创建 LogFilter 类
    • 总结

前言

在现代 Web 应用开发中,日志记录是一个至关重要的环节。它不仅帮助我们调试问题,还能监控应用性能、分析用户行为,并确保系统的稳定性和安全性。本文将详细介绍如何实现一个详细的日志过滤器 LogFilter,记录 HTTP 请求和响应的详细信息,包括请求地址、方法、参数、头信息和体信息,以及响应的状态码、头信息和体信息。此外,我们还将探讨相关的理论和技术知识。

在这里插入图片描述

技术积累

过滤器(Filter)

过滤器 是 Java Servlet 规范中定义的一种组件,用于在请求到达目标资源(如 Servlet、JSP 页面等)之前或响应返回客户端之前对请求和响应进行处理。过滤器可以用于多种用途,如日志记录、权限控制、字符编码转换、压缩响应内容等。

过滤器的工作原理

请求到达过滤器:客户端发送请求到服务器,请求首先到达过滤器。
过滤器处理请求:过滤器可以对请求进行预处理,如设置字符编码、添加请求头等。
继续过滤链:过滤器调用 filterChain.doFilter 方法,将请求传递给下一个过滤器或目标资源。
目标资源处理请求:目标资源处理请求并生成响应。
过滤器处理响应:过滤器可以对响应进行后处理,如记录响应信息、修改响应头等。
响应返回客户端:过滤器将处理后的响应返回给客户端。

过滤器的生命周期

init: 过滤器初始化,通常用于加载配置信息。
doFilter: 处理请求和响应。
destroy: 过滤器销毁,通常用于释放资源

缓存请求之 ContentCachingRequestWrappe

大家都应该知道请求body只能够读取一次,你们我们可以通过ContentCachingRequestWrappe来进行处理。
ContentCachingRequestWrapper 是 spring boot 自带的请求体重复利用的工具,不过他也有缺点:

That means if the request:
content is not consumed, then the content is not cached
and cannot be retrieved via {@link #getContentAsByteArray()}

也就是说,用ContentCachingRequestWrapper的前提:

  • 你要消费这个请求数据;
  • 能过 getContentAsByteArray 方法获取缓存内容。

缓存响应之ContentCachingResponseWrapper

使用ContentCachingResponseWrapper可以对响应内容进行缓存和修改。通过包装器,开发者可以在最终响应之前修改响应头、响应体或添加额外的信息。这对于实现响应的压缩、加密或添加自定义的响应头等功能非常有帮助。

实战演示

添加依赖

首先,在 pom.xml 文件中添加所需的依赖:

<dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Hutool --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.11</version></dependency><!-- Lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- SLF4J --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></dependency>
</dependencies>

创建 LogFilter 类

创建一个名为 LogFilter 的类,并实现 Filter 接口。这个类将负责记录请求和响应的详细信息。

/*** 日志过滤器,记录请求和响应的详细信息** @author senfel* @version 1.0* @date 2025/4/27 10:00*/
@Component
@Order(1)
@Slf4j
public class LogFilter implements Filter {/*** 过滤请求和响应* @param servletRequest* @param servletResponse* @param filterChain* @throws IOException* @throws ServletException*/@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {long startTime = System.currentTimeMillis();HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;request.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setCharacterEncoding(StandardCharsets.UTF_8.name());ContentCachingRequestWrapper contentCachingRequestWrapper = new ContentCachingRequestWrapper(request);ContentCachingResponseWrapper contentCachingResponseWrapper = new ContentCachingResponseWrapper(response);String traceId = IdUtil.fastUUID();contentCachingResponseWrapper.addHeader("Demo-Trace-Id", traceId);// 继续过滤链filterChain.doFilter(contentCachingRequestWrapper, contentCachingResponseWrapper);StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("\n【请求唯一ID】");stringBuilder.append(traceId);// 请求信息logRequestInfo(stringBuilder, contentCachingRequestWrapper);// 响应信息logResponseInfo(stringBuilder, contentCachingResponseWrapper);// 回写响应contentCachingResponseWrapper.copyBodyToResponse();long endTime = System.currentTimeMillis();long useTime = endTime - startTime;stringBuilder.append("\n【请求耗时】");if (useTime >= 500 && useTime < 1000) {stringBuilder.append("【警告500-1000ms】");} else if (useTime >= 1000 && useTime < 2000) {stringBuilder.append("【警告1000-2000ms】");} else if (useTime >= 2000 && useTime < 3000) {stringBuilder.append("【警告2000-3000ms】");} else if (useTime >= 3000) {stringBuilder.append("【警告3000ms以上】");}stringBuilder.append(useTime).append("ms");log.info(stringBuilder.toString());}/*** 记录请求信息* @param logStr* @param request*/private void logRequestInfo(StringBuilder logStr, ContentCachingRequestWrapper request) {// 打印请求地址logStr.append("\n【请求地址】").append(request.getRequestURL());logStr.append("\n【请求方法】").append(request.getMethod());// 打印请求参数logStr.append("\n【请求参数】");Enumeration<String> parameterNames = request.getParameterNames();int index = 0;while (parameterNames.hasMoreElements()) {if (index > 0) {logStr.append("&");}index++;String paramName = parameterNames.nextElement();String[] paramValues = request.getParameterValues(paramName);logStr.append(paramName);logStr.append("=");logStr.append(String.join(", ", paramValues));}// 打印请求头logStr.append("\n【请求头】");Enumeration<String> headerNames = request.getHeaderNames();index = 0;while (headerNames.hasMoreElements()) {if (index > 0) {logStr.append(";");}index++;String headerName = headerNames.nextElement();String headerValue = request.getHeader(headerName);logStr.append(headerName);logStr.append(":");logStr.append(headerValue);}// 打印请求体(如果需要)byte[] contentAsByteArray = request.getContentAsByteArray();if (contentAsByteArray.length > 0) {logStr.append("\n【请求体】").append(new String(contentAsByteArray, StandardCharsets.UTF_8));} else {logStr.append("\n【请求体】").append("无");}}/*** 记录响应信息* @param logStr* @param response*/private void logResponseInfo(StringBuilder logStr, ContentCachingResponseWrapper response) {// 打印响应状态码logStr.append("\n【响应状态码】");logStr.append(response.getStatus());// 打印响应头logStr.append("\n【响应头】");int index = 0;for (String headerName : response.getHeaderNames()) {if (index > 0) {logStr.append(";");}index++;logStr.append(headerName);logStr.append(":");logStr.append(response.getHeader(headerName));}// 打印响应体byte[] contentAsByteArray = response.getContentAsByteArray();if (contentAsByteArray.length > 0) {logStr.append("\n【响应体】");logStr.append(new String(contentAsByteArray, StandardCharsets.UTF_8));} else {logStr.append("\n【响应体】").append("无");}}
}

总结

综上所述,在我们的项目中,增加一个记录所有 HTTP 请求和响应的详细信息的日志过滤器还是比较简单。相比于获取 URL、方法、参数、头信息等信息,获取请求body和响应body才是重中之重。值得注意的是springframework给我们提供了ContentCachingRequestWrapper、ContentCachingResponseWrapper两个缓存包装类,让我们可以简单的获取数据,简直不要太好。

http://www.xdnf.cn/news/3065.html

相关文章:

  • 2025年渗透测试面试题总结-拷打题库25(题目+回答)
  • 30天通过软考高项-第一天
  • 刀客doc:小红书商业技术负责人苍响离职
  • 信息系统项目管理师——第10章 项目进度管理 笔记
  • 解决Ollama run qwen3:32b: Error: unable to load model问题
  • 阵列麦克风降噪原理
  • 记录一个单独读取evt.bdf的方法
  • 头歌java课程实验(文件操作)
  • 【CF】Day46——Codeforces Round 967 (Div. 2) B
  • 2025年高级Java后端面试题:最新技术体系深度解析
  • java发送邮件
  • 运行不会存储上一次的命令;运行命令不保存历史记录
  • 算法备案类型解析:如何判断你的算法属于哪种类型?
  • conda添加新python版本环境,如何激活和销毁
  • 深入理解 Web Service:原理、组件与核心技术详解
  • c++ 内部类
  • linux使用亚马逊aws-sdk-cpp
  • 适合五一劳动节的SVG模版
  • C++/SDL 进阶游戏开发 —— 双人塔防(代号:村庄保卫战 15)
  • Windows多网卡设置路由冲突的解决
  • 程序员如何用AI工具实现“快速验证原型”
  • Rational Rose显示工具栏及添加工具栏中想要的工具(小人图标,用例图标)
  • 浏览器限制了cookie数量怎么办
  • Educational Codeforces Round 178 (Rated for Div. 2)
  • 什么是单臂路由
  • 制作JDK17 arm64基础镜像,解决字体安装问题
  • 使用Python将大文件夹中的文件分成文件数量相等的小文件夹
  • AI编程工具“幻觉”风险与飞算JavaAl的破局之道
  • 【滑动窗口】最大连续1的个数|将x减到0的最小操作数
  • 消息处理机制 Handler、Looper 和 MessageQueue