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()")
@SneakyThrows
public 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无法使用toJSON
if (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: LogApplication
logging:
# 指定自定义的配置文件
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
.