文章目录
日志框架简述
市面上的日志框架: JUL、JCL(Apache 基金会的)、Jboss-logging、logback、log4j、log4j2、slf4j....
日志门面(抽象层) | 日志实现 |
---|---|
JCL(Jakarta Commons Logging-属于Apache基金会)、jboss-logging | JUL(java.util.logging-Java JDK自带的) |
SLF4j(Simple Logging Facade for Java) | Log4j 、 Log4j2 、Logback |
SLF4j
、 Log4j
、 Logback
出自同一人之手。
Log4j2
是另外一个人开发的,并不是 Log4j
的升级版,只是借用了 Log4j
的名号而已,Logback
才是 Log4j
的升级版。
Log4j2
相比Log4j
和Logback
有很大的性能提升。
Log4j2
能够避免死锁。
日志分级
日志级别从低到高分为:TRACE < DEBUG < INFO < WARN < ERROR
如果设置为WARN
,则低于WARN
的信息都不会输出。
SLF4J 使用
SLF4J Github 开源地址:https://github.com/qos-ch/slf4j
SLF4J 使用文档地址:https://www.slf4j.org/manual.html
开发的时候,应用中使用日志系统时,为了将来的扩展,日志记录方法的调用,不应该直接调用日志的实现类,而是调用日志抽象层里面的方法;这样以后即使更换了实现类也没有关系。
使用方式一:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
private static final Logger log = LoggerFactory.getLogger(HelloWorld.class);
public static void main(String[] args) {
log.info("Hello World");
}
}
使用方式二:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HelloWolrd {
public static void main(String[] args) {
log.info("this is info log...");
}
}
SpringBoot 日志使用(Logback)
SpringBoot 默认使用 SLF4j
和 Logback
记录日志。
Spring Boot 指定 Logback 日志配置文件:
1、logback.xml: 应用启动时会直接被日志框架 logback 识别而被使用,不经过Spring Boot,所以使用 logback.xml 作为日志配置文件,不能使用Spring Boot 的 Profile 高级功能。
2、logback-spring.xml: logback 日志框架就不能直接识别到,而会由 Spring Boot 解析此日志配置文件,从而可以使用Spring Boot 的 Profile 高级功能。
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<!-- 这里定义日志的根目录 -->
<property name="log.path" value="./" />
<property name="info.fileName" value="info" />
<property name="debug.fileName" value="debug" />
<property name="warn.fileName" value="warn" />
<property name="error.fileName" value="error" />
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 ALL -->
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/${info.fileName}.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
滚动时产生日志的归档路径及文件名
%d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
-->
<fileNamePattern>${log.path}/${info.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>90</maxHistory>
</rollingPolicy>
</appender>
<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/${debug.fileName}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/debug/${debug.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<!--用于配置符合过滤条件的操作,ACCEPT表示日志会被立即处理,不再经过剩余过滤器-->
<onMatch>ACCEPT</onMatch>
<!--用于配置不符合过滤条件的操作,DENY表示日志将立即被抛弃不再经过其他过滤器-->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/${warn.fileName}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/${warn.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/${error.fileName}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/${error.fileName}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="REQUEST" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/request.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/request-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>90</maxHistory>
</rollingPolicy>
</appender>
<!--
用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>;
通过getLogger("name/class")获取到这个logger,输出日志的时候,就会使用这个logger配置的appender输出,同时还会使用rootLogger配置的appender;
可以使用logger节点的additivity="false"属性来屏蔽rootLogger的appender,这样就可以不使用rootLogger的appender输出日志了;
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<logger name="com.example.springbootprofile.controller" level="info">
<appender-ref ref="REQUEST"/>
</logger>
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
-->
<logger name="com.example.mapper" level="debug"/>
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_ALL"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<!--开发环境:打印控制台-->
<springProfile name="dev">
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!--生产环境:输出到文件-->
<springProfile name="prd">
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_ALL"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</springProfile>
</configuration>
logback 日志配置文件案例
logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR,如果设置为INFO,则低于INFO的信息都不会输出 -->
<configuration>
<!-- 上下文名称,默认上下文名称为“default”,可以通过%contextName来打印日志上下文名称 -->
<contextName>boot</contextName>
<!-- 读取SpringBoot配置文件属性值,日志文件必须为:logback-spring.xml 才生效 -->
<springProperty scope="context" name="port" source="server.port" defaultValue="localhost"/>
<!-- 日志存放路径 -->
<property scope="context" name="log.path" value="logs"/>
<!-- 日志输出格式 -->
<property scope="context" name="log.pattern"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %X{traceId} [%thread] %logger{36}:[%L] - %msg%n"/>
<!-- 输出到控制台 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%cyan(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight(%-5level) %X{traceId} %green([%thread]) %boldMagenta(%logger{36}:[%L]) - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 输出到文件 -->
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/info.log</file>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录,会自动对日志进行拆分 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!--超过500MB就拆分日志-->
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>90</maxHistory>
</rollingPolicy>
</appender>
<!-- 输出到文件 -->
<appender name="FILE_REQUEST" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/request.log</file>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/request-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>90</maxHistory>
</rollingPolicy>
</appender>
<!-- 输出到文件,只打印ERROR日志 -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>500MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>90</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<springProfile name="local">
<!-- 设置包下的日志打印级别 -->
<logger name="com.example.boot3.mapper" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!-- 指定开发环境日志 -->
<springProfile name="dev">
<!-- 设置包下的日志打印级别为 debug -->
<logger name="com.example.boot3.mapper" level="DEBUG"/>
<!-- 设置包下的日志输出到指定 appender,日志打印级别为 info -->
<logger name="com.example.boot3.controller" level="INFO">
<appender-ref ref="FILE_REQUEST"/>
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_ALL"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</springProfile>
<!-- 指定生产环境日志输 -->
<springProfile name="prod">
<!-- 设置包下的日志输出到指定 appender,日志打印级别为 info -->
<logger name="com.example.boot3.controller">
<appender-ref ref="FILE_REQUEST"/>
</logger>
<root level="INFO">
<appender-ref ref="FILE_ALL"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</springProfile>
</configuration>
logback 配置文件解析
一、configuration 根节点
属性:
// scan : 配置文件如果发生改变,将会重新加载,默认值为true;
// scanPeriod : 检测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位时毫秒,当scan为true时,这个属性生效,默认时间间隔为1min。
// debug : 默认为false ,设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。
<configuration scan="true" scanPeriod="2" debug="true">
<!--TODO : 子节点信息-->
</configuration>
基本结构:
多个 appender 元素,零个或多个 logger 元素,最多一个 root 元素。
二、appender 子节点
负责写日志的组件。
基本结构:
属性:
name:appender 名称;
class:指定 appender 的全限定名;
<?xml version="1.0" encoding="utf-8"?>
<configuration debug="true" scan="true" scanPeriod="2">
<!-- conf consoel out -->
<appender name="console_out" class="ch.qos.logback.core.ConsoleAppender">
</appender>
<!-- conf file out -->
<appender name="file_out" class="ch.qos.logback.core.FileAppender">
</appender>
<!-- conf file out -->
<appender name="file_out" class="ch.qos.logback.core.RollingFileAppender">
</appender>
<root></root>
<loger></loger>
</configuration>
三、ConsoleAppender 日志输出到控制台
以下节点:
encoder : 对日志进行格式化。
target : 字符串System.out 或者 System.err, 默认 System.out;
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- conf consoel out -->
<appender name="console_out" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console_out" />
</root>
</configuration>
四、FileAppender 日志添加到文件
有如下节点:
file : 被写入的文件名,可以是相对目录 , 也可以是绝对目录 , 如果目录不存在则会自动创建
append : 如果是true , 日志被追加到文件结尾 , 如果是false,清空现存文件 , 默认是true
encoder : 对日志进行格式化 [具体的转换符说明请参见官网.]
prodent : 如果是true,日志会被安全的写入文件 , 即使其他的FileAppender也会向此文件做写入操作 , 默认是false
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- conf file out -->
<appender name="file_out" class="ch.qos.logback.core.FileAppender">
<file>logs/debug.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
</configuration>
SpringBoot 集成 Log4j2
Springboot整合log4j2
Maven依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除springboot自带的log依赖 -->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加springboot的log4j2依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
application.properties配置:
logging.config=classpath:log4j2-spring.xml
log4j2.xml
日志配置:
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="600">
<!-- 优先级从高到低分别是 OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL -->
<!-- 单词解释: Match:匹配 DENY:拒绝 Mismatch:不匹配 ACCEPT:接受 -->
<!-- DENY,日志将立即被抛弃不再经过其他过滤器; NEUTRAL,有序列表里的下个过滤器过接着处理日志; ACCEPT,日志会被立即处理,不再经过剩余过滤器。 -->
<!--输出日志的格式
%d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间
%p : 日志输出格式
%c : logger的名称
%m : 日志内容,即 logger.info("message")
%n : 换行符
%C : Java类名
%L : 日志输出所在行数
%M : 日志输出所在方法名
hostName : 本地机器名
hostAddress : 本地ip地址 -->
<!--变量配置-->
<Properties>
<!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%L - %msg%n" />
<!-- 定义日志存储的路径,不要配置相对路径 -->
<property name="FILE_PATH" value="E:\logs" />
<!--项目名称-->
<property name="FILE_NAME" value="SpringBootDemo" />
</Properties>
<appenders>
<!--输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
<!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
</console>
<!-- 输出控制台的配置,这里输出error级别的信息到System.err,在控制台上看到的是红色文字 -->
<Console name="console_err_appender" target="SYSTEM_ERR">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY" />
</Console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
<File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
<PatternLayout pattern="${LOG_PATTERN}"/>
</File>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
<!--这个log专门为一个类设置的,因为这个类的日志太多,滚的太快,影响其他日志查看-->
<RollingFile name="RollingFileRequest" fileName="${FILE_PATH}/${FILE_NAME}/request.log" filePattern="${FILE_PATH}/${FILE_NAME}-REQUEST-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!--interval属性用来指定多久滚动一次,默认是1 hour-->
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
<DefaultRolloverStrategy max="15"/>
</RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Console"/>
</logger>
<!--监控系统信息-->
<!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<!-- <logger name="com.example.demo.controller" level="INFO" additivity="false">-->
<!-- <appender-ref ref="RollingFileRequest"/>-->
<!-- </logger>-->
<root level="info">
<appender-ref ref="Console"/>
<appender-ref ref="Filelog"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
<appender-ref ref="console_err_appender"/>
</root>
</loggers>
</configuration>
测试类:
@RestController
@Slf4j
public class TestController {
@RequestMapping(value = "test")
public String test(){
log.info("info级别");
log.debug("debug级别");
log.warn("warn级别");
log.error("error级别");
return "HELLO-BUG";
}
}
案例:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<Properties>
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36}:%L - %msg%n"/>
<property name="FILE_PATH" value="F:\\"/>
<property name="FILE_NAME" value="SpringBootDemo"/>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<Console name="Console_debug" target="SYSTEM_OUT">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<RollingFile name="RollingFile" fileName="${FILE_PATH}/info.log"
filePattern="${FILE_PATH}/info.log.%d{yyyy-MM-dd}">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS}[%5p][%t][%c{1}:%L%X{logMark}]:%m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</appenders>
<loggers>
<logger name="com.example.mapper" level="debug" additivity="false">
<appender-ref ref="Console_debug"/>
</logger>
<root level="all">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFile"/>
</root>
</loggers>
</configuration>
使用 MDC 实现全链路调用日志跟踪
日志拦截器
import com.evcas.charge.pile.platform.common.annotation.LogNoTrace;
import com.evcas.charge.pile.platform.common.utils.StringUtils;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.UUID;
public class LogInterceptor implements HandlerInterceptor {
private static final String TRACE_ID = "traceId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) {
return true;
}
Method method = ((HandlerMethod) handler).getMethod();
if (method.isAnnotationPresent(LogNoTrace.class)) {
return true;
}
String tid = UUID.randomUUID().toString().replace("-", "").substring(0, 10);
// 如果有上层调用就用上层的ID
if (!StringUtils.isEmpty(request.getHeader(TRACE_ID))) {
tid = request.getHeader(TRACE_ID);
}
MDC.put(TRACE_ID, tid);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) {
MDC.remove(TRACE_ID);
}
}
import java.lang.annotation.*;
/**
* 不需要日志跟踪
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogNoTrace {
}
import com.gotion.opencenter.interceptor.LogInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {
@Bean
public LogInterceptor logInterceptor() {
return new LogInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor());
}
}
修改日志格式
<property name="pattern">%d{HH:mm:ss.SSS} %X{traceId} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
重点是 %X{traceId}
traceId 和 MDC 中的键名称一致。
trackId 丢失解决
异步子线程会丢失了trackId:
思路: 将父线程的trackId传递下去给子线程即可。
1、自定义线程池,交给 Spring 管理
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;
@Configuration
@EnableAsync
public class MyThreadPoolConfig {
@Bean("MyExecutor")
public Executor asyncExecutor() {
MyThreadPoolTaskExecutor executor = new MyThreadPoolTaskExecutor();
//核心线程数5:线程池创建时候初始化的线程数
executor.setCorePoolSize(5);
//最大线程数5:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(5);
//缓冲队列500:用来缓冲执行任务的队列
executor.setQueueCapacity(500);
//允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
//线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
executor.setThreadNamePrefix("myExecutor");
executor.initialize();
return executor;
}
}
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
public MyThreadPoolTaskExecutor() {
super();
}
@Override
public void execute(Runnable task) {
super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
}
}
import org.slf4j.MDC;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
public class ThreadMdcUtil {
private static final String TRACE_ID = "traceId";
// 获取唯一性标识
public static String generateTraceId() {
return UUID.randomUUID().toString();
}
public static void setTraceIdIfAbsent() {
if (MDC.get(TRACE_ID) == null) {
MDC.put(TRACE_ID, generateTraceId());
}
}
/**
* 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
*
* @param callable
* @param context
* @param <T>
* @return
*/
public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
return callable.call();
} finally {
MDC.clear();
}
};
}
/**
* 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
*
* @param runnable
* @param context
* @return
*/
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
return () -> {
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
setTraceIdIfAbsent();
try {
runnable.run();
} finally {
MDC.clear();
}
};
}
}