Spring Boot中统一记录请求、响应及异常日志的方法

Spring Boot中统一记录请求、响应及异常日志的方法

技术背景

在开发基于Spring Boot的REST API时,为了便于调试、监控和问题排查,需要对所有请求和响应进行详细的日志记录,包括请求的输入参数、请求路径、查询字符串、对应的类方法,以及响应结果(包括成功和错误情况)。

实现步骤

1. 使用Spring Boot Actuator

Actuator是Spring Boot的一个模块,它提供了HTTP请求日志记录功能。

  • 添加依赖:在pom.xml中添加spring-boot-starter-actuator依赖。
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 配置端点:在application.properties中配置要暴露的端点。
1
management.endpoints.web.exposure.include=*
  • 查看日志:启动应用后,可以通过访问/actuator/httptrace查看最近的100个HTTP请求。

2. 使用CommonsRequestLoggingFilter

Spring提供了CommonsRequestLoggingFilter来记录请求日志。

  • 配置过滤器:在配置类中添加过滤器Bean。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

@Configuration
public class LoggingConfig {

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludeClientInfo(true);
loggingFilter.setIncludeQueryString(true);
loggingFilter.setIncludePayload(true);
loggingFilter.setMaxPayloadLength(64000);
return loggingFilter;
}
}
  • 设置日志级别:在application.properties中设置过滤器的日志级别为DEBUG
1
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG

3. 自定义DispatcherServlet

通过自定义DispatcherServlet可以同时记录请求和响应日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoggableDispatcherServlet extends DispatcherServlet {

private final Logger logger = LoggerFactory.getLogger(getClass());

@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (!(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper(request);
}
if (!(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper(response);
}
try {
super.doDispatch(request, response);
} finally {
log(request, response);
updateResponse(response);
}
}

private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache) {
int status = responseToCache.getStatus();
StringBuilder logMessage = new StringBuilder();
logMessage.append("HttpStatus: ").append(status)
.append(", path: ").append(requestToCache.getRequestURI())
.append(", method: ").append(requestToCache.getMethod())
.append(", clientIp: ").append(requestToCache.getRemoteAddr());
// 可以添加更多信息
logger.info(logMessage.toString());
}

private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper =
(ContentCachingResponseWrapper) response;
responseWrapper.copyBodyToResponse();
}
}
  • 注册DispatcherServlet:在配置类中注册自定义的DispatcherServlet
1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
public class ServletConfig {

@Bean
public ServletRegistrationBean<DispatcherServlet> dispatcherRegistration() {
return new ServletRegistrationBean<>(new LoggableDispatcherServlet(), "/");
}
}

4. 使用Logbook库

Logbook是一个专门用于记录HTTP请求和响应的库。

  • 添加依赖:在pom.xml中添加logbook-spring-boot-starter依赖。
1
2
3
4
5
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>2.4.1</version>
</dependency>
  • 设置日志级别:在application.properties中设置org.zalando.logbook的日志级别为TRACE
1
logging.level.org.zalando.logbook=TRACE

5. 使用Spring AOP

通过Spring AOP可以在方法执行前后记录请求和响应信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class LoggingAspect {

private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

@Before("execution(* com.example.demo.controller.*.*(..))")
public void before(JoinPoint joinPoint) {
logger.info("Before method: {}", joinPoint.getSignature().toShortString());
logger.info("Arguments: {}", Arrays.toString(joinPoint.getArgs()));
}

@AfterReturning(pointcut = "execution(* com.example.demo.controller.*.*(..))", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
logger.info("After method: {}", joinPoint.getSignature().toShortString());
logger.info("Result: {}", result);
}

@AfterThrowing(pointcut = "execution(* com.example.demo.controller.*.*(..))", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
logger.error("Exception in method: {}", joinPoint.getSignature().toShortString(), ex);
}
}

核心代码

以下是使用CommonsRequestLoggingFilter的完整代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

@Configuration
public class LoggingConfig {

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludeClientInfo(true);
loggingFilter.setIncludeQueryString(true);
loggingFilter.setIncludePayload(true);
loggingFilter.setMaxPayloadLength(64000);
return loggingFilter;
}
}

application.properties中设置日志级别:

1
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG

最佳实践

  • 选择合适的方法:根据项目的具体需求和复杂度,选择合适的日志记录方法。如果只是简单的请求日志记录,可以使用CommonsRequestLoggingFilter;如果需要更详细的信息,可以考虑自定义DispatcherServlet或使用Logbook库。
  • 控制日志级别:在生产环境中,应适当控制日志级别,避免产生过多的日志信息影响性能。可以将日志级别设置为INFOWARN,只记录关键信息。
  • 日志存储和分析:将日志存储在合适的地方,如文件系统或日志管理系统(如ELK Stack),并进行定期分析,以便及时发现和解决问题。

常见问题

1. 日志中没有响应信息

  • 原因:某些方法(如CommonsRequestLoggingFilter)只记录请求信息,不记录响应信息。
  • 解决方法:可以使用自定义DispatcherServlet或其他方法来记录响应信息。

2. 日志记录不完整

  • 原因:可能是由于日志级别设置不正确或过滤器配置不当导致的。
  • 解决方法:检查日志级别设置,确保过滤器的配置正确,如设置setMaxPayloadLength来记录完整的请求和响应体。

3. 性能问题

  • 原因:过多的日志记录可能会影响应用的性能。
  • 解决方法:在生产环境中,适当控制日志级别,避免记录不必要的信息。可以使用异步日志记录来减少对应用性能的影响。

Spring Boot中统一记录请求、响应及异常日志的方法
https://119291.xyz/posts/2025-04-11.spring-boot-unified-logging-of-requests-responses-and-exceptions/
作者
ww
发布于
2025年4月22日
许可协议