2. 引言
在开发中,我们经常需要对 HTTP 请求体进行额外处理,比如日志记录。记录请求日志是调试应用的关键手段。
本文将介绍如何使用 Spring Boot 的日志过滤器记录请求日志的两种方案:
✅ 自定义过滤器方案(灵活但需手动实现)
✅ Spring Boot 内置过滤器方案(开箱即用)
3. Maven 依赖
首先在 pom.xml
添加核心依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.2</version>
</dependency>
这个依赖提供了 Spring Boot 应用记录请求日志所需的所有基础组件。
4. 基础 Web 控制器
先定义一个测试用控制器:
@RestController
public class TaxiFareController {
@GetMapping("/taxifare/get/")
public RateCard getTaxiFare() {
return new RateCard();
}
@PostMapping("/taxifare/calculate/")
public String calculateTaxiFare(
@RequestBody @Valid TaxiRide taxiRide) {
// 返回计算结果
}
}
接下来我们将用两种方式记录请求到该控制器的日志。
5. 自定义请求日志方案
通过自定义过滤器在请求到达控制器前捕获并记录请求体。
5.1 封装 HTTP 请求
需要用 HttpServletRequestWrapper
包装请求对象:
public class CachedHttpServletRequest extends HttpServletRequestWrapper {
private byte[] cachedPayload;
public CachedHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedPayload = StreamUtils.copyToByteArray(requestInputStream);
}
}
⚠️ 关键点:在构造函数中读取请求体并缓存到字节数组 cachedPayload
。
接着重写 getInputStream()
和 getReader()
方法:
@Override
public ServletInputStream getInputStream() {
return new CachedServletInputStream(this.cachedPayload);
}
@Override
public BufferedReader getReader() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedPayload);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
5.2 扩展 ServletInputStream
创建自定义输入流类:
public class CachedServletInputStream extends ServletInputStream {
private final static Logger LOGGER = LoggerFactory.getLogger(CachedServletInputStream.class);
private InputStream cachedInputStream;
public CachedServletInputStream(byte[] cachedBody) {
this.cachedInputStream = new ByteArrayInputStream(cachedBody);
}
}
重写核心方法:
@Override
public boolean isFinished() {
try {
return cachedInputStream.available() == 0;
} catch (IOException exp) {
LOGGER.error(exp.getMessage());
}
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return cachedInputStream.read();
}
5.3 自定义过滤器
创建过滤器类:
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@WebFilter(filterName = "RequestCachingFilter", urlPatterns = "/*")
public class RequestCachingFilter extends OncePerRequestFilter {
private final static Logger LOGGER = LoggerFactory.getLogger(RequestCachingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
CachedHttpServletRequest cachedHttpServletRequest = new CachedHttpServletRequest(request);
LOGGER.info("REQUEST DATA: " + IOUtils.toString(cachedHttpServletRequest.getInputStream(), StandardCharsets.UTF_8));
filterChain.doFilter(cachedHttpServletRequest, response);
}
}
📌 说明:
@Order
确保过滤器优先级最高urlPatterns="/*"
匹配所有请求- 核心逻辑:用包装类读取请求体并记录日志
6. Spring Boot 内置日志方案
Spring Boot 提供了现成的请求日志过滤器,通过配置即可使用。
AbstractRequestLoggingFilter
是基础类,其子类需重写 beforeRequest()
和 afterRequest()
方法。Spring Boot 提供了三个实现:
CommonsRequestLoggingFilter
(推荐)Log4jNestedDiagnosticContextFilter
(已废弃)ServletContextRequestLoggingFilter
6.1 配置 CommonsRequestLoggingFilter
添加配置类:
@Configuration
public class RequestLoggingFilterConfig {
@Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter
= new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(false);
filter.setAfterMessagePrefix("REQUEST DATA: ");
return filter;
}
}
配置项说明:
includeQueryString
:记录查询参数includePayload
:记录请求体maxPayloadLength
:请求体最大长度includeHeaders
:是否记录请求头
6.2 配置日志级别
必须将过滤器日志级别设为 DEBUG
:
方式一:logback.xml
<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
<level value="DEBUG" />
</logger>
方式二:application.properties
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
⚠️ 踩坑提示:未设置 DEBUG
级别将不会输出日志!
7. 实战演示
编写测试用例验证:
@Test
public void givenRequest_whenFetchTaxiFareRateCard_thanOK() {
TestRestTemplate testRestTemplate = new TestRestTemplate();
TaxiRide taxiRide = new TaxiRide(true, 10l);
String fare = testRestTemplate.postForObject(
URL + "calculate/",
taxiRide, String.class);
assertThat(fare, equalTo("200"));
}
执行测试后控制台输出:
18:24:04.318 [] INFO c.b.web.log.app.RequestCachingFilter - REQUEST DATA: {"isNightSurcharge":true,"distanceInMile":10}
18:24:04.321 [] DEBUG o.s.w.f.CommonsRequestLoggingFilter - Before request [POST /spring-rest/taxifare/calculate/]
18:24:04.429 [] DEBUG o.s.w.f.CommonsRequestLoggingFilter - REQUEST DATA: POST /spring-rest/taxifare/calculate/, payload={"isNightSurcharge":true,"distanceInMile":10}]
两种方案都成功记录了请求体数据。
8. 总结
本文介绍了两种 Spring Boot 记录请求日志的方案:
方案 | 优点 | 缺点 |
---|---|---|
自定义过滤器 | 灵活可控 | 需手动实现 |
内置过滤器 | 开箱即用 | 配置项有限 |
推荐场景:
- 需要深度定制日志格式 → 用自定义过滤器
- 快速实现基础日志 → 用
CommonsRequestLoggingFilter
完整代码示例见 GitHub 仓库。