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.

相关推荐
懒羊羊不懒@21 分钟前
Java基础语法—最小单位、及注释
java·c语言·开发语言·数据结构·学习·算法
ss27325 分钟前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
DokiDoki之父36 分钟前
MyBatis—增删查改操作
java·spring boot·mybatis
兩尛1 小时前
Spring面试
java·spring·面试
舒一笑1 小时前
🚀 PandaCoder 2.0.0 - ES DSL Monitor & SQL Monitor 震撼发布!
后端·ai编程·intellij idea
Java中文社群1 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
Full Stack Developme1 小时前
java.nio 包详解
java·python·nio
零千叶1 小时前
【面试】Java JVM 调优面试手册
java·开发语言·jvm
代码充电宝2 小时前
LeetCode 算法题【简单】290. 单词规律
java·算法·leetcode·职场和发展·哈希表
li3714908902 小时前
nginx报400bad request 请求头过大异常处理
java·运维·nginx