spring boot 之 结合aop整合日志

AOP

该切面仅用于请求日志记录,若有其他需求,在此基础上扩展即可,不多逼逼,直接上代码。

引入切面依赖

xml 复制代码
<!-- 切面 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

日志切面类

java 复制代码
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注解即可。

xml 复制代码
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
    <scope>provided</scope>
</dependency>

日志配置文件

resources下新建目录logslogs下新建logback-spring.xml文件。

仅配置了常用的infoerror级别,其余按需配置即可。

xml 复制代码
<?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配置

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.logLogApplication-error.log,项目中所有log.error()日志都会输出到LogApplication-error.log,其余日志则输出到LogApplication.log.

拓展

将指定的类产生的日志输出到指定的文件中。

示例:RequestAop切面中产生的是所有的请求记录,将该类的日志放入指定的文件。

logback-spring.xml新增配置,未添加请求日志文件的大小限制、存放时间等配置,若有需求,按infoerror配置仿写即可。

xml 复制代码
    <!-- 接口请求日志 -->
    <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.logLogApplication-error.logLogApplication-request.log,项目中所有log.error()日志都会输出到LogApplication-error.logRequestAop切面类的日志会输出到LogApplication-request.log,其余日志则输出到LogApplication.log.

相关推荐
WaaTong13 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_7430484414 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries15 分钟前
Java字节码增强库ByteBuddy
java·后端
佳佳_30 分钟前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
小灰灰__35 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭39 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
许野平2 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono