Logback日志框架
1、Logback入门案例
1、Logback简介
官方网站:https://logback.qos.ch/index.html、Maven 仓库地址:https://search.maven.org/artifact/ch.qos.logback/logback-classic
Logback日志框架的优势:
- Logback 是 log4j 框架的作者开发的新一代日志框架(性能比Log4j要好),它效率更高、能够适应诸多的运行环境,同时天然支持SLF4J
- Logback的定制性更加灵活,同时也是SpringBoot的内置日志框架
Logback主要分为三个模块:
- logback-core:其它两个模块的基础模块,也是最核心的模块
- logback-classic:它是Log4j的一个改良版本,同时它完整实现了SLF4J API。使你可以方便的更换成其它日志框架(Log4j、JUL)
- logback-access:访问模块与Servlet容器集成提供通过HTTP来访问日志的功能
2、Logback示例
<!--使用slf4j日志门面,实际上logback默认就自带slf4j依赖,不引入当前依赖也可以-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!--logback的基础模块,实际上也可以不导入,因为logback-classic中也包含了基础模块包-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<!--实际可以只导入logback-classic包即可-->
<!--导入logback-classic依赖,实际上当前依赖已经包含了slf4j-api及logback-core,所以导入此依赖就够了-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
logger.error("error test");
logger.warn("warn test");
logger.info("info {}", "test");
logger.debug("debug test ");
logger.trace("trace test");
}
}
17:12:38.837 [main] ERROR LogbackTest - error test
17:12:38.840 [main] WARN LogbackTest - warn test
17:12:38.840 [main] INFO LogbackTest - info test
17:12:38.841 [main] DEBUG LogbackTest - debug test
源码分析:其实我们发现即使项目中没有引入slf4j我们这里也是用的slf4j门面进行编程,下面我们查看源码及依赖传递:
1、可以查看到Logback中的Logger继承了org.slf4j.Logger
package ch.qos.logback.classic;
public final class Logger implements org.slf4j.Logger, LocationAwareLogger, AppenderAttachable<ILoggingEvent>, Serializable {}
2、从logback的pom依赖中我们看到slf4-api及logback-core,依赖会进行传递
<!--只贴出了关键部分--><
parent>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-parent</artifactId>
<version>1.2.3</version>
</parent>
<artifactId>logback-classic</artifactId>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
2、组件介绍及配置
1、几大组件介绍
- Logger:日志的记录器,主要用于存放日志对象,也可以定义日志类型、级别
- Appender:输出源,用于指定日志输出的目的地(可以是控制台、文件、数据库 等等)
- Encoder:与Layout一样,用来格式化日志信息输出,把事件转换成字符串。注意:在logback 0.9.19 版之前没有 encoder
- Layout:在Logback中Layout对象被封装在Encoder中,Encoder等于Layout。所以一般使用的都是encoder
- Level:是用来定义日志级别的(默认是:DEBUG)
- Filter:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过
- LoggerContext:各个logger都被关联到一个LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各logger。其他所有logger也通过org.slf4j.LoggerFactory 类的静态方法getLogger取得。 getLogger方法以 logger名称为参数。用同一名字调用LoggerFactory.getLogger 方法所得到的永远都是同一个logger对象的引用
Appender :Logback 将写入日志事件的任务委托给一个名为 Appender 的组件。Appender 必须实现 ch.qos.logback.core.Appender 接口,Appender组件用来定义日志输出格式,日志如何过滤以及日志文件的处理。appender元素可以包含零个或一个layout元素,零个或多个encoder元素以及零个或多个filter元素。
Encoder:一个appender有一个encoder,负责将一个event事件转换成一组byte数组,并将转换后的字节数据输出到文件中。Encoder负责把事件转换为字节数组,并把字节数组写到合适的输出流。因此,encoder可以控制在什么时候、把什么样的字节数组写入到其拥有者维护的输出流中。Encoder接口有两个实现类,LayoutWrappingEncoder与PatternLayoutEncoder。注意:在logback 0.9.19 版之前没有 encoder。在之前的版本里,多数 appender 依靠 layout 来把事件转换成字符串并用java.io.Writer 把字符串输出。在之前的版本里,用户需要在FileAppender里嵌入一个 PatternLayout。
Level:Logback日志级别如下(默认级别:DEBUG):
// ERROR(错误信息) > WARN(警告信息) > INFO(重要信息) > DEBUG(普通信息) > TRACE(追踪信息) DEBUG为默认的打印级别
public static final Integer OFF_INTEGER = 2147483647; // OFF:最高等级的而级别,用于关闭所有的日志记录
public static final Integer ERROR_INTEGER = 40000; // ERROR:记录错误信息,不会影响系统运行。一般不想输出太多日志,则使用该级别即可
public static final Integer WARN_INTEGER = 30000; // WARN:记录警告信息
public static final Integer INFO_INTEGER = 20000; // INFO:记录运行信息
public static final Integer DEBUG_INTEGER = 10000; // DEBUG:(默认)记录调式信息,一般在开发中使用
public static final Integer TRACE_INTEGER = 5000; // TRACE:记录追踪信息,记录程序的流程信息,一般情况不会使用
public static final Integer ALL_INTEGER = -2147483648; // ALL:最低等级,用于打开所有级别的日志记录
LoggerContext:可以在代码中直接获取LoggerContext,通过LoggerContext可以动态修改Logback的配置
import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
// 根据包路径或类路径获取日志记录器,"ROOT" 表示根记录器: Logger getLogger(final String name);
loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.toLevel("error"));
// 停止logback-classic,也就是停止Logback
loggerContext.stop();
2、配置文件加载
Logback提供了三种种配置文件:logback.groovy、logback-test.xml、logback.xml(位于src/main/resources目录下)如果都不存在则采用默认的配置
- Logback首先会试着查找logback.groovy文件
- 当没有找到时,继续试着查找logback-test.xml文件
- 当没有找到时,继续试着查找logback.xml文件
- 如果仍然没有找到,则使用默认配置(打印到控制台)
- 其加载顺序为(优先级从高到低):logback.groovy =》 logback-test.xml =》 logback.xml
对XML日志文件的配置,大多数人第一次接触时有一种望而生畏的感觉,其实如果仔细分析,会发现核心的部分只有三个元素:appender、logger、root
┌── 日志输出格式
┌───appender───┼── 日志过滤
│ └── 日志文件处理
│ ┌── 获取哪些(包)的日志
├────logger────┤ ──┐
│ └── 日志的输出级别 ├ 日志组件(root,logger)
│ ┌── 根节点 ──┘
configuration──┼─────root─────┤
│ └── 只有level属性
│
│─────property──── 定义变量(非必须)
│
└───contextName─── 应用上下文名称(非必须)
3、XML配置模板
模板 1:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<!--
总体说明:根节点下有2个属性,三个节点
属性: contextName 上下文名称; property 设置变量
节点: appender, root, logger
-->
<configuration scan="true" scanPeriod="10 seconds">
<!--
contextName说明:
每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字,
用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。
-->
<contextName>logback-spring</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
<property name="logging.path" value="myLogs"/>
<!--0. 日志格式和颜色渲染 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr"
converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--1. 输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<!--日志文档输出格式-->
<encoder>
<!--指定日志格式-->
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!--设置字符集-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文档-->
<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名~~~~~file设置打印的文件的路径及文件名,建议绝对路径-->
<file>${logging.path}/web_debug.log</file>
<!--日志文档输出格式-->
<encoder>
<!--指定日志格式-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<!--
日志记录器的滚动策略
SizeAndTimeBasedRollingPolicy 按日期,大小记录日志
另外一种方式:
rollingPolicy的class设置为ch.qos.logback.core.rolling.TimeBasedRollingPolicy
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<!--
归档的日志文件的路径,例如今天是2018-08-23日志,当前写的日志文件路径为file节点指定,
可以将此文件与file指定文件路径设置为不同路径,从而将当前日志文件或归档日志文件置不同的目录。
而2018-08-23的日志文件在由fileNamePattern指定。%d{yyyy-MM-dd}指定日期格式,%i指定索引
-->
<fileNamePattern>${logging.path}/web-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--
配置日志文件不能超过100M,若超过100M,日志文件会以索引0开始,命名日志文件
例如error.20180823.0.txt
-->
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录debug级别的 -->
<!-- 过滤策略:
LevelFilter : 只打印level标签设置的日志级别
ThresholdFilter:打印大于等于level标签设置的级别,小的舍弃
-->
<!--<filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的日志级别 -->
<level>debug</level>
<!--匹配到就允许-->
<onMatch>ACCEPT</onMatch>
<!--没有匹配到就禁止-->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.2 level为 INFO 日志,时间滚动输出 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/web_info.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>${logging.path}/web-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.3 level为 WARN 日志,时间滚动输出 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/web_warn.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>${logging.path}/web-warn-%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>
<!-- 2.4 level为 ERROR 日志,时间滚动输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/web_error.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>${logging.path}/web-error-%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>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
<logger name="org.springframework.web" level="info"/>
<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
【logging.level.org.mybatis=debug logging.level.dao=debug】
-->
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<!-- 4. 最终的策略 -->
<!-- 4.1 开发环境:打印控制台-->
<!-- <springProfile name="dev">
<logger name="com.cic.analysis.business.dao" level="debug"/><!– 修改此处扫描包名 –>
</springProfile>-->
<!--
root指定最基础的日志输出级别,level属性指定
appender-ref标识的appender将会添加到这个logger
-->
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
<!-- 4.2 生产环境:输出到文档
<springProfile name="pro">
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
<appender-ref ref="WARN_FILE" />
</root>
</springProfile> -->
</configuration>
模板 2:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 属性描述 scan:设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 定义日志文件 输入位置 -->
<property name="log_dir" value="logs/" />
<!-- 日志最大的历史 30天 -->
<property name="maxHistory" value="30"/>
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 对日志进行格式化 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger -%msg%n</pattern>
</encoder>
</appender>
<!-- ERROR级别日志 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录ERROR级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责出发滚动 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志输出位置 可相对、和绝对路径 -->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/error-log.log</fileNamePattern>
<!-- 最大保存30天-->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- WARN级别日志 appender -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录WARN级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/warn-log.log
</fileNamePattern>
<!-- 日志最大的历史 30天 -->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO级别日志 appender -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录INFO级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/info-log.log
</fileNamePattern>
<!-- 日志最大的历史 30天 -->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<appender name="DruidFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${log_dir}/settle-query.log-druid-%d{yyyy-MM-dd}</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>90</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
</pattern>
</encoder>
</appender>
<!-- DEBUG级别日志 appender -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录DEBUG级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/debug-log.log
</fileNamePattern>
<!-- 日志最大的历史 30天 -->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE级别日志 appender -->
<appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤器,只记录ERROR级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>TRACE</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/trace-log.log
</fileNamePattern>
<!-- 日志最大的历史 30天 -->
<maxHistory>${maxHistory}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<!-- 指定其他logger的Appender -->
<logger name="org.springframework" value="WARN" />
<logger name="org.apache" value="WARN" />
<logger name="com.netflix" value="WARN" />
<logger name="me.chanjar" value="WARN" />
<logger name="com.ulisesbocchio" value="WARN" />
<logger name="org.hibernate" value="WARN" />
<logger name="com.github.liuweijw" value="INFO" />
<logger name="c.n.discovery" value="WARN" />
<logger name="o.s.c.s" value="WARN" />
<logger name="c.u.j.encryptor" value="WARN" />
<logger name="o.s.boot" value="WARN" />
<!-- 指定druid的日志级别为DEBUG,除了继承rootLogger的appender外,自己还加了DruidFILE -->
<logger name="druid" level="DEBUG">
<appender-ref ref="DruidFILE" />
</logger>
<!-- 指定rootLogger级别 INFO,appender为STDOUT,ERROR,INFO-->
<root level="INFO">
<!-- 控制台输出 -->
<appender-ref ref="STDOUT" />
<!-- 文件输出 -->
<appender-ref ref="ERROR" />
<appender-ref ref="INFO" />
</root>
</configuration>
3、配置文件结构详解
Logback的配置,需要配置输出源Appender,打日志的Logger(子节点)和Root(根节点)实际上,它输出日志是从子节点开始,子节点如果有输出源直接输出,如果无,判断配置的additivity,是否向上级传递,即是否向root传递,传递则采用Root的输出源,否则不输出日志。
logback.xml配置文件根结点为configuration,内主要包含contextName、property、appender、filter、pattern、logger、root 等结点。基本结构可以描述为1个configuration元素,包含零个或多个appender元素,后跟零个或多个logger元素,后跟最多1个root元素(也可以没有)
01、configuration 根元素
根元素configuration有三个属性:debug、 scan、scanPeriod,及一个特殊的属性:packagingData,示例及解释如下:
- debug:默认为false,若设置为true,则打印出logback内部运行日志信息
- scan:默认值为true,若设置为true,配置文件如果发生改变,将会自动重新加载配置文件
- scanPeriod:与scan配合使用,当scan为true时,此属性生效,默认的时间间隔为1分钟,设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。可以设置为scanPeriod="30 seconds"每30秒检测一次。单位有:milliseconds、seconds、munites、hours,默认为milliseconds
注意:如果是在本地 IDE 编辑器中测试,注意修改的是 classes 编译目录下的 logback.xml 文件,而不是 resources 目录下的源文件(一般不用) - packagingData:在堆栈跟踪中启用包数据。注意从版本1.1.4开始,包装数据默认为禁用
<?xml version="1.0" encoding="UTF-8"?>
<!-- debug : 开启logback运行日志输出,true为始终输出,false为出错时才输出-->
<!-- scan :开启"热更新"-->
<!-- scanPeriod:"热更新"扫描周期,默认 60 seconds(60秒) 单位有:milliseconds、seconds、munites、hours,默认为milliseconds-->
<configuration debug="true" scan="true" scanPeriod="60 seconds">
</configuration>
开启packagingDatas配置:可按如下配置开启包数据(配置文件或者代码形式取其一即可):
<configuration packagingData="true">
</configuration>
import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.setPackagingDataEnabled(true);
如果开启了,logback 会在输出的堆栈行中显示它是属于哪个 jar 或者哪个类的。此信息由 jar 文件的名称和版本组成,表明堆栈信息来源于此。此机制对于识别软件版本问题非常有用。但是,计算成本相当昂贵,尤其是在经常引发异常的应用程序中。以下演示开启的结果,即多了 [] 括号内的信息。
14:28:48.835 [btpool0-7] INFO c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
02、contextName 元素
日志上下文名称:contextName,每个日志组件(logger)都会关联到日志上下文,默认上下文名称是:default,用于标识应用,如果多个应用输出到同一个地方,就有必要使用%contextName
来区别。contextName直接在configuration元素下:
<configuration>
<!--定义后,在其他property属性或appender中便可通过”%contextName”来获取和使用该上下文名称了-->
<contextName>HelloWorld-log</contextName>
</configuration>
03、property 属性元素
1、用来定义变量值的标签,通过property定义的值会被插入到logger上下文中。property标签中有: name、value 两个属性,属性含义如下:
- name:值是变量的名称
- value:值是变量定义的值
- 定义变量后,可以使
${name}
来使用变量
<configuration>
<!--定义变量-->
<property name="logName" value="logbackStudy" />
<!--从src/main/resources加载配置文件(文件内key=val形式每行一个), 配置文件中的值可直接使用-->
<property resource="logback.properties" />
<!--file获取绝对路径-->
<property file="D:\\logback.properties" />
</configuration>
2、如果是在Spring或SpringBoot项目当中,想让value值是通过配置文件获取,可使用springProperty来定义。这里是获取项目名称和运行的服务器IP
- scope:作用域
- name:名称,在下面引用的时候的名字
- source:在application.properties文件中的key
- defaultValue:默认值,该属性在application.properties中未设置时,会使用默认值
<!--其中source指定的spring.application.name便是在application.properties当中配置变量。此配置还是比较常用的,可以做到灵活区分环境-->
<springProperty scope="context" name="appName" source="spring.application.name" />
<springProperty scope="context" name="ip" source="spring.cloud.client.ipAddress" />
3、变量作用域(scope)
定义的变量是有作用域的,如本地作用域,上下文作用域,系统级作用域。默认是本地作用域。
- Local Scope(本地作用域):从配置文件中定义的本地变量即在本地配置文件使用。每次解析和执行配置文件时,都会重新定义本地作用域中的变量
- Context Scope(上下文作用域):一个拥有上下文作用域的变量存在于上下文中,于上下文共存,直到被清除。在所有记录事件中都可用到,包括那些通过序列化发送到远程主机的事件
- System Scope(系统级作用域):系统级作用域的变量被插入到JVM的系统属性中,生命周期和JVM一致,直到被清除
- 在进行属性替换时,查找变量的顺序为:local,context,system
04、timestamp 时间元素
获取时间戳字符串并格式化:timestamp。他有两个属性key和datePattern
- key:标识timestamp的名字
- datePattern:设置将当前时间(解析配置文件的时间)转换为字符串的模式,遵循java.txt.SimpleDateFormat的格式
<configuration>
<!-- 实际上也是定义成变量给后面使用,与property类似 -->
<timestamp key="nowDate" datePattern="yyyyMMdd" />
<timestamp key="nowDateTime" datePattern="yyyyMMdd_HHmmss" />
<contextName>${nowDateTime}</contextName>
</configuration>
05、conversionRule 元素
主要用来配置转换器,可以自定义Pattern模板,也可以引入一些其他模板来解析Pattern,比如打印彩色日志,打印IP地址。conversionRule 元素中有两个参数,解释如下:
- conversionWord:在Pattern中的关键字
- converterClass:转换器类
<!-- 彩色日志,需要在idea中安装grep console插件 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="COLOR_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%p) %clr(${PID}){magenta} %clr([%.10t]){yellow} %clr([%c{1}]){blue}%clr([%L]){faint}%X{traceId} %m%n"/>
上面%clr
就是要用ColorConverter
来解析,后面{blue}
就是要展示的颜色。
06、appender 日志输出元素
Appender 的介绍
官方文档为:https://logback.qos.ch/manual/appenders.html
Appender 的组件有两个属性:name和class,并且需要强制赋值,解释如下:
- name:定义写日志组件的唯一名称,可以随便定义,后面使用该appender是也是通过名称来指定
- class:指定要实例化的appender类的完全限定名称
Appender种类有很多,常用的种类有:ConsoleAppender(输出到控制台)、FileAppender(输出到文件)、SMTPAppender(输出到邮件)、DBAppender(输出到数据库)、AsyncAppender(异步输出,包装其它具体的appender,不单独使用)等,如下介绍常用的
- ConsoleAppender:日志输出到控制台,类名:ch.qos.logback.core.ConsoleAppender
- FileAppender:日志输入到文件,类名:ch.qos.logback.core.FileAppender
- RollingFileAppender:滚动记录文件(FileAppender的子类)当符合条件(大小、时间)日志进行切分处理。类名:ch.qos.logback.core.rolling.RollingFileAppender
- 实战中ConsoleAppender及RollingFileAppender使用较多,若需要自定义如把日志输出到消息队列,可以自定义实现AppenderBase接口
除了上述Appender外,另外还有SocketAppender(输出到socket)、SMTPAppender(输出到邮件)、DBAppender(输出到数据库)等具体使用查文档
ConsoleAppender
ConsoleAppender 就跟名字显示的一样,是将日志事件附加到控制台,进一步说就是通过 System.out(默认) 或 System.err 来进行输出。ConsoleAppender 通过用户指定的 encoder,格式化日志事件。
属性名 | 类型 | 描述 |
---|---|---|
target | String | System.out 或 System.err。默认为 System.out |
encoder | Encoder | 决定通过哪种方式将事件写入 OutputStreamAppender |
withJansi | boolean | 默认值为 false。设为 true 可以激活 Jansi 在 windows 使用 ANSI 彩色代码。 在 windows 上如果设置为 true,你应该将 org.fusesource.jansi:jansi:1.9 这个 jar 包放到 classpath 下。基于 Unix 实现的操作系统,像 Linux、Max OS X 都默认支持 ANSI 才彩色代码。 |
<configuration>
<!--日志输出器:输出到控制台-->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!--控制输出流对象, 默认为 System.out 是黑色字体, System.err 是红色字体 -->
<target>System.out</target>
<!-- 输出的日志消息格式配置, encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d [%t] [%-5level] %c %M %L %m %n</pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根记录器 -->
<root level="INFO">
<!--输出记录的日志-->
<appender-ref ref="consoleAppender"/>
</root>
</configuration>
FileAppender
FileAppender是OutputStreamAppender的子类,将日志事件输出到文件中。通过file来指定目标文件。如果该文件存在,根据append的值,要么将日志追加到文件中,要么该文件被截断。
属性名 | 类型 | 描述 |
---|---|---|
file | String | 要写入文件的名称。如果文件不存在,则新建 |
encoder | Encoder | 参见 ConsoleAppender 的属性 |
append | boolean | 如果为 true,日志事件会被追加到文件中,否则的话,清空现存文件。默认为 true |
prudent | boolean | 在严格模式下,FileAppender会将日志安全的写入指定文件。即使在不同的 JVM 或者不同的主机上运行 FileAppender 实例。默认值为 false。严格模式可以与 RollingFileAppender 结合使用 |
1、将日志输出到文件:FileAppender。包含file(文件名)、encoder(格式化)元素
<configuration>
<!--定义变量, 通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量-->
<property name="pattern" value="%d [%t] [%-5level] %c %M %L %m %n"></property>
<property name="log_file" value="./logs"></property>
<!-- 通过 "bySecond" 将时间格式化成 "yyyyMMdd'T'HHmmss" 的形式插入到 logger 的上下文中这个值对后续的配置也适用-->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss" />
<!--日志输出器:输出到文件-->
<appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
<!-- 利用之前创建的 timestamp 来创建唯一的文件, 生成的文件名: log-20220402T122403.log -->
<file>${log_file}/logback-${bySecond}.log</file>
<!-- 将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
<immediateFlush>true</immediateFlush>
<!-- 输出的日志消息格式配置, encoder 默认使用 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>${pattern}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="fileAppender"/>
</root>
</configuration>
2、输出到html格式的日志文件:appender设置为FileAppender,encoder设置为LayoutWrappingEncoder,layout设置为LayoutWrappingEncoder
<configuration>
<!--name是变量的名称,value是变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量-->
<property name="html_pattern" value="%d{yyyy-MM-dd HH:mm:ss}%t%-5level%c%M%L%m%n"></property>
<property name="log_file" value="./logs"></property>
<!--输出到html格式的日志文件, 这个也属于输出到指定文件, 还是使用FileAppender类-->
<appender name="htmlFileAppender" class="ch.qos.logback.core.FileAppender">
<!-- 日志文件保存路径-->
<file>${log_file}/logback.html</file>
<!-- html消息格式配置-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>${html_pattern}</pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="htmlFileAppender"/>
</root>
</configuration>
注意:我们只能设置打印的日志信息内容,不能设置这个网页的打印格式以及样式。但是,当我们打印出logback.html文件后,我们可以人为的修改其中的样式以及格式这个html中包含 HTML+CSS。在实际的开发中,如果日志文件不是很大,我们可以考虑使用html进行日志打印,因为可读性强。
RollingFileAppender
RollingFileAppender是FileAppender的子类,扩展了FileAppender,具有翻转日志文件的功能。当满足某个特定的条件之后,将日志输出到另外一个文件。
属性名 | 类型 | 描述 |
---|---|---|
file | String | 被写入文件名,可以是相对目录或绝对目录,如果上级目录不存在会自动创建,无默认值 |
rollingPolicy | RollingPolicy | 当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名 |
triggeringPolicy | TriggeringPolicy | 告知 RollingFileAppender 合适激活滚动 |
encoder | Encoder | 参见 ConsoleAppender 的属性 |
append | boolean | 如果为 true,日志事件会被追加到文件中,否则的话,清空现存文件。默认为 true |
prudent | boolean | 当为true时,不支持FixedWindowRollingPolicy。支持TimeBasedRollingPolicy,但是有两个限制, 1.不支持也不允许文件压缩,2.不能设置file属性,必须留空 |
1、RollingPolicy滚动策略
RollingPolicy用于配置滚动策略,支持TimeBasedRollingPolicy(基于时间滚动)、SizeAndTimeBasedRollingPolicy(基于大小和时间滚动)、FixedWindowRollingPolicy(固定窗口滚动和大小触发)、其中FixedWindowRollingPolicy和SizeBasedTriggeringPolicy一般同时使用
RollingPolicy滚动策略包括以下几种:
- TimeBasedRollingPolicy:基于时间的滚动策略
- SizeAndTimeBasedRollingPolicy:基于文件大小和时间的滚动策略
- FixedWindowRollingPolicy:基于固定窗口大小的滚动策略,就是将归档日志文件到最大了就写到下一个文件里,而窗口大小就是最多允许日志文件数
- SizeBasedTriggeringPolicy:FixedWindowRollingPolicy和SizeBasedTriggeringPolicy一般同时使用,用于触发FixedWindowRollingPolicy
2、TimeBasedRollingPolicy
TimeBasedRollingPolicy:它是基于时间来定义轮转策(最常用的轮转策略)例如按天或月。TimeBasedRollingPolicy 既负责轮转的行为,也负责触发轮转。实际上:TimeBasedRollingPolicy 同时实现了 RollingPolicy 与 TriggeringPolicy 接口。
TimeBasedRollingPolicy 的配置需要一个强制的属性 fileNamePattern 以及其它的可选属性:
属性名 | 类型 | 描述 |
---|---|---|
fileNamePattern | String | 该属性的值应该由文件名加上一个 %d 的占位符。%d 应包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略日期格式,那么就默认为 yyyy-MM-dd 。如果 fileNamePattern 以 .gz 或者 .zip 结尾,将会启动这个压缩特性 |
maxHistory | int | 这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。如单位按月轮转时maxHistory = 6,只会保留6个月的日志。单位根据时间格式维度来区分:按天(yyyy-MM-dd) 按月(yyyy-MM) |
totalSizeCap | int | 在有 maxHistory 的限制下,进一步限制所有日志文件大小之和的上限,超过则从最旧的日志开始删除 |
cleanHistoryOnStart | boolean | 如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作 |
<configuration>
<!--日志输出器:基于时间的滚动策略输出到文件-->
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/logback.log</file>
<!-- 启用基于时间的滚动策略:TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置轮转策略为:每秒钟轮转 后缀如果设置成.gz或.zip后缀会自动压缩-->
<fileNamePattern>logs/logback_%d{yyyy-MM-dd_HH-mm-ss}.log</fileNamePattern>
<!-- 只保留最近1分钟内的日志,超出后会删除旧日志-->
<maxHistory>60</maxHistory>
<!-- 所有日志文件大小之和的上限,超出后会删除旧日志-->
<totalSizeCap>10MB</totalSizeCap>
<!-- 在appender启动的时候执行删除历史日志操作-->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<!-- 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%date %-5level -[%thread] %class.%method/%line : %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="rollingFileAppender"/>
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
// 1秒后启动,每秒跑1次
new Timer().scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
logger.error("error test");
logger.warn("warn test");
logger.info("info {}", "test");
logger.debug("debug test ");
logger.trace("trace test");
}
}, 1000, 1000);
}
}
执行后可以发现1分钟后生成了61个文件。1个logback.log,60个logback_2022-04-03_12-12-xx.log。这是因为每秒生产一个文件,而maxHistory设置了保留最近一分钟的日志(并且每个日志文件都比较小,60个加起来也没超过totalSizeCap总日志大小的上限)。此时还可以发生另外一种情况,就是把totalSizeCap总文件大小设置比较小,才生成几个文件就达到totalSizeCap总日志大小的上限。
3、SizeAndTimeBasedRollingPolicy
SizeAndTimeBasedRollingPolicy:基于时间+大小进行滚动(既能按时轮转,又能限制每个日志文件的大小)是对于第一种的补充。避免单个日志文件过大。实际查看源码也可以看出SizeAndTimeBasedRollingPolicy继承了TimeBasedRollingPolicy,并增加了一个maxFileSize属性。
属性名 | 类型 | 描述 |
---|---|---|
fileNamePattern | String | 该属性的值应该由文件名加上一个 %d 的占位符。%d 应包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略日期格式,那么就默认为 yyyy-MM-dd 。并且需要包含一个 i% 的占位符,当文件大小超过maxFileSize大小时,则用下标新建文件。如果 fileNamePattern 以 .gz 或者 .zip 结尾,将会启动这个压缩特性 |
maxFileSize | int | 按照文件大小进行拆分,当文件大小达到1MB时就会将日志进行压缩 |
maxHistory | int | 这个可选的属性用来控制最多保留多少数量的归档文件,将会异步删除旧的文件。如单位按月轮转时maxHistory = 6,只会保留6个月的日志。单位根据时间格式维度来区分:按天(yyyy-MM-dd) 按月(yyyy-MM) |
totalSizeCap | int | 在有 maxHistory 的限制下,进一步限制所有日志文件大小之和的上限,超过则从最旧的日志开始删除 |
cleanHistoryOnStart | boolean | 如果设置为 true,那么在 appender 启动的时候,归档文件将会被删除。默认的值为 false。归档文件的删除通常在轮转期间执行。但是,有些应用的存活时间可能等不到轮转触发。对于这种短期应用,可以通过设置该属性为 true,在 appender 启动的时候执行删除操作 |
<configuration>
<!--日志输出器:基于文件大小和时间的滚动策略-->
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 活动中的日志文件名(支持绝对和相对路径) -->
<file>logs/logback.log</file>
<!-- 基于时间和文件大小的滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 设置轮转策略为: 每小时轮转, 按照时间和压缩格式来声明文件名-->
<!-- %i是当文件大小超过maxFileSize大小时, 则用下标新建文件, .gz或.zip后缀会自动压缩-->
<fileNamePattern>logs/logback_%d{yyyy-MM-dd_HH-mm-ss}_%i.log.zip</fileNamePattern>
<!-- 每个文件最多1MB, 保留1分钟的历史记录, 但最多200MB-->
<!-- 单个日志文件的最大大小-->
<maxFileSize>1MB</maxFileSize>
<!-- 根据当前轮转策略, 只保留最近1分钟内的日志,超出后会删除旧日志-->
<maxHistory>60</maxHistory>
<!-- 所有日志文件大小之和的上限,超出后会删除旧日志-->
<totalSizeCap>200MB</totalSizeCap>
<!-- 设置为true并且maxHistory不为零才能起效-->
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<!-- 日志输出格式, 默认为 ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%date %-5level -[%thread] %class.%method/%line : %msg%n</pattern>
</encoder>
</appender>
<!-- 根记录器 -->
<root level="INFO">
<appender-ref ref="rollingFileAppender"/>
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
// 1秒后启动,每秒跑1次
new Timer().scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
logger.error("error test");
logger.warn("warn test");
logger.info("info {}", "test");
logger.debug("debug test ");
logger.trace("trace test");
}
}, 1000, 1000);
}
}
执行后可以发现会生成文件logback.log与文件logback_2022-04-03_17-42-xx_0.log.zip若干个。若在同一个时间段内生成的单个文件超过了maxFileSize后,%i
就会生效(默认是从0开始)
4、FixedWindowRollingPolicy
可以理解成自定义滚动规则,避免使用出现单个文件过大或者日志文件过多的情况。需要同时配置triggeringPolicy用于指定滚动触发规则。对以上的补充。
FixedWindowRollingPolicy 根据固定窗口算法重命名文件,filaNamePattern 表示归档文件的名字。这个属性是必选,并必须包含一个表示整形的占位符 i%
属性名 | 类型 | 描述 |
---|---|---|
fileNamePattern | String | FixedWindowRollingPolicy 在重命名日志文件时将会根据这个属性来命名。它必须包含一个 i% 的占位符,该占位符指明了窗口索引的值应该插入的位置。 例如,当该属性的值为 MyLogFile%i.log ,最小与最大的值分别为 1 和 3 。将会产生的归档文件为 MyLogFile1.log ,MyLogFile2.log ,MyLogFile3.log 。 文件压缩的方式也是通过该属性来指定。例如,设置该属性的值为 MyLogFile%i.log.zip ,那么归档文件将会被压缩成 zip 格式。也可以选择压缩成 gz 格式。 |
minIndex | int | 表示窗口索引的下界 |
maxIndex | int | 表示窗口索引的上界 |
<configuration>
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 活动中的日志文件名(支持绝对和相对路径) -->
<file>logs/logback.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>logs/logback_%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>5</maxIndex>
</rollingPolicy>
<!-- 观察当前活动文件的大小,如果已经大于了指定的值,它会给RollingFileAppender发送信号触发对当前活动文件的轮转-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>10KB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="rollingFileAppender" />
</root>
</configuration>
TriggeringPolicy 用于通知何时触发滚动。实现类:SizeBasedTriggeringPolicy
- SizeBasedTriggeringPolicy:观察当前活动文件的大小,如果大于了指定的值,它会给RollingFileAppender发一个信号触发对当前活动文件的轮转
- SizeBasedTriggeringPolicy:只接收 maxFileSize 这一个参数,它的默认值是 10MB
- maxFileSize:可以为字节 / 千字节 /兆字节 /千兆字节,数值 加 KB,MB 或 GB。例:5000000,5000KB,5MB(这3个大小一样) 及 2GB
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
// 1秒后启动,每秒跑1次
new Timer().scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
logger.error("error test");
logger.warn("warn test");
logger.info("info {}", "test");
logger.debug("debug test ");
logger.trace("trace test");
}
}, 1000, 1000);
}
}
执行代码可以发现:最后生成文件:logback.log、logback_1.log.zip - logback_5.log.zip(zip解压后可以发现文件名:logback_2022-04-03_1754,具体可以参考源码:FixedWindowRollingPolicy)如果fileNamePattern中没有设置以.zip结尾的话,默认名称就是设定名称+Index
5、轮转周期示例 fileNamePattern
适用上面的 SizeAndTimeBasedRollingPolicy 和 TimeBasedRollingPolicy 滚动策略<fileNamePattern></fileNamePattern>
fileNamePattern 格式 | 说明 |
---|---|
app_%d.log | 每天轮转。不指定%d 日期格式时默认为 yyyy-MM-dd |
app_%d{yyyy-MM}.log | 每个月开始的时候轮转 |
app._%d{yyyy-ww}.log | 每周的第一天(取决于时区) |
app_%d{yyyy-MM-dd_HH}.log | 每小时轮转。如:app_2020-10-24_10.log |
app_%d{yyyy-MM-dd_HH-mm}.log | 每分钟轮转;。如:app_2020-10-24_10-32.log |
app_%d{yyyy-MM-dd_HH-mm, UTC}.log | 每分钟轮转(时间格式是 UTC) |
app/%d{yyyy-MM}/%d.log | 每天轮转。第一个%d 被辅助标记。第二个%d 为主要标记。如:app/2020-10/2020-10-24.log |
重点注意:fileNamePattern中设置的时间格式生产文件名不能使用空格已经冒号等特殊符号(不然不会生产新的滚动文件)
AsyncAppender 异步
上面的Appender日志输出到文件是同步输出的,即每次输出都会直接写IO到磁盘文件。对于高并发,会产生一定的阻塞,造成不必要的性能损耗。Logback提供了日志异步输出的AsyncAppender。处理方式较简单,添加一个基于异步写日志的appender,并指向原配置的appender即可。AsyncAppender 参数说明:
参数 | 默认值 | 说明 |
---|---|---|
discardingThreshold | 20 | 如果设置discardingThreshold=0,表示 queue 满了,不丢弃,block 线程。默认情况下,当阻塞队列剩余 20% 的容量时,它将丢弃级别跟踪、调试和信息事件,只保留级别警告和错误事件。要保留所有事件,请将 discardingThreshold 设置为0 |
queueSize | 256 | 假设 IO 影响 30s,日志和 qps 比例是1:1,单容器压测值 1500 qps 则可以推算出 queueSize 的值,queueSize 的设置公式:30 * 1500=45000 |
neverBlock | false | 如果为 false(默认值),则追加程序将阻止追加到完整队列,而不是丢失消息。设置为 true 时,附加程序只会丢弃消息,不会阻止您的应用程序 |
includeCallerData | false | 提取呼叫者数据可能相当昂贵。 为了提高性能,默认情况下,当事件添加到事件队列时,不会提取与事件关联的调用者数据。 默认情况下,仅复制线程名称和 MDC 等“廉价”数据。 您可以通过将 includeCallerData 属性设置为 true 来指示此附加程序包含调用方数据。 |
<configuration>
<!--输出到文件-->
<appender name="fileAppender" class="ch.qos.logback.core.FileAppender">
<file>logs/logback.log</file>
<!--提高高的日志吞吐量-->
<immediateFlush>false</immediateFlush>
<encoder>
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!--异步输出-->
<appender name="asyncAppender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 默认如果队列的80%已满,则会丢弃TRACT,DEBUG,INFO级别的日志,若要保留全部日志,设置为0 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能,默认值为256 -->
<queueSize>1024</queueSize>
<!-- 设置附加程序只会丢弃消息,不会阻止您的应用程序-->
<neverBlock>true</neverBlock>
<!--是否提取调用者数据-->
<includeCallerData>true</includeCallerData>
<!-- 添加附加的appender,最多只能添加1个 -->
<appender-ref ref="fileAppender"/>
</appender>
<!-- 异步输出关联到root根logger -->
<root level="INFO">
<appender-ref ref="asyncAppender" />
</root>
</configuration>
这里有个需要注意的地方,那就是 AsyncAppender 必须在其引用的 Appender 配置的后面,否则会使配置不生效。
SMTPAppender 邮件
使用SpringBoot + Logback 配置程序异常自动发送邮件:https://mp.weixin.qq.com/s/N4OrKlrtCDeEd95XWGi9YA
现在想更要发生异常自动发送邮件的功能,需要使用到 SMTPAppender 输出日志到邮件组件
1、加入依赖(JavaMail)
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
2、修改logback的xml的配置文件
<configuration debug="true">
<!--日志输出器:输出到控制台-->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%t] [%-5level] %c %M %L %m %n</pattern>
</encoder>
</appender>
<!--邮件设置-->
<property name="smtpHost" value="smtp.qq.com"/>
<!--QQ邮箱的SMTP(SLL)端口为465或587,其他邮箱自行去查看)-->
<property name="smtpPort" value="587"/>
<property name="mailFrom" value="xxx@qq.com"/>
<property name="username" value="xxx@qq.com"/>
<property name="mailTo" value="xxx@qq.com,xxx@qq.com"/>
<property name="password" value="fxomimpmgpjgbhxx"/>
<property name="SSL" value="false"/>
<property name="mailSubject" value="Exception information reminder"/>
<appender name="mailAppender" class="ch.qos.logback.classic.net.SMTPAppender">
<smtpHost>${smtpHost}</smtpHost>
<smtpPort>${smtpPort}</smtpPort>
<username>${username}</username>
<password>${password}</password>
<SSL>${SSL}</SSL>
<asynchronousSending>false</asynchronousSending>
<to>${mailTo}</to>
<from>${mailFrom}</from>
<subject>${mailSubject}</subject>
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<!-- 这里配置的是邮件表格的形式 跟截图的邮件格式是一样 其实不加这一块也是样式也是一样的 但是可以自己研究自定义样式-->
<Pattern>%date%level%thread%logger{0}%line%message</Pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="consoleAppender"/>
<appender-ref ref="mailAppender"/>
</root>
</configuration>
3、测试代码,输出ERROR级别日志,会自动发送到邮件中
package com.xyz;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = (Logger)LoggerFactory.getLogger(LogbackTest.class);
String s = null;
try {
s.equals("");
} catch (Exception e) {
logger.error("error message: {}",e);
}
}
}
4、邮件中收到内容如下:
Date | Level | Thread | Logger | LineOfCaller | Message |
---|---|---|---|---|---|
2022-04-06 11:37:18,549 | ERROR | main | LogbackTest | 14 | error message: {} |
java.lang.NullPointerException: null at com.xyz.LogbackTest.main(LogbackTest.java:14) |
Logback + ELK 配置
GitHub地址:https://github.com/logfellow/logstash-logback-encoder/blob/main/README.md
如果项目中的日志采用的是基于ELK(Elasticsearch、Logstash、Kibana)来进行日志管理。则可以在pom文件中引入logstash-logback-encoder依赖
<!--由于配置中使用了json格式的日志输出,所以需要引入如下依赖-->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.1</version>
</dependency>
然后在logback-spring.xml中配置对应的appender:
<!--logstash 配置部分-->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- 发送到Logstash提供的服务地址 -->
<destination>192.168.0.11:5061</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"project": "springboot-logback-elk"}</customFields>
</encoder>
</appender>
该appender的使用与其他appender的使用无异。主要使用了LogstashTcpSocketAppender类来完成与Logstash的通信。其中destination为Logstash提供的服务地址。customFields为自定义的参数,便于Logstash识别日志是从哪个业务系统传输过来的。
如下是参考一个详细(完整的logback配置示例ELK整合包含生成json日志:https://www.zhangshengrong.com/p/OgN5Dvvxan/)
<appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/logback.json</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/logback-%d{yyyy-MM-dd}.json</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC</timeZone>
</timestamp>
<pattern>
<pattern>
{
"ip": "${ip}",
"app": "${appName}",
"level": "%level",
"trace": "%X{X-B3-TraceId:-}",
"span": "%X{X-B3-SpanId:-}",
"parent": "%X{X-B3-ParentSpanId:-}",
"thread": "%thread",
"class": "%logger{40}",
"message": "%message",
"stack_trace": "%exception{10}"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
07、encoder & pattern元素
官网地址:
- https://logback.qos.ch/manual/encoders.html
- https://logback.qos.ch/manual/layouts.html
- https://logback.qos.ch/manual/layouts.html#conversionWord
encoder和layout一样,用来格式化输出。encoder是在logback 0.9.19 版本之后引入的。在这个版本之前,大部分的appender使用layout来格式化输出,但之后FileAppender和他的子类推荐使用encoder来代替layout。常用的是PatternLayoutEncoder(这是默认的encoder)所以我们不用指定,直接使用encoder标签就行。PatternLayoutEncoder的组成和layout一样。如下:
Pattern表达式:
Conversation Word | Effect |
---|---|
d{pattern}、date{pattern} | 日志打印的时间,语法与java.text.SimpleDateFormat兼容。Conversion Pattern Result |
%d | 2006-10-20 14:06:49,812 |
%date | 2006-10-20 14:06:49,812 |
%date{ISO8601} | 2006-10-20 14:06:49,812 |
%date{HH:mm:ss.SSS} | 14:06:49.812 |
%date{dd MMM yyyy ;HH:mm:ss.SSS} | 20 oct. 2006;14:06:49.812 |
%d{dd MMM yyyy ;HH:mm:ss.SSS,Australia/Perth} | 05 4月 2022 ;11:34:13.168 |
Conversation Word | Effect |
---|---|
变量名 | %date 时间打印详情请参考上方 |
%date{pattern, timezone}、%d | 日志打印的时间。pattern与java.text.SimpleDateFormat兼容 pattern不填写默认值是:yyyy-MM-dd hh:mm:ss,SSS。 第二个参数是时区,例如:%date{HH:mm:ss.SSS, Australia/Perth} |
%logger、%lo、%c{length} | 输出当前日志名称。没有length则输出全名。如:com.xyz.LogbackTest |
%class、%C{length} | 输出日志调用所在类,没有length则打印全类名。如:com.xyz.LogbackTest(与%c一样) |
%method、%M | 输出日志所在方法(没有length参数)如:main |
%caller{length} | 日志调用位置,length代表日志深度,如:Caller+0 at com.xyz.LogbackTest.main(LogbackTest.java:9) |
%thread、%t | 输出线程名(没有length参数) |
%level、%le、%p | 输出日志级别(没有length参数) |
%message、%msg、%m | 输出日志记录内容,就是调用logger的方法的时候传入的log字符串 |
%exception、%ex | 输出异常信息。如:logger.error("error...",new Exception("exception")); |
%line、%L | 输出日志的行数。如:9 |
%file、%F | 输出调用logger的java源码文件名,速度不快,避免使用。如:LogbackTest.java |
%n | 换行符 |
% | 输出%号 |
特殊占位符 | |
%X{user} | 表示可以获取外部自定义传入的值, 如:%X{user} =》org.slf4j.MDC.put("user", "xx-yy-zz"); |
宽度设置 | 可以限制上面的ConversationWord的宽度和左右对齐 |
%20logger | 最小宽度20,当小于20时,则左侧留空白。右对齐 |
%-20logger | 最小宽度20,当小于20时,则右侧留空白。左对齐 |
%.30logger | 最大宽度30,超出时从头部开始截断。如:%.2、test=》st |
%.-30logger | 最大宽度30,超出时从末尾开始截断。如:%.-2、test=》te |
%20.30logger | 最小20,最大30,小于20的时候右对齐,大于30的时候开始处截断 |
%-20.30logger | 最小20,最大30,小于20的时候左对齐,大于30的时候开始处截断 |
显示设置 | |
%highlight() | 突出显示,如:%highlight(%-5level) |
%green(%red、%blue、%white) | 字体显示为指定颜色 |
{length} | 可指定长度,如:%logger{36} |
网络访问设置 | 需要依赖logger-access模块 |
%remoteIP、%a | 远程IP |
%localIP、%A | 本地IP |
%clientHost、%h | 远程主机名 |
%localPort | 本地端口 |
%requestMethod、%m | http请求方法 |
%protocol、%H | http请求协议 |
%statusCode、%s | http请求status code |
%requestURL、%r | http请求地址 |
%requestURI、%U | http请求资源地址 |
%queryString、%q | http请求参数 |
%server、%v | 服务器地址 |
%elapsedTime、%D | http请求处理的时间,单位是毫秒 |
%elapsedSeconds、%T | http请求处理的时间,单位是秒 |
%date、%t | 日志记录时间 |
%threadName、%I | 处理请求的线程名 |
%reqAttribute{attributeName} | http请求attribute值 |
%reqCookie{cookie} | http请求cookie值 |
%reqContent | http请求体内容 |
%fullRequest | http完整请求 |
%responseContent | http响应 |
%fullResponse | http完整响应 |
格式化修饰器:可以限制上面的ConversationWord的宽度和左右对齐。比如:
- 左对齐修饰符(-):接着是可选的最小宽度修饰符,用十进制数表示。如果字符小于最小宽度,则左填充或右填充,默认是左填充(即右对齐),填充符为空格。如果字符大于最小宽度,字符永远不会被截断
- 最大宽度修饰符(.):后面加十进制数。如果字符大于最大宽度,则从前面截断。后面加减号“-”在加数字,表示从尾部截断
1、用来指定日志的输出格式及编码等其他配置(encoder元素)
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder><!--日志文件输出格式, 全称形式 及 简写形式-->
<!--2022-04-05 11:17:26,325 com.xyz.LogbackTest com.xyz.LogbackTest main INFO main INFO com.xyz.LogbackTest : test pattern...-->
<pattern>%d %lo %C %M %le %t %le %lo : %m%n</pattern>
<!--2022-04-05 11:18:00,832 com.xyz.LogbackTest com.xyz.LogbackTest main INFO main : test pattern...-->
<pattern>%date %logger %class %method %highlight(%level) %thread : %msg%n</pattern>
<!--2022-04-05 11:18:42.394 default [main] INFO com.xyz.LogbackTest - test pattern...-->
<pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
<!-- 设置字符编码 -->
<charset class="java.nio.charset.Charset">UTF-8</charset>
<!-- 将 immediateFlush 设置为 false 可以获得更高的日志吞吐量 -->
<immediateFlush>false</immediateFlush>
</encoder>
</appender>
2、使用特殊占位符%X{}
示例:
<configuration debug="false">
<!--日志输出器:输出到控制台-->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%X{uuid}] [%X{x}] [%t] [%-5level] %c %M %L %m %n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="consoleAppender"/>
</root>
</configuration>
package com.xyz;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = (Logger)LoggerFactory.getLogger(LogbackTest.class);
org.slf4j.MDC.put("uuid", UUID.randomUUID().toString());
logger.info("info {}", "test");
}
}
2022-04-06 18:46:33,795 [4effd2ef-a4de-4a6c-82c1-c1d522e9aa76] [] [main] [INFO ] com.xyz.LogbackTest main 10 info test
08、filters 过滤器元素
官网地址:https://logback.qos.ch/manual/filters.html
Filter是日志过滤器,是appender里面的子元素。执行一个过滤器会有返回DENY、NEUTRAL、ACCEPT三个枚举值中的一个。appender 有多个过滤器时,按照配置顺序执行。
- DENY:日志将立即被抛弃不再经过其他过滤器
- NEUTRAL:有序列表里的下个过滤器过接着处理日志
- ACCEPT:日志会被立即处理,不再经过剩余过滤器
1、LevelFilter
LevelFilter 基于级别来过滤日志事件。如果事件的级别与配置的级别相等,过滤器会根据配置的 onMatch 与 onMismatch 属性,接受或者拒绝事件
<configuration>
<!--日志输出器:输出到控制台,只打印INFO级别的日志-->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志级别(针对具体指定级别)过滤器-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 只显示INFO级别日志-->
<level>INFO</level>
<!-- INFO级别的日志, 被立即执行不再经过其他过滤器-->
<onMatch>ACCEPT</onMatch>
<!-- 不是INFO级别的日志, 立即被抛弃不再经过其他过滤器-->
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="consoleAppender" />
</root>
</configuration>
2、ThresholdFilter
ThresholdFilter 基于给定的临界值来过滤事件。如果事件的级别等于或高于给定的临界值,当调用 decide() 时,ThresholdFilter 将会返回 NEUTRAL。但是事件的级别低于临界值将会被拒绝。简单来说:针对指定级别及以上级别进行日志过滤
<configuration>
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志级别(针对指定级别及以上级别)过滤器-->
<!-- 会打印INFO及比INFO等级高的日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="consoleAppender" />
</root>
</configuration>
3、EvaluatorFilter
EvaluatorFilter: 求值过滤器,评估、鉴别日志是否符合指定条件。有一下子节点:
<evaluator>
:鉴别器,常用的鉴别器是JaninoEventEvaluato,也是默认的鉴别器,它以任意的java布尔值表达式作为求值条件,求值条件在配置文件解释过成功被动态编译,布尔值表达式返回true就表示符合过滤条件。evaluator有个子标签<expression>
,用于配置求值条件<onMatch>
:用于配置符合过滤条件的操作<onMismatch>
:用于配置不符合过滤条件的操作
求值表达式作用于当前日志,logback向求值表达式暴露日志的各种字段(evaluator标签中可以用的对象和值):
Name | Type | Description |
---|---|---|
event | LoggingEvent | 与记录请求相关联的原始记录事件,下面所有变量都来自event,例如,event.getMessage()返回下面"message"相同的字符串 |
message | String | 日志的原始消息,例如,设有logger mylogger,“name"的值是"AUB”,对于 mylogger.info(“Hello {}”,name); "Hello {}"就是原始消息。 |
formattedMessage | String | 日志被各式话的消息,例如,设有logger mylogger,“name"的值是"AUB”,对于 mylogger.info(“Hello {}”,name); "Hello Aub"就是格式化后的消息。 |
logger | String | logger 名。 |
loggerContext | LoggerContextVO | 日志所属的logger上下文。 |
level | int | 级别对应的整数值,所以 level > INFO 是正确的表达式。 |
timeStamp | long | 创建日志的时间戳。 |
marker | Marker | 与日志请求相关联的Marker对象,注意“Marker”有可能为null,所以你要确保它不能是null。 |
mdc | Map | 包含创建日志期间的MDC所有值得map。访问方法是:mdc.get(“myKey”) 。mdc.get()返回的是Object不是String,要想调用String的方法就要强转,例如,((String) mdc.get(“k”)).contains(“val”) .MDC可能为null,调用时注意。 |
throwable | java.lang.Throwable | 如果没有异常与日志关联"throwable" 变量为 null. 不幸的是, “throwable” 不能被序列化。在远程系统上永远为null,对于与位置无关的表达式请使用下面的变量throwableProxy |
throwableProxy | IThrowableProxy | 与日志事件关联的异常代理。如果没有异常与日志事件关联,则变量"throwableProxy" 为 null. 当异常被关联到日志事件时,“throwableProxy” 在远程系统上不会为null |
过滤器还可以来自自带的或表达式直接写在配置文件中。
<!-- Janino 是一个极小、极快的 开源Java 编译器-->
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.1.6</version>
</dependency>
<configuration>
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<!-- 根据判断条件的过虑器-->
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator>
<!--expression为java代码,需要添加groupId为org.codehaus.janino,artifactId为janino的maven包-->
<expression><!-- 默认为 ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
// 根据日志消息判断
if(event.getMessage().contains("url")){
return true;
}
if(formattedMessage.contains("url")){
return true;
}
return false;
</expression>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>%date %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="ALL">
<appender-ref ref="consoleAppender"/>
</root>
</configuration>
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
// 1秒后启动,每秒跑一次
new Timer().scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
logger.error("error test");
logger.warn("warn test");
logger.info("info {}", "url");
logger.debug("debug test ");
logger.trace("trace url");
}
}, 1000, 1000);
}
}
2022-04-03 22:46:31,251 1421 [Timer-0] INFO LogbackTest - info url
2022-04-03 22:46:31,251 1421 [Timer-0] TRACE LogbackTest - trace url
2022-04-03 22:46:32,254 2424 [Timer-0] INFO LogbackTest - info url
2022-04-03 22:46:32,254 2424 [Timer-0] TRACE LogbackTest - trace url
EvaluatorFilter 中还有一个特殊的标签,基本很少用,了解即可。
<matcher>
:匹配器,尽管可以使用String类的matches()方法进行模式匹配,但会导致每次调用过滤器时都会创建一个新的Pattern对象,为了消除这种开销,可以预定义一个或多个matcher对象,定以后就可以在求值表达式中重复引用。<matcher>
是<evaluator>
的子标签。<matcher>
中包含两个子标签,一个<name>
,用于定义matcher的名字,求值表达式中使用这个名字来引用matcher;另一个<regex>
,用于配置匹配条件
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator>
<matcher>
<Name>odd</Name>
<!-- filter out odd numbered statements -->
<regex>statement [13579]</regex>
</matcher>
<expression>odd.matches(formattedMessage)</expression>
</evaluator>
<OnMismatch>NEUTRAL</OnMismatch>
<OnMatch>DENY</OnMatch>
</filter>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
4、自定义过滤器
通过实现ch.qos.logback.core.filter.Filter接口可以自定义过滤器,自定义过滤器类LogbackUrlFilter.class,示例如下:
package com.xyz;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
public class LogbackUrlFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if(event.getMessage().contains("url")){
return FilterReply.ACCEPT;
}
if(event.getFormattedMessage().contains("url")){
return FilterReply.ACCEPT;
}
return FilterReply.DENY;
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(LogbackTest.class);
// 1秒后启动,每秒跑一次
new Timer().scheduleAtFixedRate(new TimerTask(){
@Override
public void run() {
logger.error("error test");
logger.warn("warn test");
logger.info("info {}", "url");
logger.debug("debug test ");
logger.trace("trace url");
}
}, 1000, 1000);
}
}
2022-04-03 22:46:31,251 1421 [Timer-0] INFO LogbackTest - info url
2022-04-03 22:46:31,251 1421 [Timer-0] TRACE LogbackTest - trace url
2022-04-03 22:46:32,254 2424 [Timer-0] INFO LogbackTest - info url
2022-04-03 22:46:32,254 2424 [Timer-0] TRACE LogbackTest - trace url
通常情况下,日志输出会配置三个,一个控制台输出用于开发阶段;一个INFO及以上级别的日志输出,可追踪相应的生产日志;一个单独ERROR级别的日志输出,方便快速检查出异常日志。都可以通过Appender和Filter来控制。
09、logger 元素
logger用来设置某一个类或者某个包的日志输出级别、以及关联的appender,这样就可以控制非业务日志不写到对应的文件日志中。
logger标签中包含三个属性:
-
name:要输出日志的包名或者类名,比如com.xyz。必选项
-
level:设置日志的级别(不区分大小写:TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF)如果未设置,则logger会向上继承最近一个非空级别。可选项
-
additivity:是否将日志向上级传递,默认为 true。可选项
logger通过1个或多个子节点appender-ref来控制日志的输出:
<!--表示org.springframework包下的日志以warn级别输出-->
<logger name="org.springframework" level="WARN"/>
<!--表示com包下的日志以debug级别输出-->
<logger name="com" level="debug" />
<!--对指定包指定appender进行日志控制,由于设置了info级别,additivity为true,而且关联consoleAppender,因此info以上级别的日志会输出到控制台-->
<!--同时会把日志上传到父级,即root。若root也有配置consoleAppender的输出的话,会在控制台输出两次。additivity为false,则不会。-->
<!-- 只打印包com.xyz下的日志,最低级别从info开始-->
<logger name="com.xyz" level="info" additivity="true">
<appender-ref ref="consoleAppender" />
</logger>
10、root 元素
root元素配置根记录器。它是个特殊的logger,是所有logger的根节点,只能指定日志级别及level(默认DEBUG)和appender-ref(输出的appender)
<!--这是第一种打印到多个位置的配置(此时打印到控制台和文件的日志级别都是info级别)-->
<root level="info">
<!--引入appender,日志记录器,使用name属性来获取指定的appender对象-->
<appender-ref ref="consoleAppender"/>
<appender-ref ref="fileAppender"/>
</root>
<!--这是第二种打印到多个位置的配置(此时虽然代码中写的控制台和文件的打印级别不同,但是此时打印出来的都是info级别,以最后一个级别为准)-->
<root level="ALL">
<appender-ref ref="fileAppender"/>
</root>
<!--最后一个root会覆盖前面所有的root,只会生效一个root配置-->
<root level="info">
<appender-ref ref="consoleAppender"/>
</root>
level属性的值可以是不区分大小写的字符串TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。root元素可以包含零个或多个appender-ref元素;被引用的每个appender都被添加到根记录器中。
root元素和logger元素的区别?
root是根logger,是一种logger,root和根logger其实是一回事;只不过root中不能有 name 和 additivity 这两个属性
11、include 包含元素
文件包含元素:将配置文件的一部分包含在另一个文件中,可以通过标签include来引入另一个配置文件。如下所示:
<configuration>
<include file="src/main/java/resources/includedConfig.xml"/>
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
</configuration>
includedConfig.xml文件定义了被引用的内容:
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
include标签引入的文件可以为一个文件,一个类路径上的资源,或者一个URL。如下:
<include file="src/main/java/resources/includedConfig.xml"/>
<include resource="includedConfig.xml"/>
<include url="http://xxx.com/includedConfig.xml"/>
如果被引用的文件不存在,logback会打印内部的状态信息。如果包含的文件是可选的,可以通过optional属性设置为true来进制打印显示警告信息。
<include optional="true"/>
12、springProfile 元素
如果是基于SpringBoot项目,针对不同环境(profile)有不同的日志输出,比如开发(dev)环境只输出到控制台,生产环境(prod)只输入INFO和ERROR,那则可用到Spring支持的profile机制。对应的元素为springProfile。profile的值与springboot中配置文件的spring.profiles.active值进行对照。
<configuration>
<springProfile name="dev">
<!-- configuration to be enabled when the "dev" profile is active -->
<logger name="com.example.demo.controller" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>
</springProfile>
<springProfile name="dev | test">
<!-- configuration to be enabled when the "dev" or "test" profiles are active -->
<logger name="com.example.demo.controller" level="INFO" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>
</springProfile>
<springProfile name="!production">
<!-- configuration to be enabled when the "production" profile is not active -->
<logger name="com.example.demo.controller" level="INFO" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>
</springProfile>
</configuration>
4、Logback其他操作
1、动态切换日志级别
Logback提供有代码方式直接修改日志级别,我们可以获取LoggerContext对象修改日志级别及其他配置。如下是Java代码实现(无配置文件)
package com.xyz;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
public class LogbackTest {
public static void main(String[] args) {
Logger logger = (Logger)LoggerFactory.getLogger(LogbackTest.class);
System.out.println("============使用默认的日志输出级别DEBUG==============");
printLogs(logger);
System.out.println("============动态设置日志输出级别为ERROR==============");
// 返回正在使用的 ILoggerFactory 实例.
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
// 根据包路径或类路径获取日志记录器,"ROOT" 表示根记录器: Logger getLogger(final String name);
loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.toLevel("error"));
printLogs(logger);
System.out.println("============动态设置日志输出级别为WARN==============");
loggerContext.getLogger("com.xyz").setLevel(Level.valueOf("warn"));
printLogs(logger);
}
private static void printLogs(Logger logger) {
logger.error("test error...");
logger.warn("test warn...");
logger.info("test info...");
logger.debug("test debug...");
logger.trace("test trace...");
}
}
============使用默认的日志输出级别DEBUG==============
18:16:51.570 [main] ERROR com.xyz.LogbackTest - test error...
18:16:51.570 [main] WARN com.xyz.LogbackTest - test warn...
18:16:51.570 [main] INFO com.xyz.LogbackTest - test info...
18:16:51.570 [main] DEBUG com.xyz.LogbackTest - test debug...
============动态设置日志输出级别为ERROR==============
18:16:51.570 [main] ERROR com.xyz.LogbackTest - test error...
============动态设置日志输出级别为WARN==============
18:16:51.570 [main] ERROR com.xyz.LogbackTest - test error...
18:16:51.570 [main] WARN com.xyz.LogbackTest - test warn...
2、停止logback-classic
为了释放 logback-classic 资源,停止 logback context 是一个好主意。如果停止,会关闭所有 loggers 关联的 appenders,并有序的停止所有活动线程。
import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
// 返回正在使用的 ILoggerFactory 实例.
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop(); // stop logback-classic
3、模块logback-access
logback-access模块与Servlet容器(如Tomcat和Jetty)集成,以提供HTTP访问日志功能。我们可以使用logback-access模块来替换tomcat的访问日志
-
将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下(可以从本地的maven库直接copy)
<!--logback的基础模块,实际上也可以不导入,因为logback-access中也包含了基础模块包--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <!--实际可以只导入logback-access包即可--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.2.3</version> </dependency>
-
修改$TOMCAT_HOME/conf/server.xml中的Host元素中添加:
<Valve className="ch.qos.logback.access.tomcat.LogbackValve" />
-
logback默认会在$TOMCAT_HOME/conf下查找文件:logback-access.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- always a good activate OnConsoleStatusListener --> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener"/> <property name="LOG_DIR" value="${catalina.base}/logs"/> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/access.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern> </rollingPolicy> <encoder> <!-- 访问日志的格式,官网有固定的枚举值 对应的表达式类型,两种写法都可以--> <!--<pattern>%h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}"</pattern>--> <pattern>combined</pattern> </encoder> </appender> <appender-ref ref="FILE"/> </configuration>
-
结果验证:启动tomcat,刷新页面,查看日志:logs/access.log
127.0.0.1 - - [06/四月/2022:10:43:40 +0800] "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55" 127.0.0.1 - - [06/四月/2022:10:43:40 +0800] "GET / HTTP/1.1" 200 - "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55"
-
官方配置:https://logback.qos.ch/access.html#configuration
5、MDC分布式应用追踪请求
此方式可以用在任何日志实现框架中使用,如:Logback、log4j2 等
上面所讲的日志都是在单个应用系统下记录日志的。一旦进入分布式系统,很可能就会出现日志错乱,对日志追踪和排查造成难题。如果使用像ELK这类框架将日志进行归集统一处理,也需要一个标识,来记录日志的整个请求处理过程。
Slf4j提供了MDC(Mapped Diagnostic Contexts诊断上下文映射),可以让开发人员在诊断上下文中放置信息。通过ThreadLocal实现了线程与线程之间的数据隔离。在输出时可以通过标识符%X{key}来输出MDC中设置的内容。【此方式可以用在任何日志实现框架中使用,如:Logback、log4j2 …】
分布式应用追踪请求实现思路如下:
Web拦截器增加唯一ID »» 增加ID到MDC中 »» 调用其他服务时ID作为Header参数 »» 输出日志添加ID »» 请求结束,清除ID »» 根据相同ID排查日志
下面来看一下具体的实现代码。这里使用的是SpringBoot + Filter,也可以使用HandlerInterceptor拦截器(效果一样)
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
import org.slf4j.MDC;
public class CorrelationIdLoggingFilter implements Filter {
private static final String CORRELATION_ID = "correlation_id";
private static final String UU_ID = "uu_id";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String correlationId = req.getHeader(CORRELATION_ID);
MDC.put(CORRELATION_ID, correlationId);
String uuId = UUID.randomUUID().toString();
MDC.put(UU_ID, uuId);
chain.doFilter(request, response);
MDC.remove(CORRELATION_ID);
MDC.remove(UU_ID);
}
}
@Configuration
public class FilterRegistration {
@Bean
public FilterRegistrationBean correlationIdLoggingFilterRegistrationBean() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CorrelationIdLoggingFilter()); // 可以new也可以Bean注入
registration.setName("correlationIdLoggingFilter"); // 设置过滤器名称
registration.addUrlPatterns("/*"); // 拦截路径
registration.setOrder(-1); // 设置执行顺序,数字越低优先级越高
return registration;
}
}
<!--如下是Logback.xml配置文件配置-->
<property name="log_pattern" value="%d{MM-dd HH:mm:ss.SSS} [%X{uu_id}] [%X{correlational_id}] [%5p] [%40.40c{1.}:%3L] - %m%n"/>
<!--如下是SpringBoot全局配置文件配置-->
logging.pattern.console=%d{MM-dd HH:mm:ss.SSS} [%X{uu_id}] [%X{correlation_id}] [%5p] [%40.40c{1.}:%3L] - %m%n
启动后测试可以看出:启动时候没有correlation_id与uu_id值,当有请求进来才会生成:
curl -H 'Content-Type:application/json' -H 'correlation_id:xxxxx-yyyyy-zzzzz' http://localhost:8080/logback/logs
04-15 12:16:29.740 [] [] [ INFO] [ o.s.b.w.e.t.TomcatWebServer:220] - Tomcat started on port(s): 8080 (http) with context path ''
04-15 12:16:29.750 [] [] [ INFO] [ c.x.XyzApplication: 61] - Started XyzApplication in 1.504 seconds (JVM running for 2.672)
04-15 12:16:51.659 [] [] [ INFO] [ o.a.c.c.C.[.[.[/]:173] - Initializing Spring DispatcherServlet 'dispatcherServlet'
04-15 12:16:51.659 [] [] [ INFO] [ o.s.w.s.DispatcherServlet:525] - Initializing Servlet 'dispatcherServlet'
04-15 12:16:51.660 [] [] [ INFO] [ o.s.w.s.DispatcherServlet:547] - Completed initialization in 1 ms
04-15 12:16:51.678 [e91e1660-69d2-4fd8-bdcb-304c0cd89c70] [xxxxx-yyyyy-zzzzz] [ERROR] [ c.x.Log4j2Controller: 24] - hello, I am error!
04-15 12:16:51.678 [e91e1660-69d2-4fd8-bdcb-304c0cd89c70] [xxxxx-yyyyy-zzzzz] [ WARN] [ c.x.Log4j2Controller: 25] - hello, I am warn!
04-15 12:16:51.680 [e91e1660-69d2-4fd8-bdcb-304c0cd89c70] [xxxxx-yyyyy-zzzzz] [ INFO] [ c.x.Log4j2Controller: 26] - hello, I am info!
6、SpringBoot集成Logback
SpringBoot官网文档:https://docs.spring.io/spring-boot/docs/2.5.0/reference/html/features.html#features.logging
1、SpringBoot日志依赖关系
-
spring-boot-starter是SpringBoot启动器,每一个SpringBoot应用都会依赖到它
-
sping-boot-starter依赖Sping-boot-starter-looging
-
SpringBoot 底层默认使用 SLF4J+ Logback 方式进行日志记录
-
SpringBoot 使用中间替换包把其的日志框架都替换成了SLF4J
-
SpringBoot 能自动适配所有的日志框架,且底层使用SLF4J + Logback方式记录日志,引入其他框架时,只需要把这个框架依赖的日志框架排除掉即可
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 去掉springboot默认配置 --> <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>
2、SpringBoot全局日志设置
SpringBoot官网配置文件详解:https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#application-properties.core
-
日志的级别由低到高分别为:trace(跟踪) < debug(调试) < info(信息) < warn(警告) < error(错误)
-
在配置文件中调整输出的日志级别,日志就只会在这个级别及以后的高级别生效,SpringBoot 默认使用 INFO 级别(Logback 默认是 DEBUG)
-
SpringBoot 的全局配置文件 “application.properties” 或 “application.yml” 中可以修改日志配置项:
# 默认名logback-spring.xml,如果要设置其他名称则需要如下配置 # logging.config=classpath:logback-spring.xml # 设置根节点的日志级别输出,root表示整个项目 logging.level.root=INFO # 指定特定包及类的日志输出级别,未指定就按root设置的级别输出,如果root也未指定则按SpringBoot的默认级别info输出 logging.level.org.springframework.web=DEBUG logging.level.org.hibernate=ERROR logging.level.com.xyz=DEBUG # 配置日志输出的文件,这两个选一个配置就可以了,一起配置的话,name的生效.每次启动都是追加日志 # .name=具体文件, 写入指定的日志文件.文件名可以是确切的位置,也可以是相对于当前目录的位置(相对路径为与pom.xml同级) # .path=具体目录, 写入spring.log文件到指定目录.目录名可以是确切的位置,也可以是相对于位置(文件名spring.log无法更改) logging.file.name=logs/spring-boot.log logging.file.path=logs/ # 指定控制台输出的日志格式,例如: %d{yyyy-MM-dd HH:mm:ss} [%X{uu_id}] -- [%thread] %-5level %logger{50} %msg%n # 1、%d 表示日期时间, # 2、%thread 表示线程名, # 3、%‐5level 级别从左显示5个字符宽度 # 4、%logger{50} 表示logger名字最长50个字符,否则按照句点分割。 # 5、%msg 日志消息, # 6、%n 换行符 # 7、%line 显示日志输出位置的行号,方便寻找位置 # 8、%X 特殊占位符,%X{uu_id}是获取uu_id的值,代码中设置uu_id值:org.slf4j.MDC.put("uuid", "xx-yy-zz"); # 配置日志输出格式, .file是配置输出到文件的日志格式, .console是配置输出到控制台的日志格式 logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{50}:%line %msg%n logging.pattern.file=%d{yyyy-MM-dd HH:mm} -- [%thread] %-5level %logger{50} %msg%n # 设置日志记录器组,将相关的记录器组合在一起,然后设置日志记录器组的日志级别为TRACE logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat logging.level.tomcat=TRACE # SpringBoot包括以下预定义的日志记录组,可以开箱即用 # web: org.springframework.core.codec, org.springframework.http, org.springframework.web # sql: org.springframework.jdbc.core, org.hibernate.SQL logging.level.web=INFO logging.level.sql=DEBUG
-
SpringBoot全局配置中的日志配置优先级高于第三方日志框架配置文件(application.properties > logback-spring.xml)
3、SpringBoot官网文档翻译
1、SpringBoot日志生成路径
logging.file.name | logging.file.path | 示例 | 说明 |
---|---|---|---|
(none) | (none) | 只在控制台输出 | |
指定文件名 | (none) | demo.log | 输出到当前项目根路径下的 demo.log 文件中 |
(none) | 指定目录 | logs/log_lzy | 输出到当前项目所在磁盘根路径下的/logs/log_lzy目录中的 spring.log 文件中 |
指定文件名 | 指定目录 | 当两个同时指定时, name会生效。推荐使用logging.file.name设置,因为它可自定义文件名 |
# 配置日志输出的文件,这两个选一个配置就可以了,一起配置的话,name的生效. 每次启动都是追加日志
# .name=具体文件, 写入指定的日志文件.文件名可以是确切的位置,也可以是相对于当前目录的位置(相对路径为与pom.xml同级)
# .path=具体目录, 写入spring.log文件到指定目录.目录名可以是确切的位置,也可以是相对于位置(文件名spring.log无法更改)
logging.file.name=logs/spring-boot.log
logging.file.path=logs/
- 默认情况:日志文件超过10M时,会新建文件进行递增,如logback.log、logback1.log、logback2.log,使用logging.file.max-size更改大小限制
- 默认情况:日志只记录到控制台,不写入日志文件,如果要在控制台输出之外写入到日志文件,则需要设置 .file 或 .path 属性
- 默认情况,logging.file.* 等配置使用的都是RollingFileAppender
日志输出到文件是非常有用的,比如命令行启动一个 jar 包,但是它一执行就自动报错,然后退出了,还来不急看清错误信息,此时可以修改配置把日志输出到指定文件中,再次启动错误信息就会自动存放进去
2、SpringBoot日志级别设置
SpringBoot Log Levels 日志级别官方文档:https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#features.logging.log-levels
-
通过配置
logging.level.<logger name>=<level>
可以设置所有受支持的日志系统的日志级别,其中 level 是 ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF 之一 -
可以使用 logging.level.root 配置根日志记录器:
# 未特别指定的仍然按 Spring Boot 的默认的 logging.level.root 设置输出. logging.level.root=WARN # 根节点日志级别,即整个应用的日志级别设置,默认为 info logging.level.org.springframework.web=DEBUG # 单独某个包的日志级别设置,spring web 包日志输出级别 logging.level.org.hibernate=ERROR # 单独某个包的日志级别设置,hibernate 日志输出级别 logging.level.com.xzy=info # 自己项目中指定包下的日志输出级别
3、SpringBoot日志记录器组
SpringBoot Log Groups 日志记录器组官方文档:https://docs.spring.io/spring-boot/docs/2.5.0/reference/htmlsingle/#features.logging.log-groups
-
将相关的记录器组合在一起,以便可以同时对它们进行配置,这通常是很有用的。Spring Boot 允许在 Spring 环境中定义日志组。例如下面通过将 “tomcat” 组添加到 application.properties 中来定义它,定义后,可以用一行更改组中所有记录器的级别
logging.group.tomcat=org.apache.catalina, org.apache.coyote, org.apache.tomcat logging.level.tomcat=TRACE
-
SpringBoot 包括以下预定义的日志记录组,可以开箱即用:
Name Loggers web org.springframework.core.codec, org.springframework.http, org.springframework.web sql org.springframework.jdbc.core, org.hibernate.SQL logging.level.web=INFO logging.level.sql=DEBUG
4、SpringBoot设置打印SQL
使用Mybatis的时候,SQL语句是DEBUG下才会打印,而这里我们只配置了INFO,所以想要查看SQL语句的话,有以下两种操作:
1、第一种把<root level="info">
改成<root level="DEBUG">
这样就会打印SQL,不过这样日志那边会出现很多其他消息
2、第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常INFO级别:
-
application.properties 全局配置
logging.level.sql=DEBUG logging.level.org.mybatis=debug logging.level.com.xyz.*.dao=debug
-
logback-spring.xml 自定义配置文件配置
<configuration debug="true" scan="true" scanPeriod="60 seconds">
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />
<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
</configuration>
5、JAR启动设置日志输出级别
可以在启动的时候通过命令行参数指定日志输出级别(java -jar xxx.jar --level),相当于 logging.level.root 配置的根日志记录器。
java -jar .java-se-1.0-SNAPSHOT.jar --debug
4、Logback日志配置文件详解
-
可以直接在 SpringBoot 的全局配置文件中修改 SLF4J 的默认配置,也可以使用 SLF4J 实现框架的自己的配置文件。直接放置再类路径下即可
-
官网参考链接,根据日志记录系统的不同,各自的配置文件文件也不同:
Logging System(日志系统) Customization(日志文件名称) Logback logback-spring.xml,logback-spring.groovy,logback.xml or logback.groovy Log4j2 log4j2-spring.xml,log4j2.xml JDK (Java Util Logging) logging.properties -
因为 SpringBoot 底层默认采用 SLF4J+Logback 的日志组合,也就是说如果在 src/main/resources 目录下放置其中任一类型的配置文件,SpringBoot便会自动进行使用。而SpringBoot官方推荐优先使用带有-spring的文件名配置(如有logback-spring.xml,则不会使用logback.xml)。若需要对配置文件名进行修改,或者希望把放到其它目录下,可以在application中通过logging.config属性来指定,如:
logging.config=classpath:dev/logback-spring.xml
-
具体配置文件设置详情可参考上面的教程:【配置文件结构详解】
-
这是转载其他人整理的百分号属性参数说明大全:
******************************************************************************************************************** 参数 说明 举例 输出显示媒介 ******************************************************************************************************************** %c 列出logger名字空间的全称,如果加上{<层数>}, 假设当前logger的命名空间是"a.b.c" 则表示列出从最内层算起的指定层数的名字空间 %c a.b.c %c{2} b.c %20c (若名字空间长度小于20,则左边用空格填充) %-20c (若名字空间长度小于20,则右边用空格填充) %.30c (若名字空间长度超过30,截去多余字符) %20.30c (若名字空间长度小于20,则左边用空格填充; 若名字空间长度超过30,截去多余字符) %-20.30c (若名字空间长度小于20,则右边用空格填充; 若名字空间长度超过30,截去多余字符) ******************************************************************************************************************** %C 列出调用logger的类的全名(包含包路径) 假设当前类是"org.apache.xyz.SomeClass" %C org.apache.xyz.SomeClass %C{1} SomeClass %class ******************************************************************************************************************** %d 显示日志记录时间,{<日期格式>}使用ISO8601定义的日期格式 %d{yyyy/MM/dd HH:mm:ss,SSS} 2005/10/12 22:23:30,117 %d{ABSOLUTE} 22:23:30,117 %d{DATE} 12 Oct 2005 22:23:30,117 %d{ISO8601} 2005-10-12 22:23:30,117 ******************************************************************************************************************** %F 显示调用logger的源文件名 %F MyClass.java ******************************************************************************************************************** %l 显示日志事件的发生位置,包含包路径、方法名、 源文件名,以及在代码中的行数 %l com.a.b.MyClass.main(MyClass.java:168) ******************************************************************************************************************** %L 显示调用logger的代码行 %L 129 %line %line 129 ******************************************************************************************************************** %level 显示该条日志的优先级 %level INFO %p %p INFO ******************************************************************************************************************** %m 显示输出消息 %m This is a message for debug. %message %message This is a message for debug. ******************************************************************************************************************** %M 显示调用logger的方法名 %M main ******************************************************************************************************************** %n 当前平台下的换行符 %n Windows平台下表示rn,UNIX平台下表示n ******************************************************************************************************************** %p 显示该条日志的优先级 %p INFO %level %level INFO ******************************************************************************************************************** %r 显示从程序启动时到记录该条日志时已经经过的毫秒数 %r 1215 ******************************************************************************************************************** %t 输出产生该日志事件的线程名 %t http-nio-8080-exec-10 %thread %thread http-nio-8080-exec-10 ******************************************************************************************************************** %x 按NDC(Nested Diagnostic Context,线程堆栈)顺序输出日志 假设某程序调用顺序是MyApp调用com.foo.Bar %c %x - %m%n MyApp - Call com.foo.Bar. com.foo.Bar - Log in Bar MyApp - Return to MyApp. ******************************************************************************************************************** %X 按MDC(Mapped Diagnostic Context,线程映射表) 输出日志。通常用于多个客户端连接同一台服务器, 方便服务器区分是那个客户端访问留下来的日志。 %X{5} (记录代号为5的客户端的日志) ******************************************************************************************************************** %% 显示一个百分号 %% % ********************************************************************************************************************
5、动态切换日志级别的几种方式
1、logback.xml 配置文件定时监控
修改 logback.xml 配置文件,定时监控配置变化情况。
<?xml version="1.0" encoding="UTF-8"?>
<!-- debug : 开启logback运行日志输出,true为始终输出,false为出错时才输出-->
<!-- scan :开启"热更新"-->
<!-- scanPeriod:"热更新"扫描周期,默认 60 seconds(60秒) 单位有:milliseconds、seconds、munites、hours,默认为milliseconds-->
<configuration scan="true" scanPeriod="60 seconds" debug="false"/>
- 如果是在本地 IDE 编辑器中测试,注意修改的是 classes 编译目录下的 logback.xml 文件,而不是 resources 目录下的源文件
- 鉴于现在都是微服务开发,分布式部署,而且为了方便管理,基本都是打包后部署在一些平台上面。直接修改日志配置文件不太现实
2、LoggerContext 切换日志输出级别
使用Controller接口动态修改日志输出级别,想设置什么级别,只需传参调接口即可,无论是设置root级别,还是指定包或类,不用重启服务能立马生效
package com.example.xyz;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogbackController {
private static final Logger log = (Logger) LoggerFactory.getLogger(LogbackController.class);
/**
* 打印日志测试: http://localhost:8080/logback/logs
*/
@GetMapping("logback/logs")
public void logs() {
log.error("hello, I am error!");
log.warn("hello, I am warn!");
log.info("hello, I am {}!", "info");
log.debug("hello, I am debug!");
log.trace("hello, I am trace!");
}
/**
* logback 动态切换指定包或者类的日志输出级别,立即生效.
* http:localhost:8080/logback/updateLevel
* http:localhost:8080/logback/updateLevel?level=DEBUG
* http:localhost:8080/logback/updateLevel?level=DEBUG&clazz=com.xyz
*
* @param level :日志级别,可选值有:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF,默认为 debug。不区分大小写.
* @param clazz :指定的包或者类路径,为空时默认设置全局(root)日志级别。路径不存在时,照样会设置这个路径,不会影响全局级别,这一点与 log4j2 不同.
* 比如 org.springframework。
* @return map :返回指定路径的当前日志级别,root 表示全局级别
*/
@GetMapping(value = "/logback/updateLevel")
public String setLevel(String level, String clazz) {
try {
// 返回正在使用的 ILoggerFactory 实例.
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
/**
* Logger getLogger(final String name):根据包路径或类路径获取日志记录器,"ROOT" 表示根记录器
* Level toLevel(String sArg):转换为日志级别,如果转换失败,则默认为 debug
* synchronized void setLevel(Level newLevel):为日志记录器设置日志输出级别,立即生效.
*/
if (clazz == null || "".equals(clazz.trim())) {
// 设置根日志记录器对象并设置日志输出级别
loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).setLevel(Level.toLevel(level));
} else {
// 设置指定日志记录器并设置日志输出级别,路径不存在也没有关系,会返回它的日志记录器,然后设置输出级别
loggerContext.getLogger(clazz).setLevel(Level.toLevel(level));
}
} catch (Exception e) {
log.error(e.getMessage(), e);
return "fail";
}
return "success";
}
}
3、SpringBoot Actuator 监控管理日志
使用SpringBoot Actuator监控管理日志的优点:
- 无需编码,只需要引用Spring Boot Actuator监控依赖,然后开启访问端点 /loggers,即可轻松查看日志输出级别,并进行切换
- 解耦日志框架,无论使用的 Logback 还是 Log4j2 ,都能轻松切换
1、引入pring Boot Actuator依赖
<!--监控和管理应用程序-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、开启日志访问端点: /loggers
management:
endpoint:
health:
show-details: ALWAYS # 展示节点的详细信息
endpoints:
web:
exposure:
include: info,health,logfile,loggers # 指定公开的访问端点
3、查看级别
- 发送 GET 请求,获取日志等级:http://localhost:8080/actuator/loggers
- 返回的信息非常详细,包含了 ROOT,以及程序中各个包和类的日志级别
- 其中 configuredLevel 表示配置级别,effectiveLevel 表示有效级别,configuredLevel 可能为 null,因为没有配置。
4、修改日志级别
- 发送 POST 请求,设置指定包或者类(com.xyz)日志输出级别:http://localhost:8080/actuator/loggers/com.xyz
- 发送 POST 请求,设置 root 全局日志输出级别:http://localhost:8080/actuator/loggers/root
- 请求 Body 的内容格式:{“configuredLevel”:“error”}
7、参考文献 & 鸣谢
- Spring Boot 2.x 日志配置 与集成 Logback 日志框架【CSDN 蚩尤后裔】https://blog.csdn.net/wangmx1993328/article/details/81044147
- 万字详解logback日志框架,再没这么全的了【CSDN 程序新视界】https://blog.csdn.net/wo541075754/article/details/109193354
- 学会这些Logback高级知识点,程序日志性能提高几十倍(上)【CSDN 陈皮的JavaLib】https://javalib.blog.csdn.net/article/details/113899664
- java日志(三)–slf4j和logback使用【CSDN:panda-star】https://blog.csdn.net/chinabestchina/article/details/104743907