# Logback ## Introduce `logback-classic`模块需要classpath下存在`slf4j-api.jar`, `logback-core.jar`, `logback-classic.jar`。 logback使用示例如下: ```java package chapters.introduction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld1 { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1"); logger.debug("Hello world."); } } ``` `HelloWorld1`类定义在`chapters.introduction`包下,其引入了slf4j中的`Logger`和`LoggerFactory`类。 在上述示例中,并没有引入任何logback的类,在使用日志的大多数场景中,只需要引入slf4j的api类即可。 ## Logback Architecture logback目前结构分为3个模块,`logback-core`,`logback-classic`,`logback-access`。 `logback-core`是其他两个模块的基础,`classic`模块拓展了`core`模块。`classic`模块实现了slf4j api,因而在使用slf4j时可以在logback和其他日志系统之间轻松切换。而`access`模块和servlet容器做了集成,可以通过http对日志进行访问。 ### Logger, Appender, Layout logback基于3个主要的类构成:`Logger`, `Appender`, `Layout`。`Logger`类属于logback-classic模块,Appender和Layout类则是属于logback-core模块。 #### Log Context 在logback-classic中,每个logger都关联了一个`Log Context`,log Context负责创建logger,并且log context将创建的logger按照树状结构排列。 Logger都拥有名称,其名称大小写敏感,并遵循分层的命名规则: > 如果一个logger的名称,其名称加上`.`后形成的字符串,为另一个logger的前缀,那么可称后一个logger是前一个logger的后代。如果一个logger和后代logger之间没有其他的logger,那么可以称前一个logger是后一个logger的parent。 例如,名称为`"com.foo"`的logger是名称为`"com.foo.Bar"`的parent。 root logger是位于最顶层的logger,其可以通过如下方式进行获取: ```java Logger rootLogger = LoggerFactory.​getLogger(org.slf4j.Logger.​ROOT_LOGGER_NAME); ``` 其他所有的logger也可以通过`getLogger`方法进行获取,该方法接收logger name来作为参数,`Logger`接口具有如下方法: ```java package org.slf4j; public interface Logger { // Printing methods: public void trace(String message); public void debug(String message); public void info(String message); public void warn(String message); public void error(String message); } ``` #### 有效级别 logger都会被指定一个log level,level可以是`TRACE, DEBUG, INFO, WARN, ERROR`中的一个,其定义在`ch.qos.logback.classic.Level`类中。如果指定logger没有被指定level,那么其会从`离其最近并且被指定level的先祖logger`中继承level。 为了保证所有logger都可以继承level,root logger一定会被指定log level。默认情况下,level为`debug`。 > log level顺序为: > > `TRACE < DEBUG < INFO < WARN < ERROR` #### 获取Logger 在通过`LoggerFactory.getLogger`获取logger对象时,如果向方法传入相同的名称,那么会获得相同的logger对象。 通常,应在每个class中指定一个logger,logger name为class的全类名。 #### Appender 在logback中,允许将日志打印到多个目的地,在logback中,目的地被称为`Appender`。目前,目的地可以是console、文件、remote socket server、数据库、JMS等。 对每个logger,都可以关联不止一个Appender。 `addAppender`将会将Appender关联到指定的logger。每个被允许打印的log请求都会被发送到该logger关联的所有Appender中,并且log请求还会被发送到树结构更高的Appender中。在logger结构中,Appender是增量继承的,后代logger会继承先祖logger的Appender。 如果为root logger指定一个console appender,并且为L指定一个file appender,那么对于L和L的后代logger打印日志的请求不仅会被发送到console,还会被打印到文件中。 > 可以手动指定logger L增量继承appender的标志为false,那么L和其所有后代logger都不会继承L先祖logger的appender。 > > 默认情况下,appender是否增量继承的标志,默认值为true #### Layout 如果想要指定日志打印的格式,可以将Layout对象和logger相关联。`PatternLayout`可以通过c语言`printf`的格式来指定打印格式。 `PatternLayout`中,若指定conversion pattern为`%-4relative [%thread] %-5level %logger{32} - %msg%n`,其会按如下格式打印消息: ``` 176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world. ``` #### 参数化打印 可以通过如下形式来进行参数化打印 ```java Object entry = new SomeObject(); logger.debug("The entry is {}.", entry); ``` 只有当前消息被评估应该打印时,才会通过消息模板来构造消息,将`{}`替换为entry的实际值。 ## Logback Configuration ### logback配置顺序 1. 如果`logback.configurationFile`系统变量设置 2. 如果1步骤失败,会尝试在classpath中寻找`logback-test.xml` 3. 如果2步骤失败,会尝试在classpath中寻找`logback.xml` > #### 通过命令行System Properties设置logback配置文件位置 > 可以通过向命令行传递`-Dlogback.configurationFile=/path/to/config.xml`来设置logback配置文件的位置 ### 通过xml形式配置logback ```xml %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n ``` ### 自动加载配置文件更新 logback支持对配置文件的变动进行扫描,并且对扫描到的变动进行自动配置。为了令logback支持配置自动更新,需要为`configuration`元素配置scan属性,示例如下所示: ```xml ... ``` 在开启配置自动更新时,默认会每间隔`1 minute`就扫描配置变动。可以通过`scanPeriod`属性自定义扫描周期,周期单位可以是`milliseconds, seconds, minutes, hours`。示例如下所示: ```xml ... ``` > 如果在没有为scanPerod属性值指定单位时,默认单位为ms ### xml配置文件语法 一个基础的xml配置文件可以包含如下结构: 1. 一个``元素 1. 0或多个``元素 2. 0或多个``元素 3. 最多一个``元素 #### `` ``元素用于配置logger,其接收一个必填的`name`属性,一个可选的`level`属性,一个可选的`additivity`属性。 - `additivity`属性的值可以为true或者false。 - `level`属性的值可以为`TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF`,是大小写不敏感的。 - 如果想要从上级继承level,可以将level属性的值设置为`INHERITED`或是`NULL` `logger`元素中可以包含0个或多个``元素,这样每个引用的appender都会被加入到logger中。 #### `` `root`元素用于配置root logger,其只支持一个属性`level`。由于root元素已经被命名为`ROOT`,故而也不允许指定`name`属性。 ``元素的`level`属性只能被赋值为`TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF`,不能被赋值为`INHERITED`或是`NULL`. 和logger元素一样,root元素中也能包含0个或多个``元素。 ```xml %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n ``` #### `Appender` ``元素用于配置Appender,其接收两个必填属性:`name`和`class`。其中,`name`属性指定了appender的名称,而`class`属性则是指定了appender Class的全类名。 `appender`元素可以包含0或多个``元素,0或多个``元素,0或多个``元素。除了可以包含上述公共元素外,还`appender`元素还可以包含任意数量javaBean属性相关的元素, #### `Layout` ``元素接收一个必填`class`属性,值为Layout类的全类名。和appender元素一样,layout也可以包含javaBean属性相关的元素。如果layout class为`PatternLayout`,那么class属性可以省略。 #### `encoder` ``元素接收一个必填的class属性,值为Encoder的全类名。如果encoder类为`PatternLayoutEncoder`,那么class属性可以省略。 当想要向多个appender中输出日志时,只需要在logger中定义多个appender即可,示例如下 ```xml myApp.log %date %level [%thread] %logger{10} [%file:%line] -%kvp- %msg%n %kvp %msg%n ``` 上述配置中,定义了两个appender,`FILE`和`STDOUT`。 `FILE` appender将日志打印到`myApp.log`文件中。`STDOUT` appender则是将日志打印到控制台。 root logger通过``标签来引用appender,每个appender都有其自己的encoder,encoder通常在appender之间并不共享。同样的,layout也不会在多个appender之间共享。 > 由于logger不仅会将日志发送到其自身的logger,并且还会将日志发送给其先祖logger的appender,故而将相同的appender共享给拥有上下级关系的logger将会导致日志的重复打印。 可以通过设置`additivity`属性来设置后代logger不继承先祖logger的appender,配置示例如下所示: ```xml foo.log %date %level [%thread] %logger{10} [%file : %line] -%kvp- %msg%n %msg%n ``` ### 变量替换 可以以`${VAR_NAME}`的形式来使用变量替换,且`HOSTNAME`和`CONTEXT_NAME`变量是默认定义的。 如下示例展示了如何定义变量: ```xml ${USER_HOME}/myApp.log %kvp %msg%n ``` 变量除了可以在配置文件中定义,还可以通过System Properties进行传递, ```shell java -DUSER_HOME="/home/sebastien" MyApp2 ``` ```xml ${USER_HOME}/myApp.log %kvp %msg%n ``` 当存在多个变量时,可以单独为变量定义一个文件: ```xml ${USER_HOME}/myApp.log %kvp %msg%n ``` ```properties USER_HOME=/home/sebastien ``` 除了通过``元素的file属性指定变量文件位置外,还可以通过`variable`元素的resource属性指定classpath下的文件路径: ```xml ${USER_HOME}/myApp.log %kvp %msg%n ``` #### 变量作用域 一个变量可以被定义在如下作用域: - LOCAL SCOPE: 具有local作用域的变量,生命周期为从其文件定义该变量的位置开始,一直到该配置文件的末尾 - CONTEXT SCOPE: 具有context作用域的变量,在context存在期间一直存在,直到context执行clear操作 - SYSTEM SCOPE: 具有system作用域的变量,将会添加到jvm的system properties中,生命周期和jvm相同,直到该变量被清空 在变量替换时,变量首先从local scope中查找,其次到context scope中找,再其次到system scope中找,最后到os环境变量中找。 ``元素中可以含有`scope`属性,其值可以为`local`,`context`或`system`。如果没有指定scope属性,默认是`local`。 ```xml /opt/${nodeId}/myApp.log %kvp %msg%n ``` 在上述示例中,尽管`nodeId`变量被定义为context scope,其在每个logging event中都可以使用。 #### 为变量赋值默认值 可以按`"${aName:-golden}"`的方式为指定返回的默认值,当aName变量未定义时,返回值为`golden`。 #### 变量嵌套 在定义变量时,支持变量的嵌套: ```properties USER_HOME=/home/sebastien fileName=myApp.log destination=${USER_HOME}/${fileName} ``` 并且,表达式也支持嵌套变量: `"${${userid}.password}"`表达式在`userid`变量为`asahi`的情况下,表示`asahi.password`变量的值 在为变量指定默认值时,默认值也可以通过变量来指定,例如`${id:-${userid}}`,在`id`变量未定义时,会返回`userid`变量的值。 ### 条件处理配置文件 logback配置文件支持通过``,``,``元素来条件指定配置。条件处理需要`janino`库。 条件处理语法如下: ```xml ... ... ... ``` 条件为java表达式,且只允许访问system properties和context properties。`proerpty()`方法或`p()`方法将会返回变量的值,例如`p('k')`或`property('k')`将会返回变量`k`的值。如果`k`变量未定义,将会返回空字符串。 `isDefined`方法则是可以用来检查指定变量是否被定义。并且,可以通过`isNull`方法来检查指定变量的值是否为空。 ```xml %d %-5level %logger{35} -%kvp- %msg %n ${randomOutputDir}/conditional.log %d %-5level %logger{35} -%kvp- %msg %n ``` ## Appender logback把日志事件的写操作委托给了Appender组件。Appender必须实现`ch.qos.logback.core.Appender`接口,接口主要方法如下: ```java package ch.qos.logback.core; import ch.qos.logback.core.spi.ContextAware; import ch.qos.logback.core.spi.FilterAttachable; import ch.qos.logback.core.spi.LifeCycle; public interface Appender extends LifeCycle, ContextAware, FilterAttachable { public String getName(); public void setName(String name); void doAppend(E event); } ``` 其中,doAppend方法负责将日志输出到对应的输出设备。 appender是命名实体,故而其可以通过name来进行引用。Appender实现了FilterAttachable接口,故而可以将一个或多个filter关联到appender。 appender负责将日志事件打印到输出设备,但是,appender可以将日志的格式化操作委托给`Layout`或`Encoder`对象。每个layout或encoder都只会关联一个appender,一些logger拥有内置的固定format格式,对于用于内置固定format的appender,其不需要encoder或layout。 > 例如,SocketAppender并没有encoder或layout,其只是将日志事件序列化,并且通过网络传输序列化后的数据 ### AppenderBase `ch.qos.logback.core.AppenderBase`是一个实现了Appender接口的抽象类,其针对appender中的部分接口提供了实现。logback中内置的所有apender实现都实现了该抽象类。 `AppenderBase`类实现了doAppend方法,并留下了`append`方法供实现类进行实现。AppenderBase类中,doAppend方法被synchronized修饰,多线程环境下doAppend方法的调用是阻塞的。 ### Logback Core logback core是其他logback模块的基础。其内置了如下开箱即用的Appender。 #### OutputStreamAppender `OutputStreamAppender`会将日志事件追加到`java.io.OutputStream`中,该类为其他基于`OutputSteamAppender`的appender提供了基础。 ConsoleAppender和FileAppender都继承了OutputStreamAppender。 #### ConsoleAppender ConsoleAppender会将日志追加到console,即`System.out`或`System.err`中,前者是默认的目标设备。`ConsoleAppender`通过用户指定的`encoder`来对日志事件进行格式化。 ConsoleAppender拥有如下property: - encoder : `Encoder`类型 - target : 字符串类型,值为`System.out`或`System.err` - withJansi : boolean类型,默认为false、 如下为ConsoleAppender的使用示例: ```xml %-4relative [%thread] %-5level %logger{35} -%kvp- %msg %n ``` #### FileAppender `FileAppender`是`OutputStreamAppender`的子类,将日志事件打印到文件中。目标文件通过`File`选项来指定,如果指定文件已经存在,是否清空文件内容或将日志追加到文件末尾,取决于`append`属性。 FileAppender拥有如下property: - append: boolean类型,当文件已经存在时,如果设置为true,日志追加到文件末尾,如果日志设置为false,已存在文件的内容将会被清空。默认该属性被设置为true - encoder:Encoder类型 - file:字符串类型,为写入文件的文件名,如果文件不存在,那么文件会被创建。如果该路径值中存在不存在的目录,FileAppender会自动创建目录。 - bufferSize:FileSize类型,当immediateFlush属性被设置为false时,可以通过bufferSize选项将会设置output buffer的大小。bufferSize的默认值为8192。在定义FileSize类型时,可以按`KB, MB, GB`来指定,只需要以`5MB`形式指定即可。在没有指定后缀单位时,默认为字节 - prudent: 当prudent属性设置为true时,会将`append`属性设置为true。prudent依赖与排他的file lock,在开启prudent后,写日志开销通常是prudent关闭开销的3倍。 > ##### Immediate flush > 默认情况下,每个log事件都会立即刷新到outputstream中,这样可以避免日志的丢失(如果日志刷新存在延迟,那么应用在未刷新的情况下当即可能会使缓存中的日志丢失)。 > > 当时,如果要提高吞吐量,可以将`immediateFlush`property设置为false。 配置示例如下所示: ```xml testFile.log true true %-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n ``` ##### `` 对于短期应用,可能会期望在每次程序运行时,都创建唯一的日志文件。可以通过``元素来实现该需求,示例如下: ```xml log-${bySecond}.txt %logger{35} -%kvp- %msg%n ``` timestamp元素接收两个必填属性,`key`和`datePattern`,并接收一个非必填的属性`timeReference`。 - datePattern : 格式和`SimpleDateFormat`相同 - timeReference : 默认情况下,timestamp元素的值为当前配置文件被解析的事件,也可以将其设为`contextBirth`,即context创建时间 #### RollingFileAppender `RollingFileAppender`继承了FileAppender,并且支持了滚动日志的能力。RollgingAppender拥有如下子组件: - `RollingPolicy`:负责如何执行日志滚动操作 - `TriggeringPolicy`:负责决定是否/何时出发日志滚动 RollingFileAppender同时需要RollingPolicy和TriggerPolicy组件,但是如果RollingPolicy实现了TriggerPolicy接口,那么只需要RollingPolicy即可。 如下是RollingFileAppender的属性: - file: 值为String类型,如果日志只被写到RollingPolicy指定的目标设备,那么file属性可以为空 - append:是否追加 - encoder:同FileOutputStream - rollingPolicy:值为`RollingPolicy`类型 - triggeringPolicy:值为`TriggeringPolicy`类型 - prudent ##### RollingPolicy `RollingPolicy`负责日志滚动过程,其中涉及文件移动和重命名。 RollingPolicy接口展示如下: ```java package ch.qos.logback.core.rolling; import ch.qos.logback.core.FileAppender; import ch.qos.logback.core.spi.LifeCycle; public interface RollingPolicy extends LifeCycle { public void rollover() throws RolloverFailure; public String getActiveFileName(); public CompressionMode getCompressionMode(); public void setParent(FileAppender appender); } ``` 其中,各方法代表含义如下: - rollober: 将当前日志文件归档 - getActiveFileName:计算当前日志文件的文件名(实时日志将会被写入到其中) - getCompressionMode: 决定压缩模式 - setParent:设置关联的FileAppender ##### TimeBasedRollingPolicy TimeBasedRollingPolicy是最常用的rollingPolicy,其基于时间来执行滚动操作,可以通过其设置按天或按月滚动。 TimeBasedRollingPolicy同时实现了RollingPolicy和TriggeringPolicy接口。 TimeBasedRollingPolicy接收一个必填的`fileNamePattern`参数,并且拥有可选填参数: - `fileNamePattern`: 该属性定义了已经被滚动(已被归档)文件的文件名,该属性的应该含有文件名,并且在适当位置加上`%d转换说明符`(%d{yyyy-MM-dd})。 > ##### %d转换说明符 > %d转换说明符包含一个date-and-time格式的字符串,该格式和SimpleDateFormat相同。如果该date-and-time被省略,那么默认为yyyy-MM-dd. > > 可以在fileNamePattern中含有多个%d转换说明符,但是只能有一个`主%d转换说明符`,主%d转换说明符用于推断滚动周期。其他说有的%d转换说明符都需要通过`aux`来进行修饰(`%d{yyyy/MM, aux}`)。 > > 示例如下 > > `/var/log/%d{yyyy/MM, aux}/myapplication.%d{yyyy-MM-dd}.log`. 滚动周期的值将会从date-and-time表达式来推断。 RollingFileAppender中,file属性可以被省略或设置为null。如果为RollingFileAppender设置了file属性,那么可以解耦当前日志和已经被归档的日志。如果file属性的值,那么当前日志路径就是file文件的值,此时当前日志的路径不会随着日期的变动而改变。 但是,在省略file属性的情形下,那么当前日志文件路径将会根据fileNamePattern来计算 - `maxHistory`: `maxHistory`指定了保留归档日志的最大数量,会对旧日志进行异步删除。 如果将maxHistory设置为0,将会禁用旧日志删除。默认情况下,maxHistory被设置为0。 - `totalSizeCap`:`totalSizeCap`控制了所有归档日志文件的最大大小,当最大小超过阈值时,最老的文件将会被删除。`totalSizeCap`需要`maxHistory`属性也被设置,通常,totalSizeCap都在maxHistory之后被应用。 默认情况下,totalSizeCap被设置为0,代表不会有总大小的阈值限制。 - `cleanHistoryOnStart`:如果该属性被设置为true,当appender启动时归档日志将会被清除。 TimeBasedRollingPolicy支持文件自动压缩,如果`fileNamePattern`后缀以`.gz`或`.zip`结尾,文件压缩将会自动被应用。 示例如下 ``` /wombat/foo.%d.gz ``` 上述示例当前日志被输出到`/wombat/foo.yyyy-MM-dd`文件中,但是在第二天触发归档后,文件将会被压缩到`/wombat/foo.yyyy-MM-dd.gz`中。 TimeBasedRollingPolicy示例如下: ```xml logFile.log logFile.%d{yyyy-MM-dd}.log 30 3GB %-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n ``` #### SizeAndTimeBasedRollingPolicy SizeAndTimeBasedRollingPolicy支持配置单个日志文件的最大大小,其除了支持%d转换符外,还支持%i转换符,%i转换符也是必填的。每当当前日志文件达到最大文件大小时,其都会递增`%i`序列号,并且旧日志文件将会被归档。序列号从0开始。 SizeAndTimeBasedRollingPolicy还包含如下属性: - `maxFileSize`:每当当前日志文件达到`maxFileSize`指定的大小时,其都会递增序列号,序列号默认为0. maxFileSize为FileSize类型,可以通过`KB, MB, GB`等单位来指定。 - `checkIncrement`:检查当前日志文大小的间隔时间,默认为60s ```xml mylog.txt mylog-%d{yyyy-MM-dd}.%i.txt 100MB 60 20GB %msg%n ``` #### Encoder 通常,PatternLayoutEncoder,其pattern可以指定为如下形式: ```xml %d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n ```