星驰编程网

免费编程资源分享平台_编程教程_代码示例_开发技术文章

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

技术背景

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

实现步骤

1. 使用Spring Boot Actuator

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

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

2. 使用CommonsRequestLoggingFilter

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

  • 配置过滤器:在配置类中添加过滤器Bean。
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
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG

3. 自定义DispatcherServlet

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

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
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依赖。
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>2.4.1</version>
</dependency>
  • 设置日志级别:在application.properties中设置org.zalando.logbook的日志级别为TRACE
logging.level.org.zalando.logbook=TRACE

5. 使用Spring AOP

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

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
的完整代码示例:

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中设置日志级别:

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

最佳实践

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

常见问题

1. 日志中没有响应信息

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

2. 日志记录不完整

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

3. 性能问题

  • 原因:过多的日志记录可能会影响应用的性能。
  • 解决方法:在生产环境中,适当控制日志级别,避免记录不必要的信息。可以使用异步日志记录来减少对应用性能的影响。
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言