一.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");
}
}
效果展示 可以设置每个日志等级输出到控制台颜色
二.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测试返回结果";
}
输出内容
配置的日志文件内容