一、目的
实现以下效果:
二、干
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();
// 确保清除任何资源或状态
// 这里可以清理一些与请求相关的资源,如果有必要的话
}
}
}
- 引入p6spy依赖开始配置sql打印
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.2</version>
</dependency>
- 新建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 "";
}
}
- 在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注解动态开启关闭