一.log4j2配置详解

1.pom.xml依赖配置

需先将springboot自带的日志关闭

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

引入log4j2

使用高一点的版本,低版本存在安全漏洞

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.16.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.16.0</version>
</dependency>

2.log4j2.xml 配置

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF 0 > FATAL 100 > ERROR 200 > WARN 300 > INFO 400 > DEBUG 500 > TRACE 600 > ALL 700-->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="1800">

    <Properties>
        <!-- ==============================================公共配置============================================== -->
        <!-- 设置日志文件的目录名称 -->
        <property name="logFileName">logs_file</property>

        <!-- 日志默认存放的位置,可以设置为项目根路径下,也可指定绝对路径 -->
        <!-- 存放路径一:通用路径,window平台 -->
        <property name="basePath">d:/logs/${logFileName}</property>
<!--        <property name="basePath">/usr/local/log/logs/${logFileName}</property>-->
        <!-- 存放路径二:web工程专用,java项目没有这个变量,需要删掉,否则会报异常,这里把日志放在web项目的根目录下 -->
        <!-- <property name="basePath">${web:rootDir}/${logFileName}</property> -->
        <!-- 存放路径三:web工程专用,java项目没有这个变量,需要删掉,否则会报异常,这里把日志放在tocmat的logs目录下 -->
        <!-- <property name="basePath">${sys:catalina.home}/logs/${logFileName}</property>-->

        <!-- 控制台默认输出格式,"%-5level":日志级别,"%l":输出完整的错误位置,是小写的L,因为有行号显示,所以影响日志输出的性能,根据等级设置颜色 -->
        <property name="console_log_pattern">%d %highlight{%-5level}{ERROR=Bright RED, WARN=Bright Yellow, APIINFO=Bright BLUE, INFO=Bright Green, DEBUG=Bright Cyan, TRACE=Bright White} %style{[%t]}{bright,magenta} %style{%c{1}.%M(%F:%L)}{cyan}: %msg%n</property>
        <!-- 日志文件默认输出格式,不带行号输出(行号显示会影响日志输出性能);%C:大写,类名;%M:方法名;%m:错误信息;%n:换行 -->
        <!-- <property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %C.%M - %m%n</property> -->
        <!-- 日志文件默认输出格式,另类带行号输出(对日志输出性能未知);%C:大写,类名;%M:方法名;%L:行号;%m:错误信息;%n:换行 -->
        <property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %C.%M[%L line] - %m%n</property>

        <!-- 日志默认切割的最小单位 -->
        <property name="every_file_size">20MB</property>
        <!-- 日志默认输出级别 -->
        <property name="output_log_level">DEBUG</property>

        <!-- ===========================================所有级别日志配=========================================== -->
        <!-- 日志默认存放路径(所有级别日志) -->
        <property name="rolling_fileName">${basePath}/all.log</property>
        <!-- 日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="rolling_filePattern">${basePath}/%d{yyyy-MM}/all-%d{yyyy-MM-dd-HH}-%i.log.gz</property>
        <!-- 日志默认同类型日志,同一文件夹下可以存放的数量,不设置此属性则默认为7个,filePattern最后要带%i才会生效 -->
        <property name="rolling_max">500</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="rolling_timeInterval">12</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="rolling_timeModulate">true</property>

        <!-- ============================================apiInfo级别日志============================================ -->
        <!-- Info日志默认存放路径(Info级别日志) -->
        <property name="apiInfo_fileName">${basePath}/apiInfo.log</property>
        <!-- Info日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="apiInfo_filePattern">${basePath}/%d{yyyy-MM}/apiInfo-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- Info日志默认同一文件夹下可以存放的数量,不设置此属性则默认为7个 -->
        <property name="apiInfo_max">100</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="apiInfo_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="apiInfo_timeModulate">true</property>

          <!-- ============================================Info级别日志============================================ -->
        <!-- Info日志默认存放路径(Info级别日志) -->
        <property name="info_fileName">${basePath}/info.log</property>
        <!-- Info日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="info_filePattern">${basePath}/%d{yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- Info日志默认同一文件夹下可以存放的数量,不设置此属性则默认为7个 -->
        <property name="info_max">100</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="info_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="info_timeModulate">true</property>

        <!-- ============================================Warn级别日志============================================ -->
        <!-- Warn日志默认存放路径(Warn级别日志) -->
        <property name="warn_fileName">${basePath}/warn.log</property>
        <!-- Warn日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="warn_filePattern">${basePath}/%d{yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- Warn日志默认同一文件夹下可以存放的数量,不设置此属性则默认为7个 -->
        <property name="warn_max">100</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="warn_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="warn_timeModulate">true</property>

        <!-- ============================================Error级别日志============================================ -->
        <!-- Error日志默认存放路径(Error级别日志) -->
        <property name="error_fileName">${basePath}/error.log</property>
        <!-- Error日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="error_filePattern">${basePath}/%d{yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- Error日志默认同一文件夹下可以存放的数量,不设置此属性则默认为7个 -->
        <property name="error_max">100</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="error_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="error_timeModulate">true</property>

        <!-- ============================================DEBUG级别日志============================================ -->
        <!-- Error日志默认存放路径(Error级别日志) -->
        <property name="debug_fileName">${basePath}/debug.log</property>
        <!-- Error日志默认压缩路径,将超过指定文件大小的日志,自动存入按"年月"建立的文件夹下面并进行压缩,作为存档 -->
        <property name="debug_filePattern">${basePath}/%d{yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz</property>
        <!-- Error日志默认同一文件夹下可以存放的数量,不设置此属性则默认为7个 -->
        <property name="debug_max">100</property>
        <!-- 日志默认同类型日志,多久生成一个新的日志文件,这个配置需要和filePattern结合使用;
                如果设置为1,filePattern是%d{yyyy-MM-dd}到天的格式,则间隔一天生成一个文件
                如果设置为12,filePattern是%d{yyyy-MM-dd-HH}到小时的格式,则间隔12小时生成一个文件 -->
        <property name="debug_timeInterval">1</property>
        <!-- 日志默认同类型日志,是否对封存时间进行调制,若为true,则封存时间将以0点为边界进行调整,
                如:现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am -->
        <property name="debug_timeModulate">true</property>


        <!-- ============================================控制台显示控制============================================ -->
        <!-- 控制台显示的日志最低级别 -->
        <property name="console_print_level">INFO</property>

    </Properties>
    <!--    自定义日志等级 -->
    <CustomLevels>
        <!--注意 : intLevel 值越小,级别越高 (log4j2 官方文档)-->
        <!--日志级别以及优先级排序: OFF 0 > FATAL 100 > ERROR 200 > WARN 300 > INFO 400 > DEBUG 500 > TRACE 600 > ALL 700-->
        <CustomLevel name="APIINFO" intLevel="350" />
    </CustomLevels>
    <!--定义appender -->
    <appenders>
        <!-- =======================================用来定义输出到控制台的配置======================================= -->
        <Console name="Console" target="SYSTEM_OUT">
            <!-- 设置控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="${console_print_level}" onMatch="ACCEPT" onMismatch="DENY"/>
            <!-- 设置输出格式,不设置默认为:%m%n -->
            <PatternLayout pattern="${console_log_pattern}" disableAnsi="false"/>

        </Console>
        <!-- 自定义日志级别 -->
        <!-- =======================================打印APIINFO级别的日志到文件======================================= -->
        <RollingFile name="ApiInfoFile" fileName="${apiInfo_fileName}" filePattern="${apiInfo_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${apiInfo_timeInterval}" modulate="${apiInfo_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <DefaultRolloverStrategy max="${apiInfo_max}" />
            <Filters>
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="APIINFO" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>

        <!-- ================================打印root中指定的level级别以上的日志到文件================================ -->
        <RollingFile name="RollingFile" fileName="${rolling_fileName}" filePattern="${rolling_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${rolling_timeInterval}" modulate="${warn_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <!-- 设置同类型日志,同一文件夹下可以存放的数量,如果不设置此属性则默认存放7个文件 -->
            <DefaultRolloverStrategy max="${rolling_max}" />
        </RollingFile>

        <!-- =======================================打印INFO级别的日志到文件======================================= -->
        <RollingFile name="InfoFile" fileName="${info_fileName}" filePattern="${info_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${info_timeInterval}" modulate="${info_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <DefaultRolloverStrategy max="${info_max}" />
            <Filters>
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>

        <!-- =======================================打印WARN级别的日志到文件======================================= -->
        <RollingFile name="WarnFile" fileName="${warn_fileName}" filePattern="${warn_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${warn_timeInterval}" modulate="${warn_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <DefaultRolloverStrategy max="${warn_max}" />
            <Filters>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>

        <!-- =======================================打印ERROR级别的日志到文件======================================= -->
        <RollingFile name="ErrorFile" fileName="${error_fileName}" filePattern="${error_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${error_timeInterval}" modulate="${error_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <DefaultRolloverStrategy max="${error_max}" />
            <Filters>
                <ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>

        <!-- =======================================打印DEBUG级别的日志到文件======================================= -->
        <RollingFile name="DebugFile" fileName="${debug_fileName}" filePattern="${debug_filePattern}">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <TimeBasedTriggeringPolicy interval="${debug_timeInterval}" modulate="${debug_timeModulate}"/>
                <SizeBasedTriggeringPolicy size="${every_file_size}"/>
            </Policies>
            <DefaultRolloverStrategy max="${debug_max}" />
            <Filters>
                <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
                <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </RollingFile>
    </appenders>

    <!--定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!-- 设置打印sql语句配置开始,以下两者配合使用,可以优化日志的输出信息,减少一些不必要信息的输出 -->
        <!-- 设置java.sql包下的日志只打印DEBUG及以上级别的日志,此设置可以支持sql语句的日志打印 -->
        <logger name="org.apache.ibatis" level="INFO">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFile"/>

        </logger>
        <!-- 设置org.mybatis.spring包下的日志只打印WARN及以上级别的日志 -->
        <logger name="org.mybatis.spring" level="INFO" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
        <!-- 设置org.springframework包下的日志只打印WARN及以上级别的日志 -->
        <logger name="org.springframework" level="INFO" additivity="false">
            <appender-ref ref="Console"/>
        </logger>
        <!-- 设置com.qfx.workflow.service包下的日志只打印WARN及以上级别的日志 -->
        <logger name="com.qfx.workflow.service" level="INFO" additivity="false">
            <appender-ref ref="Console"/>
        </logger>

        <logger name="org.apache.ibatis" level="DEBUG"/>
        <!-- 设置打印sql语句配置结束 -->

        <!--建立一个默认的root的logger-->
        <root level="${output_log_level}">
            <appender-ref ref="RollingFile"/>
            <appender-ref ref="Console"/>
            <appender-ref ref="InfoFile"/>
            <appender-ref ref="WarnFile"/>
            <appender-ref ref="ErrorFile"/>
            <appender-ref ref="DebugFile"/>
            <appender-ref ref="ApiInfoFile"/>
        </root>
    </loggers>

</configuration>

将log4j2.xml文件放到项目 resources 目录下

3.application.properties配置

#设置log4j2.xml的路径
logging.config=classpath:log4j2.xml

4.测试效果

public class test {
    private static final Logger log = LogManager.getLogger(test.class);
    private static final Level apiInfo = Level.valueOf("apiInfo"); //自定义日志级别
    @Test
    public void log4jTest(){
        log.info("info");
        log.error("error");
        log.warn("warning");
        log.log(apiInfo,"apiInfo");
    }
}

 效果展示   可以设置每个日志等级输出到控制台颜色

springbootlog4j springbootlog4j2配置 timebased_log4j

二.AOP处理接口日志配置

1.引入AOP的pom依赖

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
        </dependency>

2.新建ApiLog.class  便于controller内使用注解,灵活存储日志

import java.lang.annotation.*;

/**
 * 打印方法入参和出参的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ApiLog {
    /**
     * 日志描述信息
     *
     * @return
     */
    String description() default "";
}

3.新建 ApiLogAspect  切面文件  这里只使用了环绕通知,也可以 前置通知和后置通知配合使用

1.定义注解切点

/**
     * 以自定义 @WebLog 注解为切点
     */
    @Pointcut("@annotation(ApiLog)")
    public void apiLog() {
    }

2.环绕通知相关代码 (ResponseVo为统一返回格式,可根据项目自定义)

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class ResponseVo{
    Integer code;
    String message;
    Object data;
    boolean state;
}
/**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("apiLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        long startTime = System.currentTimeMillis();
//         开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取 @WebLog 注解的描述信息
        String methodDescription = getAspectLogDescription(proceedingJoinPoint);
        // 请求参数解析
        Map<String, Object> paramMap = new HashMap<String, Object>();
        Object[] objs = proceedingJoinPoint.getArgs();
        String[] argNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames(); // 参数名
        for (int i = 0; i < objs.length; i++) {
            if (!(objs[i] instanceof ExtendedServletRequestDataBinder) && !(objs[i] instanceof HttpServletResponseWrapper)) {
                paramMap.put(argNames[i], objs[i]);
            }
        }


        try {
            StringBuffer logString = new StringBuffer();
            // 打印请求相关参数
            logString.append(LINE_SEPARATOR + "=================== Start ===================" + LINE_SEPARATOR);
            logString.append("请求地址: " + request.getRequestURL().toString() + LINE_SEPARATOR);
            logString.append("请求名称: " + methodDescription + LINE_SEPARATOR);
            logString.append("请求方式: " + request.getMethod() + LINE_SEPARATOR);
            logString.append("方法路径: " + proceedingJoinPoint.getSignature().getDeclaringTypeName() + proceedingJoinPoint.getSignature().getName() + LINE_SEPARATOR);
            logString.append("请求来源: " + request.getRemoteAddr() + LINE_SEPARATOR);
            logString.append("请求参数: " + JSONObject.toJSONString(paramMap) + LINE_SEPARATOR);
            Object result = proceedingJoinPoint.proceed();
            logString.append("返回参数: " + JSON.toJSONString(new ResponseVo(200, "执行成功", result, true)) + LINE_SEPARATOR);
            long endTime = System.currentTimeMillis();
            long ms = endTime - startTime;
            logString.append("执行耗时: " + ms + "ms" + LINE_SEPARATOR);
            logString.append("=================== End ===================" + LINE_SEPARATOR + LINE_SEPARATOR);
            log.log(apiInfo, logString);
            return new ResponseVo(200, "执行成功", result, true);

        } catch (Exception e) {
            int errReturnCode = 201;  //异常编码
            String errReturnMsg = e.getMessage();  //异常信息
            if (e instanceof BusinessErrorException) {  //自定义异常
                BusinessErrorException errmsg = (BusinessErrorException) e;
                errReturnCode = errmsg.getCode();
                errReturnMsg = errmsg.getMessage();
            } 


            StringWriter stringWriter = new StringWriter();
            PrintWriter writer = new PrintWriter(stringWriter);
            e.printStackTrace(writer);
            StringBuffer buffer = stringWriter.getBuffer();  //异常详情  buffer.toString()




            StringBuffer logString = new StringBuffer();
            StringBuffer errorlogString = new StringBuffer();

            logString.append(LINE_SEPARATOR + "=================== Start ===================" + LINE_SEPARATOR);
            logString.append("请求地址: " + request.getRequestURL().toString() + LINE_SEPARATOR);
            logString.append("请求名称: " + methodDescription + LINE_SEPARATOR);
            logString.append("请求方式: " + request.getMethod() + LINE_SEPARATOR);
            logString.append("方法路径: " + proceedingJoinPoint.getSignature().getDeclaringTypeName() + proceedingJoinPoint.getSignature().getName() + LINE_SEPARATOR);
            logString.append("请求来源: " + request.getRemoteAddr() + LINE_SEPARATOR);
            logString.append("请求参数: " + JSONObject.toJSONString(paramMap) + LINE_SEPARATOR);
            logString.append("返回参数: " + JSONObject.toJSONString(new ResponseVo(errReturnCode, errReturnMsg, null, false)) + LINE_SEPARATOR);
            logString.append("异常信息: " + buffer.toString() + LINE_SEPARATOR);
            long endTime = System.currentTimeMillis();
            long ms = endTime - startTime;
            logString.append("执行耗时: " + ms + "ms" + LINE_SEPARATOR);
            logString.append("=================== End ===================" + LINE_SEPARATOR + LINE_SEPARATOR);
            log.log(apiInfo, logString);


            errorlogString.append(LINE_SEPARATOR + "=================== 出现异常 ===================" + LINE_SEPARATOR);
            String uuid = UUID.randomUUID().toString();
            errorlogString.append("请求地址: " + request.getRequestURL().toString() + LINE_SEPARATOR);
            errorlogString.append("请求名称: " + methodDescription + LINE_SEPARATOR);
            errorlogString.append("请求方式: " + request.getMethod() + LINE_SEPARATOR);
            errorlogString.append("方法路径: " + proceedingJoinPoint.getSignature().getDeclaringTypeName() + proceedingJoinPoint.getSignature().getName() + LINE_SEPARATOR);
            errorlogString.append("请求来源: " + request.getRemoteAddr() + LINE_SEPARATOR);
            errorlogString.append("请求参数: " + JSONObject.toJSONString(paramMap) + LINE_SEPARATOR);
            errorlogString.append("返回参数: " + JSONObject.toJSONString(new ResponseVo(errReturnCode, errReturnMsg, null, false)) + LINE_SEPARATOR);
            errorlogString.append("出现异常: " + e.getMessage() + LINE_SEPARATOR);
            errorlogString.append("异常信息: " + buffer.toString() + LINE_SEPARATOR);
            errorlogString.append("=================== 当前异常结束 ===================" + LINE_SEPARATOR + LINE_SEPARATOR);
            log.error(errorlogString);


            throw e;
        }


    }

完整代码

package com.dzfp_new.config;

import cn.dev33.satoken.exception.NotLoginException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dzfp_new.config.returnConfig.BusinessErrorException;
import com.dzfp_new.config.returnConfig.BusinessMsgEnum;
import com.dzfp_new.config.returnConfig.ResponseVo;
import com.dzfp_new.dao.loginfo.logoinfoMapper;
import com.dzfp_new.entity.loginfo;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@Aspect
@Component
public class ApiLogAspect {
    /**
     * 换行符
     */
    private static final String LINE_SEPARATOR = System.lineSeparator();
    private static final Logger log = LogManager.getLogger(ApiLogAspect.class);
    private static final Level apiInfo = Level.valueOf("apiInfo");  //自定义异常



    /**
     * 以自定义 @WebLog 注解为切点
     */
    @Pointcut("@annotation(ApiLog)")
    public void apiLog() {
    }


    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("apiLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        long startTime = System.currentTimeMillis();
//         开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取 @WebLog 注解的描述信息
        String methodDescription = getAspectLogDescription(proceedingJoinPoint);
        // 请求参数解析
        Map<String, Object> paramMap = new HashMap<String, Object>();
        Object[] objs = proceedingJoinPoint.getArgs();
        String[] argNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames(); // 参数名
        for (int i = 0; i < objs.length; i++) {
            if (!(objs[i] instanceof ExtendedServletRequestDataBinder) && !(objs[i] instanceof HttpServletResponseWrapper)) {
                paramMap.put(argNames[i], objs[i]);
            }
        }

//为避免高并发日志输出错乱,将接口日志统计使用 StringBuffer 组合完成后再进行输出
        try {
            StringBuffer logString = new StringBuffer();
            // 打印请求相关参数
            logString.append(LINE_SEPARATOR + "=================== Start ===================" + LINE_SEPARATOR);
            logString.append("请求地址: " + request.getRequestURL().toString() + LINE_SEPARATOR);
            logString.append("请求名称: " + methodDescription + LINE_SEPARATOR);
            logString.append("请求方式: " + request.getMethod() + LINE_SEPARATOR);
            logString.append("方法路径: " + proceedingJoinPoint.getSignature().getDeclaringTypeName() + proceedingJoinPoint.getSignature().getName() + LINE_SEPARATOR);
            logString.append("请求来源: " + request.getRemoteAddr() + LINE_SEPARATOR);
            logString.append("请求参数: " + JSONObject.toJSONString(paramMap) + LINE_SEPARATOR);
            Object result = proceedingJoinPoint.proceed();
            logString.append("返回参数: " + JSON.toJSONString(new ResponseVo(200, "执行成功", result, true)) + LINE_SEPARATOR);
            long endTime = System.currentTimeMillis();
            long ms = endTime - startTime;
            logString.append("执行耗时: " + ms + "ms" + LINE_SEPARATOR);
            logString.append("=================== End ===================" + LINE_SEPARATOR + LINE_SEPARATOR);
            log.log(apiInfo, logString);

            return new ResponseVo(200, "执行成功", result, true);

        } catch (Exception e) {
            int errReturnCode = 201;  //异常编码
            String errReturnMsg = e.getMessage();  //异常信息
            if (e instanceof BusinessErrorException) {
                BusinessErrorException errmsg = (BusinessErrorException) e;
                errReturnCode = errmsg.getCode();
                errReturnMsg = errmsg.getMessage();
            }


            StringWriter stringWriter = new StringWriter();
            PrintWriter writer = new PrintWriter(stringWriter);
            e.printStackTrace(writer);
            StringBuffer buffer = stringWriter.getBuffer();  //异常详情  buffer.toString()




            StringBuffer logString = new StringBuffer();
            StringBuffer errorlogString = new StringBuffer();

            logString.append(LINE_SEPARATOR + "=================== Start ===================" + LINE_SEPARATOR);
            logString.append("请求地址: " + request.getRequestURL().toString() + LINE_SEPARATOR);
            logString.append("请求名称: " + methodDescription + LINE_SEPARATOR);
            logString.append("请求方式: " + request.getMethod() + LINE_SEPARATOR);
            logString.append("方法路径: " + proceedingJoinPoint.getSignature().getDeclaringTypeName() + proceedingJoinPoint.getSignature().getName() + LINE_SEPARATOR);
            logString.append("请求来源: " + request.getRemoteAddr() + LINE_SEPARATOR);
            logString.append("请求参数: " + JSONObject.toJSONString(paramMap) + LINE_SEPARATOR);
            logString.append("返回参数: " + JSONObject.toJSONString(new ResponseVo(errReturnCode, errReturnMsg, null, false)) + LINE_SEPARATOR);
            logString.append("异常信息: " + buffer.toString() + LINE_SEPARATOR);
            long endTime = System.currentTimeMillis();
            long ms = endTime - startTime;
            logString.append("执行耗时: " + ms + "ms" + LINE_SEPARATOR);
            logString.append("=================== End ===================" + LINE_SEPARATOR + LINE_SEPARATOR);
            log.log(apiInfo, logString);


            errorlogString.append(LINE_SEPARATOR + "=================== 出现异常 ===================" + LINE_SEPARATOR);
            String uuid = UUID.randomUUID().toString();
            errorlogString.append("请求地址: " + request.getRequestURL().toString() + LINE_SEPARATOR);
            errorlogString.append("请求名称: " + methodDescription + LINE_SEPARATOR);
            errorlogString.append("请求方式: " + request.getMethod() + LINE_SEPARATOR);
            errorlogString.append("方法路径: " + proceedingJoinPoint.getSignature().getDeclaringTypeName() + proceedingJoinPoint.getSignature().getName() + LINE_SEPARATOR);
            errorlogString.append("请求来源: " + request.getRemoteAddr() + LINE_SEPARATOR);
            errorlogString.append("请求参数: " + JSONObject.toJSONString(paramMap) + LINE_SEPARATOR);
            errorlogString.append("返回参数: " + JSONObject.toJSONString(new ResponseVo(errReturnCode, errReturnMsg, null, false)) + LINE_SEPARATOR);
            errorlogString.append("出现异常: " + e.getMessage() + LINE_SEPARATOR);
            errorlogString.append("异常信息: " + buffer.toString() + LINE_SEPARATOR);
            errorlogString.append("=================== 当前异常结束 ===================" + LINE_SEPARATOR + LINE_SEPARATOR);
            log.error(errorlogString);



            throw e;
        }


    }


    /**
     * 获取切面注解的描述-正确方式
     *
     * @param joinPoint 切点
     * @return 描述信息
     * @throws Exception
     */
    public String getAspectLogDescription(JoinPoint joinPoint)
            throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = null;
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        methodSignature = (MethodSignature) signature;
        Method currentMethod = joinPoint.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
        return currentMethod.getAnnotation(ApiLog.class).description();
    }
}

测试接口日志效果

@RestController
@RequestMapping("test")
public class testController {
    @Autowired
    Util util;
    @Autowired
    enterpriseService es;

    @ApiLog(description = "log4j2接口测试") //使用注解@ApiLog代表该接口需进行日志存储
    @RequestMapping("test")
    public String test(String name){
        return "log4j2测试返回结果";
    }

 输出内容

springbootlog4j springbootlog4j2配置 timebased_log4j_02

 配置的日志文件内容

springbootlog4j springbootlog4j2配置 timebased_log4j_03