日志框架
- 日志框架由日志门面+日志适配器+日志实现框架组成
- 市面上面的一些日志框架组合如下:slf4j+log4j;slf4j+logback;slf4j+log4j2
- log4j是一款开源的日志框架
- log4j2是log4j的升级版,参考了logback的一些优秀的设计,log4j是Apache的一款开源的日志框架,通过log4j,我们可以给日志输出到控制台,文件等地方;我们可以控制每一条日志的输出格式,通过定义日志的每一条输出级别,细致的控制日志的生成过程;log4j由三个重要的部分组成,日志记录器logger,输出端appender,日志格式化器layout三个重要部分组成
- logger:启用或者禁用哪些日志,以什么日志级别输出
- appender:控制日志输出到控制台还是文件里面
- layout:控制日志以什么格式输出
- 日志的级别:TRACE,DEBUG,INFO,WARN,ERROR,FATAL
- 输出的规则:级别高于配置的规则,日志才会输出
- logback由三个部分组成,logback-core,logback-classic,logback-access三个部分组成
前言
springboot内部使用Commons Logging作为内部日志的实现,但是它也支持各种日志实现,如slf4j+logback和slf4j+log4j2,默认情况下,spingboot使用slf4j+logback进行日志记录。
logback配置
详细的配置文件
- 在yaml里面配置日志的级别,来控制日志的输出,这里是INFO,它的级别高于logback.xml里面的level级别的控制
logging:
level:
root: INFO
config: classpath:config/logback.xml
- 不同环境的配置文件可以使用spring.profiles.active=sit的形式来指定加载不同的配置文件同时在日志文件中输出日志
- 对应logback里面的配置
- 详细的logback配置如下
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--property-->
<!--项目名称-->
<property name="appName" value="test"/>
<!--日志路径-->
<property name="rootPath" value="/logs/"/>
<!--日志格式-->
<property name="logPattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%p][%thread][TRACE_ID:%X{traceId}][%logger{39}#%M %L]LINECONTENT:%m%n"/>
<!--debug和warn日志最大保存30天-->
<property name="log.maxHistory" value="30"/>
<!--info日志最大保留7天-->
<property name="log.info.maxHistory" value="7"/>
<!--存档文件的总大小10GB-->
<property name="log.totalSizeCap" value="10GB"/>
<!--单个文件的大小20MB-->
<property name="log.maxFileSize" value="20MB"/>
<!--重启清理日志文件,否-->
<property name="log.cleanHistoryOnStart" value="false"/>
<!--Appender-->
<!--输出到控制台-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
</appender>
<!--输出到DEBUG文件-->
<appender name="debug_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${rootPath}/${appName}-debug.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${rootPath}/${appName}-debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${log.maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<cleanHistoryOnStart>${log.cleanHistoryOnStart}</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--输出到INFO文件-->
<appender name="info_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${rootPath}/${appName}-info.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${rootPath}/${appName}-info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>${log.info.maxHistory}</maxHistory>
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${log.maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<cleanHistoryOnStart>${log.cleanHistoryOnStart}</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--输出到WARN文件-->
<appender name="warn_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${rootPath}/${appName}-warn.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${rootPath}/${appName}-warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxHistory>${log.maxHistory}</maxHistory>
<totalSizeCap>${log.totalSizeCap}</totalSizeCap>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${log.maxFileSize}</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<cleanHistoryOnStart>${log.cleanHistoryOnStart}</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>${logPattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>warn</level>
</filter>
</appender>
<!-- 异步输出 -->
<appender name ="debug_async" class= "ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold >0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref ="debug_file"/>
</appender>
<!-- 异步输出 -->
<appender name ="info_async" class= "ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold >0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref ="info_file"/>
</appender>
<springProfile name="prod">
<root level="info">
<appender-ref ref="debug_async"/>
<appender-ref ref="info_async"/>
<appender-ref ref="warn_file"/>
</root>
</springProfile>
<springProfile name="sit,uat">
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug_file"/>
<appender-ref ref="info_file"/>
<appender-ref ref="warn_file"/>
</root>
</springProfile>
<!--默认的包控制-->
<logger name="org.mybatis" value="debug"/>
</configuration>
- 创建的文件文件如下&打印的日志格式
- 分布式链路ID的实现采用slf4j里面的MDC模式来实现,对应在logback.xml里面的文件位置
- MDC的实现方式如下,详细代码
package org.example.filter;
import cn.hutool.core.util.IdUtil;
import org.slf4j.MDC;
public class MDCUtils {
/**
* 追踪ID的名称
*/
public final static String TRACE_ID = "traceId";
/**
* 赋值MDC
*/
public static void addTraceId(){
String traceId = createTraceId();
MDC.put(TRACE_ID,traceId);
}
/**
* MDC设置traceId
*/
public static void putTraceId(String traceId){
MDC.put(TRACE_ID,traceId);
}
/**
* 获取MDC
*/
public static String getTraceId(){
return MDC.get(TRACE_ID);
}
/**
* 移除MDC
*/
public static void removeTraceId(){
MDC.remove(TRACE_ID);
}
/**
* 创建traceId
*/
public static String createTraceId(){
return IdUtil.getSnowflake().nextIdStr();
}
}
package org.example.filter;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class WebTraceFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
try {
String traceId = request.getHeader(MDCUtils.TRACE_ID);
if (StrUtil.isBlank(traceId)){
MDCUtils.addTraceId();
}else{
MDCUtils.putTraceId(traceId);
}
filterChain.doFilter(request,response);
}catch (Exception e){
log.error("链路ID设置出错",e);
} finally {
MDCUtils.removeTraceId();
}
}
}
- MDC的方式一些好的监控工具例如zipkin,skywalking里面也有一些好的实现,配置如下截图
- logback配置的pom文件如下
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.6.13</version>
</parent>
<dependencies>
<!-- Spring MVC依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springBoot的Test依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
- 我们在实际项目开发中,打印日志是一个比较重要的事情,在打印日志的时候,要善于利用日志的开关来控制日志的输出,这样有利于日志的输出控制和问题的排查。
log4j2
- pom依赖,需要排除spring-boot-starter-logging和引入spring-boot-starter-log4j2
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.6.13</version>
</parent>
<dependencies>
<!-- Spring MVC依赖 -->
<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>
</dependency>
<!-- springBoot的Test依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
- 在resource目录下面创建log4j2.xml配置文件
- log4j2.xml配置文件的详细内容
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<!-- %d{HH:mm:ss.SSS} 毫秒的时间
%p代表输出该条日志的等级
%t 当前线程名称
%-5level 日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
%c{1} 类名
%L 输出行号
%msg 日志文本
%n 换行
其他常用的占位符有:
%F 输出所在的类文件名,如Client.java
%M 输出所在方法名
%m是输出代码指定的日志信息
%l 输出语句所在的行数, 包括类名、方法名、文件名、行数 (这个比较强大) -->
<configuration status="WARN" monitorInterval="30">
<!--先定义所有的appender-->
<appenders>
<!--这个输出控制台的配置-->
<console name="Console" target="SYSTEM_OUT">
<!--输出日志的格式-->
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] [%t] - %l - %m%n"/>
</console>
<!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
<File name="log" fileName="E:/log/test.log" append="false">
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] [%t] - %l - %m%n"/>
</File>
<!--INFO日志文件输出 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingFile name="RollingFileInfo" fileName="E:/log/info.log"
filePattern="${sys:user.home}/log/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
<!-- Warn日志文件输出 -->
<RollingFile name="RollingFileWarn" fileName="E:/log/warn.log"
filePattern="${sys:user.home}/log/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
<DefaultRolloverStrategy max="20"/>
</RollingFile>
<!-- Error日志文件输出 -->
<!--<RollingFile name="RollingFileError" fileName="E:/log/error.log"-->
<!--filePattern="${sys:user.home}/log/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">-->
<!--<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>-->
<!--<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/>-->
<!--<Policies>-->
<!--<TimeBasedTriggeringPolicy/>-->
<!--<SizeBasedTriggeringPolicy size="100 MB"/>-->
<!--</Policies>-->
<!--</RollingFile>-->
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!--节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等-->
<!--<logger name="org.springframework" level="INFO"></logger>-->
<!--<logger name="org.mybatis" level="INFO"></logger>-->
<root level="INFO">
<appender-ref ref="Console"/>
<appender-ref ref="log"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<!--<appender-ref ref="RollingFileError"/>-->
</root>
</loggers>
</configuration>