【SpringBoot】 记录Http请求响应日志和SQL日志

1,438 阅读2分钟

一、目的

实现以下效果:

image.png

二、干

1。 新建一个GlobalHttpLogger切面类,用来配置Http请求响应日志,使用Aop的方式完成这个功能。

import com.google.gson.Gson;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.concurrent.locks.ReentrantLock;

@Aspect
@Component
public class GlobalHttpLogger {

    private static final Logger logger = LoggerFactory.getLogger(GlobalHttpLog.class);
    private ReentrantLock lock = new ReentrantLock();

    @Pointcut("execution(public * vip.xiaonuo..*.controller..*.*(..))")
    public void webLog() {
    }
    
    @Around("webLog()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取开始时间并将其保存到请求属性中
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);

        try {
            lock.lock();
            // 打印请求信息
            logger.info("========================================== Start ==========================================");
            logger.info("URL            : {}", request.getRequestURL().toString());
            logger.info("HTTP Method    : {}", request.getMethod());
            logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
            logger.info("IP             : {}", request.getRemoteAddr());
            logger.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));

            // 继续执行目标方法
            Object result = joinPoint.proceed();

            // 打印响应信息
            logger.info("Response       : {}", new Gson().toJson(result));
            logger.info("Time-Consuming : {} ms", (System.currentTimeMillis() - startTime));
            logger.info("=========================================== End ===========================================");
            logger.info("");

            return result;
        } catch (Throwable ex) {
            // 捕获异常并记录错误信息
            logger.error("Exception in {}.{}(): {}",
                    joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName(),
                    ex.getMessage(), ex);
            throw ex;  // 重新抛出异常以保持原始行为
        } finally {
            lock.unlock();
            // 确保清除任何资源或状态
            // 这里可以清理一些与请求相关的资源,如果有必要的话
        }
    }
}
  1. 引入p6spy依赖开始配置sql打印
<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.8.2</version>
</dependency>
  1. 新建Spy配置类实现MessageFormattingStrategy类,并重写方法。
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;

public class P6spyLogger implements MessageFormattingStrategy {

    private static final Logger logger = LoggerFactory.getLogger(P6spyLogger.class);
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");

    @Override
    public String formatMessage(int i, String s, long elapsed, String s1, String s2, String sql, String s4) {
        if (!"".equals(s.trim())) {
            return "SQL执行         : " + elapsed + "ms ---> "+sql;
        }
        return "";
    }
}
  1. 在resource目录下新建spy.properties文件,内容如下。注意logMessageFormat更换成自己的配置类
module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory

# 自定义SQL打印处理类
logMessageFormat=vip.xiaonuo.core.log.P6spyLogger

# 自带的日志格式 (default is com.p6spy.engine.spy.appender.FileLogger)
#appender=com.p6spy.engine.spy.appender.StdoutLogger
appender=com.p6spy.engine.spy.appender.Slf4JLogger
#appender=com.p6spy.engine.spy.appender.FileLogger

# 配置记录Log例外
excludecategories=info,debug,result,resultset

# 设置使用p6spy driver来做代理
deregisterdrivers=true

# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss

# 实际驱动
driverlist=com.mysql.cj.jdbc.Driver

# 是否开启慢SQL记录
outagedetection=true

# 慢SQL记录标准 秒
outagedetectioninterval=2

5.更改mybatis数据库链接url与driverClass

spring.datasource.dynamic.datasource.master.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
spring.datasource.dynamic.datasource.master.url=jdbc:p6spy:mysql://localhost:3306/snowy?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&useInformationSchema=true

ok,完事!测试运行即可。

三、代码解释

  • 使用一个Around切面就行了,其他的都多余,会出现bug。
  • 为什么代码中打印加锁?很明确,为了保证日志打印的完整性,连贯性。
  • 不加锁,一旦同时发起多个请求日志打印就混乱了。
  • 本日志配置仅仅适用于开发环境,生产环境请把此类注释掉。
  • 或者使用ConditionalOnProperty注解动态开启关闭