官方文档
一、选择 LogBack 原因
- 执行效率更高
- 经过大量的测试
- 与
SLF4J
配合使用零开销 - 文档齐全
- 使用 XML 或 Groovy 配置文件
- 配置文件自动重新加载
- 从 I/O 失败中优雅地恢复
- 通过配置可以自动删除旧日志文档
- 日志压缩
- 过滤器
- …
二、最简使用教程
Step1 - 添加依赖
```javaorg.slf4j slf4j-api 1.7.25
<a name="5dPMO"></a>
## Step2 - 创建配置文件

<a name="EzKFE"></a>
## Step3 - 自定义 XML 配置文件
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<logger name="com.base22" level="TRACE"/>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Step4 - 使用 SLF4J API 完成日志记录
private static void doSlf4jAPi() {
Logger logger = LoggerFactory.getLogger(TestSlf4jApi.class);
logger.info("Hello World");
logger.info("hello, {}, hello, {}", "world", "java");
logger.debug("debug");
logger.warn("warn");
logger.error("error");
logger.error("当前LoggerFactory对象为:" + logger.getClass());
}
// OUTPUT
// 当前LoggerFactory对象为:class ch.qos.logback.classic.Logger
三、Logback 架构
配置文件语法
- 标签名
大小写敏感
logger
标签name
: 必要属性。level
: 可选属性。additivity
: 可选属性。logger
元素可以包含0
个或多个<appender-ref>
元素。被引用的appender
都会添加到被命名的logger
中。
root
标签- 仅支持
level
属性。 - 与
logger
标签相似,<root>
可以包含多个<appender-ref>
元素。
- 仅支持
- 配置
Appenders
name
: 必要属性。class
: 必要属性。表示要实例化的appender
类的全限定名。layout
: 非必要属性。可包含0个或多个。class
encoder
: 非必要属性。可包含0个或多个。filter
: 非必要属性。可包含0个或多个。class
Appenders
累加: 比如下面的XML
配置将会在日志文件myApp.log
中输出两行一模一样的日志。因为在logger
以及root
都配置了相同的appender
,所以导致这种情况发生。怎么解决呢? 你可以根据项目需要把appender
单独放在其中一个即可。或者通过设置additivity=false
禁用从父类中继承行为。 ```xml%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} = %msg%n myApp.log %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} = %msg%n
- 设置上下文名称。 `<contextName></contextName>`
- 定义变量: 使用 `<property>` 或 `<variable>` 标签定义,使用 `${}` 完成属性获取
```xml
<configuration>
<property name="USER_HOME" value="/home/sebastien" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
当定义标签众多,可以创建一个包含所有变量的单独文件可能更加方便。
<property file="src/main/java/chapters/configuration/variables1.properties" />
// variables1.properties
USER_HOME=/home/sebastien
- 属性作用域
LOCAL SCOPE
: 定义在配置文件中。CONTEXT SCOPE
: 上下文中的属性定义,与上下文共同存在。它在所有日志事件中都可用。SYSTEM SCOPE
:JVM
在替换期间,首先在LOCAL SCOPE
作用域查找属性,然后在 CONTEXT_SCOPE
作用域中查找属性,最后在 SYSTEM SCOPE
作用域中查找属性。
可以通过指定 scope
来设置属性的范围:
<property scope="context" name="nodeId" value="firstNode" />
- 默认的变量值: 使用
:-
表示,如${aName:-golden}
动态定义属性
<configuration> <define name="rootLevel" class="a.class.implementing.PropertyDefiner"> <shape>round</shape> <color>brown</color> <size>24</size> </define> <root level="${rootLevel}"/> </configuration>
条件配置
<if condition='property("HOSTNAME").contains("torino")'> <then> <appender name="CON" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d %-5level %logger{35} - %msg %n</pattern> </encoder> </appender> <root> <appender-ref ref="CON" /> </root> </then> </if>
3.1 三个模块
logback-core
:基础模块,是其他两个模块的依赖。logback-classic
:相对于Log4J
显著改进的版本。logback-access
:与Servlet
容器集成,提供Http-Access
日志功能。3.2 Logger Context
单个logger
附属于一个LoggerContext
,这个LoggerContext
负责制造loggers
并安排在一个类似树的层次结构中。
3.3 命名层次
名为 com.foo
的记录器是名为 com.foo.Bar
记录器的父记录器。 org.slf4j.Logger.ROOT_LOGGER_NAME
为每个记录器(Logger)的父类(有点类似 Java 的 Class 类)。Logger
接口中一些基本方法:
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);
}
3.4 级别
记录器可以被分配一个级别( Level
), ch.qos.logback.classic
定义了以下几个级别:
- TRACE
- DEBUG
- INFO
- WARN
- ERROR
日志级别顺序 TRACE < DEBUG < INFO < WARN < ERROR
Level
为 final
类,不能被实例化,因为存在一个更加灵活的 Marker
接口。
如果没有为 logger
分配级别,那么它将从最近的具有分配级别的父类继承一个。默认情况下,根记录器分配的级别为 DEBUG
。
基本选择原则:如果 p >= q
,则启用向具有有效级别 q
的记录器发出的级别 p
的日志请求。
横坐标表示 Level
,纵坐标表示请求类型,比如当 Level=INFO
时,则会打印 INFO、WARN、ERROR
这三种级别的日志信息。可以使用以下代码测试:
public class TestLogbackLevel {
@Test
public void testLevel() {
ch.qos.logback.classic.Logger logger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
// set its Level to INFO. The setLevel() method requires a logback logger
logger.setLevel(Level.INFO);
Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");
// This request is enabled, because WARN >= INFO
logger.warn("Low fuel level.");
// This request is disabled, because DEBUG < INFO.
logger.debug("Starting search for nearest gas station.");
// The logger instance barlogger, named "com.foo.Bar",
// will inherit its level from the logger named
// "com.foo" Thus, the following request is enabled
// because INFO >= INFO.
barlogger.info("Located nearest gas station.");
// This request is disabled, because DEBUG < INFO.
barlogger.debug("Exiting gas station search");
}
}
3.5 △ Appenders
输出目的地(output destination)被称为 appender
。以下这些都可以做为 Appender
:
- 控制台(console)
- 文件(files)
- 远程socket服务(remote socket servers)
- MySQL、PostgreSQL、Oracle and other databases
- JMS and remote UNIX Syslog daemons
一个 Appender
可以绑定多个 logger
。Appender
规则如下:
Logger L 的日志语句输出将转到 L 中所有的 Appenders
及其祖先。可以通过设置 additivity
标志为 false
禁用此规则。
通常,用户不仅希望自定义输出目的地,还希望自定义输出格式。这可以通过将 布局(layout)
与 appender
关联来实现。
3.6 Layout
负责根据用户自定义格式化日志记录请求 。 PatternLayout
是标准日志发布一部分,允许用户根据类似于 C
语言 printf
函数的转换模式指定输出格式。比如:
"%-4relative [%thread] %-5level %logger{32} - %msg%n"
[main] DEBUG manual.architecture.HelloWorld2 - Hello world.
3.7 △Appender
3.7.1 Appender相关接口
public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable {
public String getName();
public void setName(String name);
void doAppend(E event);
}
重要的方法是 doAppender
,实现了 FilterAttachable
,因此可以将一个或多个过滤器添加到 Appender
实例中。 Appender
最终输出日志事件,日志格式化需要代理给 Layout
或 Encoder
对象实现。每个 layout/encoder
绑定有且只有一个 appender
。
3.7.2 AppenderBase
ch.qos.logback.core.AppenderBase
是实现 Appender
接口的抽象类,它提供所有 Appender
共享的基本功能,比如获取/设置它们的名称, activation
状态, layout
以及 filters
。
3.7.3 Logback-Core
Logback-core 为构建其他 logback 模块奠定了基础。
OutputStreamAppender
将事件添加到 java.io.OutputStream
。此类提供其他 appenders
构建的基本服务。
ConsoleAppender
使用 System.out
或 System.err
输出日志。
FileAppender
FileAppender
是 OutputStreamAppender
的子类,它将日志事件附加到文件中。目标文件由 File
选项指定。如果文件已经存在,则根据追加属性的值将其追加或截断。
属性名称 | 类型 | 描述 |
---|---|---|
append | boolean | true : 添加到已存在文件的末尾,默认值。false : 任何已存在的文件都会被截断。 |
encoder | Encoder | |
file | String | 要写入的文件名称。如果文件不存在,则自动创建。 |
prudent | boolean | 默认值: false true : FileAppender 完全写入指定文件。由于依赖锁实现锁而实现安全写入,这样会导致吞吐量下降。 |
默认情况下,每个日志事件都会立即刷新到底层输出流。这种默认方式比较安全,但如果想提高吞吐量,可以设置为 false
。例子:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>testFile.log</file>
<append>true</append>
<!-- set immediateFlush to false for much higher logging throughput -->
<immediateFlush>true</immediateFlush>
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
通过时间戳命令文件
<configuration>
<!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
the key "bySecond" into the logger context. This value will be
available to all subsequent configuration elements. -->
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!-- use the previously created timestamp to create a uniquely
named log file -->
<file>log-${bySecond}.txt</file>
<encoder>
<pattern>%logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
<timestamp>
接收两个必要属性,分别是key
和datePattern
以及一个timeReference
属性。RollingPolicy
: 负责执行滚动所需的操作TriggeringPolicy
: 确定是否滚动以及什么时候滚动,什么时候触发时机。 | 属性名称 | 类型 | 描述 | | —- | —- | —- | | file | String | | | append | boolean |
| | rollingPolicy | RollingPolicy | 在发生滚动时将决定RollingFileAppender
行为的组件 | | triggeringPolicy | TriggeringPolicy | 何时激活滚动过程 | | prudent | boolean | 谨慎模式 |
滚动策略
RollingPolicy
负责滚动过程,包括 文件移动
和 重命名
。
public interface RollingPolicy extends LifeCycle {
public void rollover() throws RolloverFailure;
public String getActiveFileName();
public CompressionMode getCompressionMode();
public void setParent(FileAppender appender);
}
rollover()
: 完成归档当前日志文件的工作。getActiveFileName()
: 返回当前日志文件的文件名(将活动日志写入其中)△基于时间滚动策略 — TimeBasedRollingPolicy
基于时间
的滚动策略。可能是最受欢迎的日志策略。定义一个基于时间
的滚动策略。比如按月
、日
等。
属性名称 | 类型 | 描述 |
---|---|---|
fileNamePattern | String | 包含文件的名称加上 %d{时间格式} 默认格式为 yyyy-MM-dd ,如果存在多个,需要使用 aux 参数指定辅助标签。 |
maxHisotory | int | 归档日志保存最大时间,异步删除旧的日志文件。 |
totalSizeCap | int | 所有归档文件总大小。当超出上限时,将异常删除最早的归档文件。若 maxHistory 与 totalSizeCap 同时设置,则 maxHistroy 首先被应用 |
cleanHistoryOnStart | boolean | 在启动应用程序时执行档案删除。默认为 false |
以下是效果说明
文件名称模式 | 滚动调度 |
---|---|
foo.%d | 每日滚动(午夜) |
/wombat/%d{yyyy/MM}/foo.txt | 每月开始 |
/wombat/foo.%d{yyyy-ww}.log | 每周第一天 |
/wombat/foo%d{yyyy-MM-dd_HH}.log | 每小时 |
/wombat/foo%d{yyyy-MM-dd_HH-mm}.log | 每分钟 |
/wombat/foo%d{yyyy-MM-dd_HH-mm, UTC}.log | 每分钟 |
/foo/%d{yyyy-MM,aux}/%d.log | 每日更新。档案位于包含年份和月份的文件夹下 |
/wombat/foo.%d.gz | TimeBasedRollingPolicy 支持自动文件压缩, fileNamePattern 以 .gz 或 .zip 结尾。 |
滚动时间不是基于服务器时间,而是基于日期事件到达时间。
下面是 RollingFileAppender
和 TimeBasedRollingPolicy
的示例配置。
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logFile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每日滚动生成日志文件 -->
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 保留30天历史记录,总大小上限为3GB -->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>
</configuration>
△基于大小和时间的滚动策略 — SizeAndTimeBasedRollingPolicy
有时候希望基本按日期归档文件,但同时限制每个日志文件的大小。可以使用 SizeAndTimeBasedRollingPolicy
策略达到目的。
<configuration>
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>mylog.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<!--每个日志文件最大容量为100MB,保留60天,日志总量最大为20GB-->
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="ROLLING" />
</root>
</configuration>
注意
%i
和%d{}
都是必要的。每当当前日志文件大小超过maxFileSize
时,都会使用一个不断增的索引(从0开始)将其归档。
固定时间窗口滚动策略 — FixedWindowRollingPolicy
固定窗口滚动策略。以下是其可用属性:
属性名称 | 类型 | 描述 |
---|---|---|
minIndex | int | 窗口下限 |
maxIndex | int | 窗口上限 |
fileNamePattern | String |
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>tests.%i.log.zip</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>3</maxIndex>
</rollingPolicy>
3.7.4 触发策略概述
TriggeringPolicy
定义指示 RollingFileAppender
是否滚动。接口定义如下:
public interface TriggeringPolicy<E> extends LifeCycle {
// activeFile: 正在活动的文件,event: 日志事件
public boolean isTriggeringEvent(final File activeFile, final <E> event);
}
3.7.5 AsyncAppender
异步
记录 ILoggingEvents
事件,它只做事件分配器,因此必须引用另一个 appender
以执行异步写入操作。 事件
存储在 BlockingQueue
队列,由 AsyncAppender
创建的工作线程从队列头部获取日志事件,并将它们分派到 AsyncAppender
绑定的单个 appender
中,这个 appender
是真正用来处理事件,包括格式化以及完成写入操作。
注意: 在队列容量超过
80%
时,AsyncAppender
将会删除TRACE、 DEBUG 和 INFO
级别的事件。这种策略以损失事件为代价换取吞吐率。
当应用程序关闭或重新部署时, AsyncAppender
必须得以停止。可以通过停止 LoggerContext
来实现,这将关闭所有 appender
,包括任何 AsyncAppender
实例。 AsyncAppender
将等待工作线程最大刷新时间 maxFlushTime
,达到设定的时间,不管工作线程有没有完成工作都会被强制关闭。
可以通过向 JVM
注册 shutdown hook
以正确关闭 LoggerContext
。
属性名称 | 类型 | 描述 |
---|---|---|
queueSize | int | 阻塞队列最大容量,默认值: 256 |
discardingThreshold | int | 丢弃阈值,默认值: 20% |
includeCallerData | boolean | |
maxFlushTime | int | 毫秒为单位指定最大队列刷新超时 |
neverBlock | boolean |
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myapp.log</file>
<encoder>
<pattern>%logger{35} - %msg%n</pattern>
</encoder>
</appender>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<root level="DEBUG">
<appender-ref ref="ASYNC" />
</root>
</configuration>
3.8 Encoders
Encoders
负责将事件转换为 字节数组(byte array)
,并将字节数组写入 Outputstream
。
目前, PatternLayoutEncoder
是真正有用的 Encoder
。
public interface Encoder<E> extends ContextAware, LifeCycle {
/**
* Get header bytes. This method is typically called upon opening of
* an output stream.
*
* @return header bytes. Null values are allowed.
*/
byte[] headerBytes();
/**
* Encode an event as bytes.
*
* @param event
*/
byte[] encode(E event);
/**
* Get footer bytes. This method is typically called prior to the closing
* of the stream where events are written.
*
* @return footer bytes. Null values are allowed.
*/
byte[] footerBytes();
}
3.8.1 LayoutWrappingEncoder
public class LayoutWrappingEncoder<E> extends EncoderBase<E> {
protected Layout<E> layout;
private Charset charset;
// encode a given event as a byte[]
public byte[] encode(E event) {
// 将用户事件转换为字符串
String txt = layout.doLayout(event);
// 根据用户选择的字符集编码转换为字节
return convertToBytes(txt);
}
private byte[] convertToBytes(String s) {
if (charset == null) {
return s.getBytes();
} else {
return s.getBytes(charset);
}
}
}
3.8.2 PatternLayoutEncoder
这是 LayoutWrappingEncoder 的一个扩展,仅限于包装 PatternLayout 的实例。
3.9 Layout
3.9.1 配置自定义 layout
继承
LayoutBase<ILoggingEvent>
public class MySampleLayout extends LayoutBase<ILoggingEvent> { public String doLayout(ILoggingEvent event) { StringBuffer sbuf = new StringBuffer(128); sbuf.append(event.getTimeStamp() - event.getLoggingContextVO.getBirthTime()); sbuf.append(" "); sbuf.append(event.getLevel()); sbuf.append(" ["); sbuf.append(event.getThreadName()); sbuf.append("] "); sbuf.append(event.getLoggerName(); sbuf.append(" - "); sbuf.append(event.getFormattedMessage()); sbuf.append(CoreConstants.LINE_SEP); return sbuf.toString(); } }
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="chapters.layouts.MySampleLayout" /> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
3.9.2 PatternLayout
PatternLayout
接受一个日志事件并返回string
。这个字符串可以通过调整PatternLayout
的转换模式来定制。PatternLayout
的转换模式与C
语言()的printf()
函数的转换模式密切相关,每个转换说明符以%
开始,后面跟着可选的格式修饰符
、转换字
和大括号可选参数。
比如%logger{10}
:logger
是转换词,10
是选项。
转换字 | 效果 |
---|---|
logger{length} | mainPackage.sub.sample.Bar%logger{0} : Bar%logger{5} : m.s.s.Bar%logger{26} : mainPackage.sub.sample.Bar |
d{pattern} date{pattern} d{pattern, timezone} date{pattern, timezone} |
%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 |
m/msg/message | 输出与日志记录事件关联的信息 |
n | 输出平台相关的换行分隔符。 \n 或 \r\n |
p/le/level | 输出日志事件级别 |
r/relative | 输出从应用程序启动到创建日志事件之间经过的毫秒数 |
t/thread | 输出生成日志记录事件的线程名称 |
X{key:-defaultVal} mdc{key:-defaultVal} |
输出与生成日志记录事件的线程关联的 MDC 如 %MDC{userid} 则通过 key=userid 查找 MDC 对应的 value 值。如果为空,则输出 :- 提供了默认值,如果没有指定默认值,则输出空字符串。 |
xEx{depth} xException{depth} xThrowable{depth} xEx{depth, evaluator-1, …, evaluator-n} xException{depth, evaluator-1, …, evaluator-n} xThrowable{depth, evaluator-1, …, evaluator-n} |
堆栈深度。默认输出完整的堆栈。 - short : 打印第一行- full : 打印完整的堆栈跟踪- any integer : 打印堆栈跟踪的给定行数 |
property{key} | 从上下文获取,获取不到则从系统属性中查找 |
3.10 格式化修饰符
借助修饰符,可以改变每个数据字段的 最小
和 最大宽度
。
可选的格式修饰符放在 百分比符号
和 转换字符或单词之间
,即 % 格式修饰符 转换字符
。
格式修饰符 | 含义 | 描述 |
---|---|---|
- 最小字段宽度(十进制) |
左对齐 | %-10thread : 左对齐,小于20则为空格%.30 : 字符串长于30,从头截断%20.30 : 小于20,左边添加空格,长于30,从头截断%.-30 : 从后面截断 |
3.10.1 分组
%-30(%d{HH:mm:ss.SSS} [%thread]) %-5level %logger{32} - %msg%n
3.11 过滤器
在 logback-classic 中,过滤器可以添加到 Appender 实例中。通过向附加程序添加一个或多个过滤器,可以按任意标准过滤事件,例如日志消息的内容、 MDC 的内容、一天的时间或日志事件的任何其他部分。
public class SampleFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getMessage().contains("sample")) {
return FilterReply.ACCEPT;
} else {
return FilterReply.NEUTRAL;
}
}
}
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="chapters.filters.SampleFilter" />
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger - %msg%n
</pattern>
</encoder>
</appender>
<root>
<appender-ref ref="STDOUT" />
</root>
</configuration>
3.12 MDC
3.12.1 相关方法
public class MDC {
//Put a context value as identified by key
//into the current thread's context map.
public static void put(String key, String val);
//Get the context identified by the key parameter.
public static String get(String key);
//Remove the context identified by the key parameter.
public static void remove(String key);
//Clear all entries in the MDC.
public static void clear();
}
3.12.2 XML 引用
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout>
<Pattern>%X{first} %X{last} - %m%n</Pattern>
</layout>
</appender>
3.12.3 MDC 高级使用
put()
和 get()
等 MDC
操作只影响当前线程和子线程。其他线程中的 MDC
不受影响。底层是由 ThreadLocal
实现。
// ch.qos.logback.classic.util.LogbackMDCAdapter
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
四、Logger 日志输出过程
- 获取过滤器链决策:如果它存在,则调用
TurboFilter
链,可以设置context-wide
阈值或根据每个日志记录请求相关信息(如Marker、Level, Logger, message, or the Throwable
)过滤掉某些事件。如果过滤器链返回结果有FilterReply.DENY,删除日志记录请求
,如果是FilterReply.NEUTRALdd,继续下一步
,回复FilterReply.ACCEPT,跳过下一步,直接第三步
。 - 应用基本选择规则:logback 将记录器的有效级别与请求级别进行比较。如果根据这个测试禁用了日志记录请求,那么 logback 将删除该请求,而不需要进一步处理。否则,它将继续进行下一步。
- 创建 LoggerEvent 对象:如果请求在前面的过滤器中幸存下来,logback 将创建一个
ch.qos.logback.classic.LoggingEvent
对象,LoggingEvent
对象包含请求的所有相关参数。例如请求的日志记录器、请求级别、消息本身、可能随请求一起传递的异常、当前时间、当前线程、关于发出日志记录请求的类和 MDC 的各种数据。 - 调用 appenders:在创建
LoggingEvent
对象之后,logback 将调用所有适用的附加程序的doAppend()
方法,即从 logger 上下文继承的附加程序。 - 格式化输出:格式化输出属于被调用的
appender
的职责。 - 发送 LoggingEvent :日志事件完全格式化之后,由每个
Appender
将其发送到目的地。
以下为其 XML
流程图:
ConsoleAppender
:将向控制台输出日志请求,并缩短日志记录器名称,以增加控制台窗口的空间,而不会失去可读性。RollingFileAppender
:将日志记录事件发送给一个名为logFile.log
的文件。并且每分钟更新一次活动文件。旧的文件将会被重合名并压缩为zip
文件。 ```javatestFile.log …%msg%n
<a name="YEkec"></a>
#### 将默认配置文件位置设置为系统属性
shell
java -Dlogback.configurationFile=/path/to/config.xml chapters.configuration.MyApp1
<a name="G9eno"></a>
##### 五、常用 Logback 日志配置
<a name="eYL7k"></a>
## 5.1 日志版本:
- `logback-core` : `1.2.3`
- `logback-class` : `1.2.3`
- `slf4j-api` : `1.7.30`
<a name="Y5wV6"></a>
## 5.2 基于 `TimeBasedRollingPolicy` 滚动策略
一般是按每天进行日志滚动( `%d{yyyy-MM-dd}` )。无论生成文件有多大,当天的日志全部归为一个日志文件。
xml
<?xml version=”1.0” encoding=”UTF-8”?>
<!--#2 滚动日志输出-->
<appender name="ROLLINGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.log</file>
<!--配置滚动策略: 基于时间滚动(%d{yyyy-MM-dd}每天生成单独日志文件)-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
<!--保留天数: 30-->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} = %msg%n</Pattern>
</encoder>
</appender>
<!--#3 定义level-->
<logger name="com.clarencezero" level="TRACE">
</logger>
<!--#4 关联appender-->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ROLLINGFILE"/>
</root>
<a name="lAacQ"></a>
## 5.3 基于`SizeAndTimeBasedRollingPolicy` 大小和时间的滚动策略
如果单独基于时间进行每日日志滚动生成,这样会导致单个日志非常大,所以应该把时间和空间大小结合起来生成日志文件。
```xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<property name="LOG_HOME" value="D://log"/>
<property name="APP_NAME" value="hospital"/>
<!-- #1 控制台日志输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--1-1 输出格式化-->
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/${APP_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM, aux}/${APP_NAME}-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<!--每个日志文件最大容量为100MB,保留60天,日志总量最大为20GB-->
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>20GB</totalSizeCap>
</rollingPolicy>
<encoder>
<Pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%15thread] %-5level %logger{36} = %msg%n</Pattern>
</encoder>
</appender>
<!--#3 定义level-->
<logger name="com.clarencezero" level="TRACE">
</logger>
<!--#4 关联appender-->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ROLLING"/>
</root>
</configuration>
SizeAndTimeBasedRollingPolicy
: 基于日志文件大小和时间进行滚动策略${LOG_HOME}/%d{yyyy-MM, aux}/${APP_NAME}-%d{yyyy-MM-dd}.%i.txt
: 每天生成日志文件可能有几十个,把这几十个日志文件放入单独的以yyyy-MM
命名的日志文件夹中。
5.4 设置配置文件热部署
<configuration scan="true" scanPeriod="30 seconds" >
...
</configuration>
- 默认时间间隔:
一分钟
- 默认时间单位:
毫秒
实现原理
: 当scan="ture"
时,将会安装ReconfigureOnChangeTask
,此任务在单独的单独的线程中运行并检查配置文件是否已更改。5.5 启用堆栈跟踪数据打包
在版本1.1.4
中,默认是禁用打包堆栈数据的。因为堆栈计算成本相当高,特别是在频繁引发异常的应用程序中。比如:
可以使用如下配置开启: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]
<configuration packagingData="true"> ... </configuration>
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); lc.setPackagingDataEnabled(true);
5.6 关闭资源
通过JVM
的shutdown hook
关闭日志和翻译资源是一种方便的方法:
默认的名称为<configuration debug="true"> <!-- in the absence of the class attribute, assume ch.qos.logback.core.hook.DefaultShutdownHook --> <shutdownHook/> .... </configuration>
DefaultShutdownHook
,将在指定的延迟(默认为0)之后停止logback context
。在30秒内任何在后台运行的日志文件压缩任务都要完成。在Web
环境中,webShutdownHook
将会自动安装,这使得<shutdownHook/>
没必要显示配置。五、Logback 异步 Appender — AsyncAppenderBase
可以学习一下关于多线程的写法: ```java package ch.qos.logback.core;
import ch.qos.logback.core.spi.AppenderAttachable; import ch.qos.logback.core.spi.AppenderAttachableImpl; import ch.qos.logback.core.util.InterruptUtil;
import java.util.Iterator; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;
public class AsyncAppenderBase
AppenderAttachableImpl<E> aai = new AppenderAttachableImpl<E>();
// 定义一个阻塞队列用以缓存日志事件
BlockingQueue<E> blockingQueue;
/**
* The default buffer size.
*/
public static final int DEFAULT_QUEUE_SIZE = 256;
int queueSize = DEFAULT_QUEUE_SIZE;
// 初始化工作线程
Worker worker = new Worker();
public static final int DEFAULT_MAX_FLUSH_TIME = 1000;
int maxFlushTime = DEFAULT_MAX_FLUSH_TIME;
@Override
public void start() {
if (isStarted())
return;
blockingQueue = new ArrayBlockingQueue<E>(queueSize);
// 设置守护线程
worker.setDaemon(true);
worker.setName("AsyncAppender-Worker-" + getName());
super.start();
// 启动工作线程
worker.start();
}
@Override
public void stop() {
if (!isStarted())
return;
// mark this appender as stopped so that Worker can also processPriorToRemoval if it is invoking
// aii.appendLoopOnAppenders
// and sub-appenders consume the interruption
super.stop();
// 中断工作线程,使其可以终止。 请注意,子Appender可以消耗此中断
worker.interrupt();
InterruptUtil interruptUtil = new InterruptUtil(context);
try {
interruptUtil.maskInterruptFlag();
// 最多等待 maxFlushTime 毫秒数,以使得线程关闭
worker.join(maxFlushTime);
// check to see if the thread ended and if not add a warning message
if (worker.isAlive()) {
addWarn("Max queue flush timeout (" + maxFlushTime + " ms) exceeded. Approximately " + blockingQueue.size()
+ " queued events were possibly discarded.");
} else {
addInfo("Queue flush finished successfully within timeout.");
}
} catch (InterruptedException e) {
int remaining = blockingQueue.size();
addError("Failed to join worker thread. " + remaining + " queued events may be discarded.", e);
} finally {
interruptUtil.unmaskInterruptFlag();
}
}
class Worker extends Thread {
public void run() {
AsyncAppenderBase<E> parent = AsyncAppenderBase.this;
AppenderAttachableImpl<E> aai = parent.aai;
// loop while the parent is started
while (parent.isStarted()) {
try {
E e = parent.blockingQueue.take();
aai.appendLoopOnAppenders(e);
} catch (InterruptedException ie) {
break;
}
}
addInfo("Worker thread will flush remaining events before exiting. ");
for (E e : parent.blockingQueue) {
aai.appendLoopOnAppenders(e);
parent.blockingQueue.remove(e);
}
aai.detachAndStopAllAppenders();
}
}
}
```