Logback是SpringBoot内置的日志处理框架,你会发现spring-boot-starter其中包含了spring-boot-starter-logging,该依赖内容就是Spring Boot 默认的日志框架 logback。它比所有现有的日志系统都要快,而且占用空间更小,还提供了在其他日志系统中没有的独特而有用的特性。Springboot 默认可以通过Slf4j + Logback来操作日志,十分便捷且性能很高。
上图中common-logging 和 Slf4j都是外观模式,不提供具体的日志功能实现,当项目中引入各种日志框架的时候,可以使用外观模式快速的切换使用各个日志框架。外观模式不懂得小伙伴可以查看外观模式
logback 的架构
logback的基本架构足够通用,可以应用于不同的环境。目前,logback分为三个模块:logback-core,logback-classic和logback-access。
core模块是其它两个模块的基础,classic模块继承core模块,classic模块相对log4j版本有显著的改进,logback-classic天生实现了SLF4J API,所以你可以在logback和其他日志框架之间自由切换,比如log4j和JDK1.4引入的JUL(java.util.logging)。access模块集成了Servlet容器,用来提供HTTP-access日志功能,一个单独的文档包含访问模块文档。
在本文档的其余部分中,我们将引用logback-classic模块来编写logback。
appender
根据日志程序选择性地启用或禁用日志记录请求的功能只是一部分。Logback允许将日志请求打印到多个目的地。在logback中,输出目的地称为appender。目前,针对控制台、文件、远程套接字服务器、MySQL、PostgreSQL、Oracle和其他数据库、JMS和远程UNIX Syslog守护进程存在附加程序。
一个logger可以附加多个appender。
addAppender方法向给定的logger添加一个appender。对于给定的logger,每个启用的日志请求都将被转发到该logger中的所有appender以及层次结构中更高的appender。换句话说,appender是附加地从日志程序层次结构继承的。例如,如果将控制台appender添加到根logger,那么所有启用的日志请求至少都将打印在控制台上。此外,如果向logger(L)添加了一个文件appender,然后,为 L 和 L 的子节点启用的日志记录请求将打印在文件里和控制台上。通过将logger的additivity flag设置为false,可以覆盖此默认行为,使追加器积累不再是附加的。
encoder
Encoder负责两件事, 一是把事件转换为字节数组, 二是把字节数组写入输出流。在logback 0.9.19版之前没有encoder。在之前的版本里, 多数appender依靠layout来把事件转换成字符串并用java.io.Writer把字符串输出。在之前的版本里, 用户需要在FileAppender里嵌入一个PatternLayout。而从0.9.19版开始, FileAppender和其子类使用encoder, 不接受layout。
1、LayoutWrappingEncoder
直到logback 0.9.19版本,许多输出源依赖于Layout实例控制日志输出格式。因为基于Layout需要大量的代码,因此logback为编码器提供了一种方式与布局相互操作。LayoutWrappingEncoder缩小了encoders和layouts之间的差距,它实现了Encoder接口并且包装了Layout,并把转换日志事件对象的任务委托给Layout。
2、PatternLayoutEncoder
PatternLayout是最常用的布局,logback提供了通用案例:PatternLayoutEncoder,该类对LayoutWrappingEncoder进行了扩展,从Logback 0.9.19版本开始,无论FileAppender及其子类是否配置了PatternLayout,PatternLayoutEncoder都会被使用
PatternLayout
中封装了defaultConverterMap
变量,在日志<pattern>中使用的格式化变量例如:<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level logger_name:%logger{36} - [%tid] - message:%msg%n</pattern>
都在defaultConverterMap中进行默认定义,如果说在日志中想使用自己的变量可以继承PatternLayout然后将自己的变量塞入到defaultConverterMap
中。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package ch.qos.logback.classic;
import ch.qos.logback.classic.pattern.CallerDataConverter;
import ch.qos.logback.classic.pattern.ClassOfCallerConverter;
import ch.qos.logback.classic.pattern.ContextNameConverter;
import ch.qos.logback.classic.pattern.DateConverter;
import ch.qos.logback.classic.pattern.EnsureExceptionHandling;
import ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter;
import ch.qos.logback.classic.pattern.FileOfCallerConverter;
import ch.qos.logback.classic.pattern.LevelConverter;
import ch.qos.logback.classic.pattern.LineOfCallerConverter;
import ch.qos.logback.classic.pattern.LineSeparatorConverter;
import ch.qos.logback.classic.pattern.LocalSequenceNumberConverter;
import ch.qos.logback.classic.pattern.LoggerConverter;
import ch.qos.logback.classic.pattern.MDCConverter;
import ch.qos.logback.classic.pattern.MarkerConverter;
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.pattern.MethodOfCallerConverter;
import ch.qos.logback.classic.pattern.NopThrowableInformationConverter;
import ch.qos.logback.classic.pattern.PropertyConverter;
import ch.qos.logback.classic.pattern.RelativeTimeConverter;
import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter;
import ch.qos.logback.classic.pattern.ThreadConverter;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.pattern.color.HighlightingCompositeConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.pattern.PatternLayoutBase;
import ch.qos.logback.core.pattern.color.BlackCompositeConverter;
import ch.qos.logback.core.pattern.color.BlueCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldBlueCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldCyanCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldGreenCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldMagentaCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldRedCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldWhiteCompositeConverter;
import ch.qos.logback.core.pattern.color.BoldYellowCompositeConverter;
import ch.qos.logback.core.pattern.color.CyanCompositeConverter;
import ch.qos.logback.core.pattern.color.GrayCompositeConverter;
import ch.qos.logback.core.pattern.color.GreenCompositeConverter;
import ch.qos.logback.core.pattern.color.MagentaCompositeConverter;
import ch.qos.logback.core.pattern.color.RedCompositeConverter;
import ch.qos.logback.core.pattern.color.WhiteCompositeConverter;
import ch.qos.logback.core.pattern.color.YellowCompositeConverter;
import ch.qos.logback.core.pattern.parser.Parser;
import java.util.HashMap;
import java.util.Map;
public class PatternLayout extends PatternLayoutBase<ILoggingEvent> {
public static final Map<String, String> defaultConverterMap = new HashMap();
public static final String HEADER_PREFIX = "#logback.classic pattern: ";
public PatternLayout() {
this.postCompileProcessor = new EnsureExceptionHandling();
}
public Map<String, String> getDefaultConverterMap() {
return defaultConverterMap;
}
public String doLayout(ILoggingEvent event) {
return !this.isStarted() ? "" : this.writeLoopOnConverters(event);
}
protected String getPresentationHeaderPrefix() {
return "#logback.classic pattern: ";
}
static {
defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
defaultConverterMap.put("d", DateConverter.class.getName());
defaultConverterMap.put("date", DateConverter.class.getName());
defaultConverterMap.put("r", RelativeTimeConverter.class.getName());
defaultConverterMap.put("relative", RelativeTimeConverter.class.getName());
defaultConverterMap.put("level", LevelConverter.class.getName());
defaultConverterMap.put("le", LevelConverter.class.getName());
defaultConverterMap.put("p", LevelConverter.class.getName());
defaultConverterMap.put("t", ThreadConverter.class.getName());
// thread
defaultConverterMap.put("thread", ThreadConverter.class.getName());
defaultConverterMap.put("lo", LoggerConverter.class.getName());
defaultConverterMap.put("logger", LoggerConverter.class.getName());
defaultConverterMap.put("c", LoggerConverter.class.getName());
defaultConverterMap.put("m", MessageConverter.class.getName());
defaultConverterMap.put("msg", MessageConverter.class.getName());
defaultConverterMap.put("message", MessageConverter.class.getName());
defaultConverterMap.put("C", ClassOfCallerConverter.class.getName());
defaultConverterMap.put("class", ClassOfCallerConverter.class.getName());
defaultConverterMap.put("M", MethodOfCallerConverter.class.getName());
defaultConverterMap.put("method", MethodOfCallerConverter.class.getName());
defaultConverterMap.put("L", LineOfCallerConverter.class.getName());
defaultConverterMap.put("line", LineOfCallerConverter.class.getName());
defaultConverterMap.put("F", FileOfCallerConverter.class.getName());
defaultConverterMap.put("file", FileOfCallerConverter.class.getName());
defaultConverterMap.put("X", MDCConverter.class.getName());
defaultConverterMap.put("mdc", MDCConverter.class.getName());
defaultConverterMap.put("ex", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("exception", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("rEx", RootCauseFirstThrowableProxyConverter.class.getName());
defaultConverterMap.put("rootException", RootCauseFirstThrowableProxyConverter.class.getName());
defaultConverterMap.put("throwable", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("xEx", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("xException", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("xThrowable", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("nopex", NopThrowableInformationConverter.class.getName());
defaultConverterMap.put("nopexception", NopThrowableInformationConverter.class.getName());
defaultConverterMap.put("cn", ContextNameConverter.class.getName());
defaultConverterMap.put("contextName", ContextNameConverter.class.getName());
defaultConverterMap.put("caller", CallerDataConverter.class.getName());
defaultConverterMap.put("marker", MarkerConverter.class.getName());
defaultConverterMap.put("property", PropertyConverter.class.getName());
defaultConverterMap.put("n", LineSeparatorConverter.class.getName());
defaultConverterMap.put("black", BlackCompositeConverter.class.getName());
defaultConverterMap.put("red", RedCompositeConverter.class.getName());
defaultConverterMap.put("green", GreenCompositeConverter.class.getName());
defaultConverterMap.put("yellow", YellowCompositeConverter.class.getName());
defaultConverterMap.put("blue", BlueCompositeConverter.class.getName());
defaultConverterMap.put("magenta", MagentaCompositeConverter.class.getName());
defaultConverterMap.put("cyan", CyanCompositeConverter.class.getName());
defaultConverterMap.put("white", WhiteCompositeConverter.class.getName());
defaultConverterMap.put("gray", GrayCompositeConverter.class.getName());
defaultConverterMap.put("boldRed", BoldRedCompositeConverter.class.getName());
defaultConverterMap.put("boldGreen", BoldGreenCompositeConverter.class.getName());
defaultConverterMap.put("boldYellow", BoldYellowCompositeConverter.class.getName());
defaultConverterMap.put("boldBlue", BoldBlueCompositeConverter.class.getName());
defaultConverterMap.put("boldMagenta", BoldMagentaCompositeConverter.class.getName());
defaultConverterMap.put("boldCyan", BoldCyanCompositeConverter.class.getName());
defaultConverterMap.put("boldWhite", BoldWhiteCompositeConverter.class.getName());
defaultConverterMap.put("highlight", HighlightingCompositeConverter.class.getName());
defaultConverterMap.put("lsn", LocalSequenceNumberConverter.class.getName());
}
}
layout
Layout只负责把事件转换为字符串。此外, 因为layout不能控制事件何时被写出, 所以不能成批地聚集事件。相比之下, encoder不但可以完全控制待写出的字节的格式, 而且可以控制字节何时及是否被写出。
目前, PatternLayoutEncoder是唯一有用的encoder, 它基本上是封装了PatternLayout, 让PatternLayout负责大多数工作。因此, 似乎encoder并没有带来多少好东西, 反而只有不需要的复杂性。然而, 我们希望当新的、强大的encoder到来时, 这种印象会改变。
Springboot 日志配置
配置 | 解释 | 实例 |
logging.config | 通过在类路径的根目录中或在Spring Environment属性logging.config指定的位置中提供适当的配置文件来进一步自定义各种日志记录系统 | logging.config=classpath:logback-spring.xml |
指定logback配置文件的路径logging.level | 作为package(包)的前缀来设置日志级别 | logging.level.hpu.edu.xingsi.mapper=dubug 指定hpu.edu.xing.mapper包下的所有类的日志级别为dubug |
logging.file.name | 指定输出的日志文件的文件名 logging.file.name=xingsi.log | logging.file.path=指定输出的日志文件的文件路径 logging.file.path=E:/xingsi |
logging.exception-conversion-word | 定义当日志发生异常时的转换字 logging.exception-conversion-word=%wEx logging.file.max-size 默认当日志文件最大为10M,可以通过logging.file.max-size属性来调整大小 | logging.file.max-size=20MB |
logging.file.total-size-cap | 日志档案的总大小可以通过设置logging.file.total-size-cap的值来限定 | logging.file.total-size-cap=1MB |
logging.file.clean-history-on-start | 当日志归档文件的总大小超过该阈值时,将删除备份。要在应用程序启动时强制进行日志归档清理,请使用logging.file.clean-history-on-start属性。 | logging.file.clean-history-on-start=ture |
logging.pattern.console | 指定日志输出到控制台的格式 | logging.pattern.console=“%d{yyyy-MM-dd HH:mm:ss.SSS} >>> [%thread] >>> %-5level >>> %logger{50} >>> %msg%n” |
默认情况下Spring Boot将日志输出到控制台,不会写到日志文件。如果要编写除控制台输出之外的日志文件,则需在application.properties中设置logging.path或logging.file属性。二者不能同时使用,如若同时使用,则只有logging.file生效。不过现在这两个配置已经被弃用。非常有趣的是 logging.file.name, logging.file.path 这两个spring 官方推荐的最新配置也不能同时生效,看似前者指定文件名称,后者指定文件路径,实则不然。
- logging.file.name 可以指定路径和log文件的名字。
- logging.file.path 只可以只当log的路径, 不能指定log的名字, 使用缺省值。* spring.log。
- 二者只可以存在一个。
如果Springboot默认提供的配置满足不了需求,可以自定义logback的配置。
Logback 通用配置
<?xml version="1.0" encoding="UTF-8"?>
<!-- configuration 标签属性
scan:
当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:
设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:
当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"></include>
<!--
有两个属性,name和value;其中name的值是变量的名称,value的值时变量定义的值。
通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。
-->
<property name="appName" value="exporter" />
<springProperty scope="context" name="loggerHome" source="logger.log-home" defaultValue="/var/logs/"/>
<!--
两个属性 key:标识此<timestamp> 的名字;datePattern:设置将当前时间(解析配置文件的时间)转换为字符串的模式,
遵循java.txt.SimpleDateFormat的格式。
-->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<contextName>${appName}</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<!-- <encoder>-->
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<!-- </encoder>-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level logger_name:%logger{36} - [%tid] - message:%msg%n</pattern>
</layout>
</encoder>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- <encoder>-->
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<!-- </encoder>-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level logger_name:%logger{36} - [%tid] - message:%msg%n</pattern>
</layout>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- <encoder>-->
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<!-- <charset>utf-8</charset>-->
<!-- </encoder>-->
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level logger_name:%logger{36} - [%tid] - message:%msg%n</pattern>
</layout>
</encoder>
<file>logs/allens-exporter.log</file>
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">-->
<!-- <fileNamePattern>logs/allens-exporter-compress.log.%i</fileNamePattern>-->
<!-- <maxHistory>15</maxHistory>-->
<!-- </rollingPolicy>-->
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/allens-exporter/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<MaxHistory>30</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>100MB</MaxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
<!-- <MaxFileSize>100MB</MaxFileSize>-->
<!-- </triggeringPolicy>-->
</appender>
<appender name="FILE-OTHER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<file>logs/allens-exporter-other.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/allens-exporter-other/%d{yyyy-MM-dd}-OTHER.%i.log</fileNamePattern>
<MaxHistory>30</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>100MB</MaxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- <root level="INFO">-->
<!-- <!– <appender-ref ref="CONSOLE" /> –>-->
<!-- <appender-ref ref="FILE" />-->
<!-- <appender-ref ref="STDOUT" />-->
<!-- </root>-->
<!-- com.allens.export 包下只输出 ERROR日志 -->
<!-- <logger name="com.allens.export" level="ERROR"/>-->
<!-- 另起一个文件做日志 -->
<!-- <logger name="com.allens.export.controller" level="DEBUG" additivity="false">-->
<!-- <appender-ref ref="FILE-OTHER" />-->
<!-- </logger>-->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="com.allens.export" level="ERROR"/>
<!--
没有设置addtivity,默认为true,将此logger的打印信息向上级传递
-->
</springProfile>
</configuration>
<springProfile>
Springboot对Logback做的适配,可以在logback.xml中引用springboot配置变量,以及不同缓存使用不同的Appender进行日志输出。
<springProfile name="local">
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
<logger name="com.allens.export" level="ERROR"/>
<!--
没有设置addtivity,默认为true,将此logger的打印信息向上级传递
-->
</springProfile>
上述代码就是只会在local环境下生效,在local本地环境下,会使用STDOUT APPENDER 将日志输出到控制台中。注意springboot使用logback.xml要从命名为logback-spring.xml
不然springboot无法识别,然后在application.yml中加入配置:
logging:
config: classpath:logback-spring.xml
<logger>元素
<logger>元素只接受一个必需的name属性,一个可选的level属性和一个可选的additivity属性,允许值为true或false。 level属性的值允许一个不区分大小写的字符串值TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF。特殊于大小写不敏感的值INHERITED或其同义词NULL将强制记录器的级别从层次结构中的较高级别继承,<logger>元素可以包含零个或多个<appender-ref>元素; 这样引用的每个appender都被添加到指定的logger中,(注:additivity属性下面详说),logger元素级别具有继承性。
<root>元素
<root>元素配置根记录器。 它支持单个属性,即level属性。 它不允许任何其他属性,因为additivity标志不适用于根记录器。 此外,由于根记录器已被命名为“ROOT”,因此它也不允许使用name属性。 level属性的值可以是不区分大小写的字符串TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一<root>元素可以包含零个或多个<appender-ref>元素; 这样引用的每个appender都被添加到根记录器中(注:additivity属性下面详说)。
<appender>
appender使用<appender>元素配置,该元素采用两个必需属性name和class。 name属性指定appender的名称,而class属性指定要实例化的appender类的完全限定名称。 <appender>元素可以包含零个或一个<layout>元素,零个或多个<encoder>元素以及零个或多个<filter>元素,下图说明了常见的结构:
重要:在logback中,输出目标称为appender,addAppender方法将appender添加到给定的记录器logger。给定记录器的每个启用的日志记录请求都将转发到该记录器中的所有appender以及层次结构中较高的appender。换句话说,appender是从记录器层次结构中附加地继承的。例如,如果将控制台appender添加到根记录器,则所有启用的日志记录请求将至少在控制台上打印。如果另外将文件追加器添加到记录器(例如L),则对L和L的子项启用的记录请求将打印在文件和控制台上。通过将记录器的additivity标志设置为false,可以覆盖此默认行为,以便不再添加appender累积。
Appender是一个接口,它有许多子接口和实现类,具体如下图所示:
其中最重要的两个Appender为:ConsoleAppender 、RollingFileAppender。
ConsoleAppender
ConsoleAppender,如名称所示,将日志输出到控制台上。
RollingFileAppender
RollingFileAppender,是FileAppender的一个子类,扩展了FileAppender,具有翻转日志文件的功能。 例如,RollingFileAppender 可以记录到名为log.txt文件的文件,并且一旦满足某个条件,就将其日志记录目标更改为另一个文件。
有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即 RollingPolicy 负责执行翻转所需的操作。 RollingFileAppender的第二个子组件,即 TriggeringPolicy 将确定是否以及何时发生翻转。 因此,RollingPolicy 负责什么和TriggeringPolicy 负责什么时候。
作为任何用途,RollingFileAppender 必须同时设置 RollingPolicy 和 TriggeringPolicy。 但是,如果其 RollingPolicy 也实现了TriggeringPolicy 接口,则只需要显式指定前者。
滚动策略
TimeBasedRollingPolicy:可能是最受欢迎的滚动策略。 它根据时间定义翻转策略,例如按天或按月。 TimeBasedRollingPolicy承担滚动和触发所述翻转的责任。 实际上,TimeBasedTriggeringPolicy实现了RollingPolicy和TriggeringPolicy接口。
SizeAndTimeBasedRollingPolicy:有时您可能希望按日期归档文件,但同时限制每个日志文件的大小,特别是如果后处理工具对日志文件施加大小限制。 为了满足此要求,logback 提供了 SizeAndTimeBasedRollingPolicy ,它是TimeBasedRollingPolicy的一个子类,实现了基于时间和日志文件大小的翻滚策略。
<encoder>元素
encoder中最重要就是pattern属性,它负责控制输出日志的格式,这里给出一个我自己写的示例:
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n</pattern>
其中:%d{yyyy-MM-dd HH:mm:ss.SSS}:日期
- %-5level:日志级别
- %highlight():颜色,info为蓝色,warn为浅红,error为加粗红,debug为黑色
- %thread:打印日志的线程
- %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符
- %logger:日志输出的类名
- %-40.40():如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符
- %cyan:颜色
- %msg:日志输出内容
- %n:换行符
<filter>元素
filter中最重要的两个过滤器为:LevelFilter、ThresholdFilter。
LevelFilter 根据精确的级别匹配过滤事件。 如果事件的级别等于配置的级别,则筛选器接受或拒绝该事件,具体取决于onMatch和onMismatch属性的配置。 例如下面配置将只打印INFO级别的日志,其余的全部禁止打印输出。Filter 可以做在线日志收集器,可以拦截所有的日志发送到mq或本地LinkBlockingQueue中,异步消费发送websocket到前端接收进行展示。
日志脱敏也可以使用filter进行过滤关键字,只不过要注意性能问题。
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
项目中使用日志时的注意事项
这里再说下log日志输出代码,一般有人可能在代码中使用如下方式输出:
Object entry = new SomeObject();
logger.debug("The entry is " + entry);
上面看起来没什么问题,但是会存在构造消息参数的成本,即将entry转换成字符串相加。并且无论是否记录消息,都是如此,即:那怕日志级别为INFO,也会执行括号里面的操作,但是日志不会输出,下面是优化后的写法:
if(logger.isDebugEnabled()) {
Object entry = new SomeObject();
logger.debug("The entry is " + entry);
}
上面的写法,首先对设置的日志级别进行了判断,如果为debug模式,才进行参数的构造,对第一种写法进行了改善。不过还有最好的写法,使用占位符:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有在评估是否记录之后,并且只有在决策是肯定的情况下,记录器实现才会格式化消息并将“{}”对替换为条目的字符串值。 换句话说,当禁用日志语句时,此表单不会产生参数构造的成本。
logback作者进行测试得出:第一种和第三种写法将产生完全相同的输出。 但是,在禁用日志记录语句的情况下,第三个变体将比第一个变体优于至少30倍。
如果有多个参数,写法如下:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
如果需要传递三个或更多参数,则还可以使用Object []变体:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
记录日志的时候我们可能需要在文件中记录下异常的堆栈信息,经过测试,logger.error(e)
不会打印出堆栈信息,正确的写法是:
logger.error("程序异常, 详细信息:{}", e.getLocalizedMessage() , e);