使用
Logback 的 Maven 依赖:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
其依赖关系如下:
其中 logback-core 是 Logback 的核心包,而 logback-classic 是 Slf4j 的实现。我们写一个示例:
public class LogbackTest {
private static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
public static void main(String[] args) {
LOGGER.debug("Hello Logback!");
// 打印内部的状态
LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
}
}
在没有配置文件的情况下,Logback 的日志信息默认会输出到控制台。我们还可以通过 StatusPrinter 来打印 Logback 的内部执行信息:
可以看到,Logback 没有在 classpath 路径下找到 logback-test.xml、logback.groovy、logback.xml 文件,因此采用默认的配置,即输出到控制台。下面我们来看看 Logback 的配置文件如何设置:
配置文件
Logback 的配置文件非常灵活,最基本的结构为
1. appender
appender 就是配置日志的输出目的地,通过 name 属性指定名字,通过 class 属性指定目的地:
- ch.qos.logback.core.ConsoleAppender:输出到控制台
- ch.qos.logback.core.FileAppender:输出到文件
- ch.qos.logback.core.rolling.RollingFileAppender:文件大小超过阈值时产生一个新文件
- ch.qos.logback.classic.AsyncAppender:异步输出
除了输出到本地,还可以通过 SocketAppender 和 SSLSocketAppender 输出到远程设备,通过 SMTPAppender 输出到邮件,甚至可以通过 DBAppender 通过 JDBC 输出到数据库中。
此外还有两个通用属性为:encoder 负责把日志信息转换成字节数组,并且把字节数组写到输出流。pattern 用来指定日志的输出格式,具体配置参见官网。
2. root
level 表示日志级别,值可以为:TRACE、DEBUG、INFO、WARN、ERROR、ALL、OFF。
appender-ref 用来指定具体的 appender。
3. 自动重载配置
之前提到 Logback 很强的一个功能就是支持自动重载配置,那想要启用这个功能也非常简单,只需要在 configuration 元素上添加 scan=true 即可。启用后,Logback 会起一个 ReconfigureOnChangeTask 的定时任务来监视配置文件的变化。
默认情况下,扫描的时间间隔是一分钟一次。如果想要调整时间间隔,可以通过 scanPeriod 属性进行调整,单位可以是毫秒(milliseconds)、秒(seconds)、分钟(minutes)或者小时(hours)。下面这个示例指定的时间间隔是 30 秒:
<configuration scan="true" scanPeriod="30 seconds">
...
</configuration>
注意:如果指定了时间间隔,没有指定时间单位,默认的时间单位为毫秒。
4. 异步输出
使用 Logback 提供的 AsyncAppender 即可实现异步的日志记录。AsyncAppende 类似装饰模式,也就是在不改变类原有基本功能的情况下为其增添新功能。这样,我们就可以把 AsyncAppender 附加在其他的 Appender 上,将其变为异步的。
定义一个异步 Appender ASYNCFILE,包装之前的同步文件日志记录的 FileAppender,就可以实现异步记录日志到文件:
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE"/>
</appender>
但在使用 AsyncAppender 我们要注意它的一些参数配置,下面通过源码进行分析:
public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
boolean includeCallerData = false;//是否收集调用方数据
protected boolean isDiscardable(ILoggingEvent event) {
Level level = event.getLevel();
return level.toInt() <= Level.INFO_INT;//丢弃<=INFO级别的日志
}
protected void preprocess(ILoggingEvent eventObject) {
eventObject.prepareForDeferredProcessing();
if (includeCallerData)
eventObject.getCallerData();
}
}
public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {
BlockingQueue<E> blockingQueue;//异步日志的关键,阻塞队列
public static final int DEFAULT_QUEUE_SIZE = 256;//默认队列大小
int queueSize = DEFAULT_QUEUE_SIZE;
static final int UNDEFINED = -1;
int discardingThreshold = UNDEFINED;
boolean neverBlock = false;//控制队列满的时候加入数据时是否直接丢弃,不会阻塞等待
@Override
public void start() {
...
blockingQueue = new ArrayBlockingQueue<E>(queueSize);
if (discardingThreshold == UNDEFINED)
discardingThreshold = queueSize / 5;//默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法
...
}
@Override
protected void append(E eventObject) {
if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
return;
}
preprocess(eventObject);
put(eventObject);
}
private boolean isQueueBelowDiscardingThreshold() {
return (blockingQueue.remainingCapacity() < discardingThreshold);
}
private void put(E eventObject) {
if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
blockingQueue.offer(eventObject);
} else {
putUninterruptibly(eventObject);
}
}
//以阻塞方式添加数据到队列
private void putUninterruptibly(E eventObject) {
boolean interrupted = false;
try {
while (true) {
try {
blockingQueue.put(eventObject);
break;
} catch (InterruptedException e) {
interrupted = true;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
}
includeCallerData 用于控制是否收集调用方数据,默认是 false,此时方法行号、方法名等信息将不能显示(源码第 2 行以及 9 到 10 行)。
queueSize 用于控制阻塞队列大小,使用的 ArrayBlockingQueue 阻塞队列(源码第 26 行),默认大小是 256(源码第 17 行),即内存中最多保存 256 条日志。
discardingThreshold 是控制丢弃日志的阈值,主要是防止队列满后一直阻塞。默认情况下,队列剩余量低于队列长度的 20%,就会丢弃 TRACE、DEBUG 和 INFO 级别的日志。(参见源码第 3 到 6 行、19 到 20 行、27 到 28 行、34 到 36 行)
neverBlock 用于控制队列满的时候,加入的数据是否直接丢弃,不会阻塞等待,默认是 false(源码第 46 行)。这里需要注意一下 offer 方法和 put 方法的区别,当队列满的时候 offer 方法不阻塞,而 put 方法会阻塞;neverBlock 为 true 时,使用 offer 方法。
可以看出 queueSize、discardingThreshold 和 neverBlock 这三个参数息息相关,使用时务必按需进行设置和取舍,到底是性能为先,还是数据不丢为先。
如果考虑绝对性能为先,那就设置 neverBlock 为 true,永不阻塞。如果考虑绝对不丢数据为先,那就设置 discardingThreshold 为 0,即使是 <= INFO 的级别日志也不会丢,但最好把 queueSize 设置大一点,毕竟默认的 queueSize 太小,太容易阻塞。如果希望兼顾两者,可以丢弃不重要的日志,把 queueSize 设置大一点,再设置一个合理的 discardingThreshold。
5. 配置总览
<?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">
<!-- 运行环境,dev:开发,test:测试,pre:预生产,pro:生产 -->
<property name="system_host" value="dev" />
<property file="system.properties" />
<!-- 上下文变量设置,用来定义变量值,其中name的值是变量的名称,value的值时变量定义的值。 通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<property name="CONTEXT_NAME" value="logback-test" />
<!-- 日志文件存放路径设置,绝对路径 -->
<property name="logs.dir" value="/opt/logs" />
<!-- 日志文件存放路径设置,tomcat路径 -->
<property name="logs.dir" value="${catalina.base}/logs" />
<!-- 定义日志文件 相对输入位置 -->
<property name="log_dir" value="log" />
<!-- 日志输出格式设置 -->
<!--
%d{yyyy-MM-dd HH:mm:ss} [%level] - %msg%n
Logger: %logger
Class: %class
File: %file
Caller: %caller
Line: %line
Message: %m
Method: %M
Relative: %relative
Thread: %thread
Exception: %ex
xException: %xEx
nopException: %nopex
rException: %rEx
Marker: %marker
newline:%n
-->
<property name="CUSTOM_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{90} - %msg%n" />
<!-- 上下文名称:<contextName>, 每个logger都关联到logger上下文, 默认上下文名称为“default”。但可以使用<contextName>设置成其他名字,用于区分不同应用程序的记录。
一旦设置,不能修改。 -->
<contextName>${CONTEXT_NAME}</contextName>
<!-- <appender>是<configuration>的子节点,是负责写日志的组件。 有两个必要属性name和class。name指定appender名称,
class指定appender的实现类。 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!-- 对日志进行格式化。 -->
<encoder>
<pattern>${CUSTOM_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="file"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 按天来回滚,如果需要按小时来回滚,则设置为{yyyy-MM-dd_HH} -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/testC.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 如果按天来回滚,则最大保存时间为30天,30天之前的都将被清理掉 -->
<maxHistory>30</maxHistory>
<!-- 按时间回滚的同时,按文件大小来回滚 -->
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 也可以使用下面这个策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
<!--日志文件最大的大小-->
<MaxFileSize>100MB</MaxFileSize>
<!--日志整体最大
可选的totalSizeCap属性控制所有归档文件的总大小。当超过总大小上限时,将异步删除最旧的存档。
totalSizeCap属性也需要设置maxHistory属性。此外,“最大历史”限制总是首先应用,“总大小上限”限制其次应用。
-->
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<!-- 过滤器,只记录WARN级别的日志 -->
<!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 设置过滤级别 -->
<level>WARN</level>
<!-- 用于配置符合过滤条件的操作 -->
<onMatch>ACCEPT</onMatch>
<!-- 用于配置不符合过滤条件的操作 -->
<onMismatch>DENY</onMismatch>
</filter>
<!-- 日志输出格式 -->
<encoder>
<pattern>${CUSTOM_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="log_file"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。 -->
<file>${logs.dir}/logback-test.log</file>
<!-- 按照固定窗口模式生成日志文件,当文件大于20MB时,生成新的日志文件。窗口大小是1到3,当保存了3个归档文件后,将覆盖最早的日志 -->
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<!-- 必须包含“%i”例如,假设最小值和最大值分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz
或者 没有log%i.log.zip -->
<FileNamePattern>${logs.dir}/logback-test.%i.log</FileNamePattern>
<!-- 窗口索引最小值 -->
<minIndex>1</minIndex>
<!-- 窗口索引最大值 -->
<maxIndex>3</maxIndex>
</rollingPolicy>
<!-- 日志级别过滤器(自定义计算条件) -->
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator">
<expression>
e.level.toInt() == WARN.toInt() || e.level.toInt() == INFO.toInt()
</expression>
</evaluator>
<OnMismatch>DENY</OnMismatch>
<OnMatch>NEUTRAL</OnMatch>
</filter>
<!-- 激活滚动的条件。 -->
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<!-- 活动文件的大小,默认值是10MB -->
<maxFileSize>30MB</maxFileSize>
</triggeringPolicy>
<!-- 对记录事件进行格式化。 -->
<encoder>
<pattern>${CUSTOM_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 异步输出 -->
<appender name="ASYNC_logback" class="ch.qos.logback.classic.AsyncAppender">
<!-- 控制丢弃日志的阈值,主要是防止队列满后阻塞。默认如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<!-- <discardingThreshold>0</discardingThreshold> -->
<!-- 更改默认的阻塞队列的深度,该值会影响性能。默认值为256,即内存中最多保存256条日志 -->
<!-- <queueSize>256</queueSize> -->
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="log_file" />
</appender>
<!-- 指定包输出路径 -->
<!-- 用来设置某一个 包 或者具体的某一个 类 的日志打印级别、以及指定<appender>, name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。
additivity:是否向上级logger传递打印信息。默认是true。(这个logger的上级就是上面的root) <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。 -->
<logger name="org.logback.test" level="DEBUG" additivity="true">
<appender-ref ref="stdout" />
</logger>
<!-- 特殊的<logger>元素,是根logger。只有一个level属性,应为已经被命名为"root". level:设置打印级别,大小写无关:TRACE,
DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。 <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个loger。 -->
<root>
<level value="WARN" />
<!-- if表达式,需要Janino jar -->
<!-- Janino 2.6.0版本开始,除了janino.jar之外, commons-compiler.jar也需要在类路径中 -->
<if condition='property("system_host").contains("dev")'>
<then>
<appender-ref ref="stdout" />
</then>
</if>
<appender-ref ref="file" />
</root>
</configuration>
参考资料
Logback 的官网上是有一份手册的,非常详细,足足 200 多页,只不过是英文版的。如果英文阅读能力有限的话,可以到 GitHub 上查看翻译的中文版。