AOP
该切面仅用于请求日志记录,若有其他需求,在此基础上扩展即可,不多逼逼,直接上代码。
引入切面依赖
<!-- 切面 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
日志切面类
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;@Slf4j
@Aspect
@Component
public class RequestAop {private static final String START_TIME = "request-start";// 按需修改需要扫描的controller层@Pointcut("execution(* com.example.controller..*.*(..))")public void pointCut() {//该方法仅用于扫描controller包下类中的方法,而不做任何特殊的处理。}@Before("pointCut()")public void doBefore(JoinPoint joinPoint) {HttpServletRequest request =((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();Long start = System.currentTimeMillis();request.setAttribute(START_TIME, start);}@Around("pointCut()")@SneakyThrowspublic Object doAround(ProceedingJoinPoint joinPoint) {Object result = joinPoint.proceed();try {// 获取方法名称String method = joinPoint.getSignature().getName();// 获取类名称String className = joinPoint.getSignature().getDeclaringTypeName();// 获取请求HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();// 请求路径String requestUrl = request.getRequestURL().toString();// 获取请求参数进行打印Signature signature = joinPoint.getSignature();// 参数名数组String[] parameterNames = ((MethodSignature) signature).getParameterNames();// 构造参数组集合List<Object> argList = new ArrayList<>();for (Object arg : joinPoint.getArgs()) {// request/response无法使用toJSONif (arg instanceof HttpServletRequest) {argList.add("request");} else if (arg instanceof HttpServletResponse) {argList.add("response");} else {argList.add(JSON.toJSON(arg));}}log.info("类名:[{}] 方法名:[{}] 请求URL:[{}] 请求参数:{} -> {} 请求结果:{}", className, method, requestUrl, JSON.toJSON(parameterNames), JSON.toJSON(argList), JSON.toJSON(result));} catch (Exception e) {log.error("切面类参数获取失败: {}", e.getMessage());}return result;}@After("pointCut()")public void doAfter(JoinPoint joinPoint) {HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();Long start = (Long) request.getAttribute(START_TIME);Long end = System.currentTimeMillis();// 耗时long costTime = end - start;// 方法名String method = joinPoint.getSignature().getName();log.info("方法名:[{}] 请求耗时:[{}ms]", method, costTime);}
}
日志
级别
日志级别(Log Levels)是指日志消息的优先级或者重要程度,它用于对日志的不同类型和重要程度进行分类和过滤。
不同的日志框架可能使用不同的命名和数量的日志级别,但基本概念是相似的。以下是常见的几个标准日志级别:
1,TRACE(追踪)
:最低级别的日志,包含详细的调试信息,用于追踪代码的执行流程,如方法的输入参数、内部状态等。
2,DEBUG(调试)
:用于输出调试信息,在开发和调试阶段使用,帮助排查问题和跟踪代码执行情况以及验证程序的行为。
3,INFO(信息)
:提供程序运行过程中的重要信息,用于向用户提供一些关键的操作状态和进度,如程序启动关闭、配置项变更等。
4,WARN(警告)
:表示潜在的问题或异常情况,不会阻止程序继续执行,但可能会影响程序的正常运行,需要开发人员注意。
5,ERROR(错误)
:表示错误情况,通常表示某个功能或步骤无法正常完成,但程序仍然可以继续运行,需要开发人员关注和解决。
6,FATAL(致命)
:最高级别的日志,表示最严重的错误,表示程序无法继续运行,会导致应用程序的中断或崩溃,如系统崩溃。
特别说明:以上日志级别由上往下依次增强,而日志级别越高,控制台打印出的日志信息就越少,但打印出的日志信息越重要。
引入lombok
依赖
引入lombok
后,在需要记录日志的类上添加@Slf4j
注解即可。
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.32</version><scope>provided</scope>
</dependency>
日志配置文件
在resources
下新建目录logs
,logs
下新建logback-spring.xml
文件。
仅配置了常用的info
和error
级别,其余按需配置即可。
<?xml version="1.0" encoding="utf-8"?>
<configuration><!-- 引入默认得配置文件 --><include resource="org/springframework/boot/logging/logback/defaults.xml"/><!-- 模块名标识日志名称 --><springProperty scope="context" name="springAppName" source="spring.application.name"/><!-- info日志单文件大小限制 --><springProperty scope="context" name="logback.fileInfoLog.maxFileSize" source="logback.fileInfoLog.maxFileSize" defaultValue="1024MB" /><!-- info日志最大保留时长单位天 --><springProperty scope="context" name="logback.fileInfoLog.maxHistory" source="logback.fileInfoLog.maxHistory" defaultValue="30" /><!-- info日志文件总大小,超过该大小,旧得即将删除 --><springProperty scope="context" name="logback.fileInfoLog.totalSizeCap" source="logback.fileInfoLog.totalSizeCap" defaultValue="10GB" /><!-- error日志单文件大小限制 --><springProperty scope="context" name="logback.fileErrorLog.maxFileSize" source="logback.fileErrorLog.maxFileSize" defaultValue="1024MB" /><!-- error日志最大保留时长单位天 --><springProperty scope="context" name="logback.fileErrorLog.maxHistory" source="logback.fileErrorLog.maxHistory" defaultValue="30" /><!-- error日志文件总大小,超过该大小,旧得即将删除 --><springProperty scope="context" name="logback.fileErrorLog.totalSizeCap" source="logback.fileErrorLog.totalSizeCap" defaultValue="10GB" /><!-- 日志目录 --><springProperty scope="context" name="logback.rootDir" source="logback.rootDir" defaultValue="logs"/><!-- 控制台输出得日志格式 --><property name="CONSOLE_LOG_PATTERN"value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/><!-- 日志文件输出得日志格式 --><property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p %t [%c:%L]-%m%n"/><!-- 控制台输出 --><appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender"><layout class="ch.qos.logback.classic.PatternLayout"><pattern>${CONSOLE_LOG_PATTERN}</pattern></layout></appender><!-- info日志得设定 --><appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>DENY</onMatch><onMismatch>ACCEPT</onMismatch></filter><encoder><pattern>${FILE_LOG_PATTERN}</pattern></encoder><file>${logback.rootDir}/${springAppName}.log</file><!--滚动策略--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" ><!--路径--><fileNamePattern>${logback.rootDir}/%d{yyyy-MM,aux}/%d{yyyy-MM-dd,aux}/${springAppName}-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern><maxFileSize>${logback.fileInfoLog.maxFileSize}</maxFileSize><maxHistory>${logback.fileInfoLog.maxHistory}</maxHistory><totalSizeCap>${logback.fileInfoLog.totalSizeCap}</totalSizeCap><cleanHistoryOnStart>true</cleanHistoryOnStart></rollingPolicy></appender><!-- 错误日志 --><appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>ERROR</level></filter><encoder><pattern>${FILE_LOG_PATTERN}</pattern></encoder><file>${logback.rootDir}/${springAppName}-error.log</file><!--滚动策略--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" ><!--路径--><fileNamePattern>${logback.rootDir}/%d{yyyy-MM,aux}/%d{yyyy-MM-dd,aux}/${springAppName}-error-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern><maxFileSize>${logback.fileErrorLog.maxFileSize}</maxFileSize><maxHistory>${logback.fileErrorLog.maxHistory}</maxHistory><totalSizeCap>${logback.fileErrorLog.totalSizeCap}</totalSizeCap><cleanHistoryOnStart>true</cleanHistoryOnStart></rollingPolicy></appender><appender name="ASYNC_consoleLog" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="consoleLog"/></appender><appender name="ASYNC_fileInfoLog" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="fileInfoLog"/></appender><appender name="ASYNC_fileErrorLog" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="fileErrorLog"/></appender><root level="info"><appender-ref ref="ASYNC_consoleLog" /><appender-ref ref="ASYNC_fileInfoLog" /><appender-ref ref="ASYNC_fileErrorLog" /></root></configuration>
application.yml
配置
# spring.application.name 必须配置
# 因为上述日志配置文件指定了项目启动后输出的日志文件命名,即为该配置
spring:application:name: LogApplicationlogging:
# 指定自定义的配置文件config: classpath:logs/logback-spring.xml
# 指定输出的日志级别
# trace < debug < info < warn < error
# 例如:指定输出级别为info,则trace和debug均不会输出level:root: info #该方式指定的是整个项目的日志输出级别# com.example.controller: debug #也可以指定具体某个包下的日志输出级别
结果展示
以上述配置为例,项目启动后会在项目下生成logs
目录,该目录下会有两个日志文件:LogApplication.log
和 LogApplication-error.log
,项目中所有log.error()
日志都会输出到LogApplication-error.log
,其余日志则输出到LogApplication.log
.
拓展
将指定的类产生的日志输出到指定的文件中。
示例:RequestAop
切面中产生的是所有的请求记录,将该类的日志放入指定的文件。
logback-spring.xml
新增配置,未添加请求日志文件的大小限制、存放时间等配置,若有需求,按info
、error
配置仿写即可。
<!-- 接口请求日志 --><appender name="requestLog" class="ch.qos.logback.core.rolling.RollingFileAppender"><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERROR</level><onMatch>DENY</onMatch><onMismatch>ACCEPT</onMismatch></filter><encoder><pattern>${FILE_LOG_PATTERN}</pattern></encoder><!--此处配置输出文件名称为 应用名-request.log --><file>${logback.rootDir}/${springAppName}-request.log</file><!--滚动策略--><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy" ><!--路径--><fileNamePattern>${logback.rootDir}/%d{yyyy-MM,aux}/%d{yyyy-MM-dd,aux}/${springAppName}-request-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern><maxFileSize>${logback.fileInfoLog.maxFileSize}</maxFileSize><maxHistory>${logback.fileInfoLog.maxHistory}</maxHistory><totalSizeCap>${logback.fileInfoLog.totalSizeCap}</totalSizeCap><cleanHistoryOnStart>true</cleanHistoryOnStart></rollingPolicy></appender><appender name="ASYNC_requestLog" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="requestLog"/></appender><!--将切面类所在包的位置配置上--><logger name="com.example.aop.RequestAop" additivity="false" level="INFO"><appender-ref ref="ASYNC_requestLog"/></logger>
以上述配置为例,项目启动后会在项目下生成logs
目录,该目录下会有三个日志文件:LogApplication.log
、 LogApplication-error.log
、LogApplication-request.log
,项目中所有log.error()
日志都会输出到LogApplication-error.log
,RequestAop
切面类的日志会输出到LogApplication-request.log
,其余日志则输出到LogApplication.log
.