使用

Logback 的 Maven 依赖:

  1. <dependency>
  2. <groupId>ch.qos.logback</groupId>
  3. <artifactId>logback-classic</artifactId>
  4. <version>1.2.3</version>
  5. </dependency>

其依赖关系如下:
image.png
其中 logback-core 是 Logback 的核心包,而 logback-classic 是 Slf4j 的实现。我们写一个示例:

  1. public class LogbackTest {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
  3. public static void main(String[] args) {
  4. LOGGER.debug("Hello Logback!");
  5. // 打印内部的状态
  6. LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
  7. StatusPrinter.print(lc);
  8. }
  9. }

在没有配置文件的情况下,Logback 的日志信息默认会输出到控制台。我们还可以通过 StatusPrinter 来打印 Logback 的内部执行信息:
image.png
可以看到,Logback 没有在 classpath 路径下找到 logback-test.xml、logback.groovy、logback.xml 文件,因此采用默认的配置,即输出到控制台。下面我们来看看 Logback 的配置文件如何设置:

配置文件

Logback 的配置文件非常灵活,最基本的结构为 元素,包含 0 或多个 元素,其后跟 0 或多个 元素,其后再跟最多只能存在一个的 元素。
image.png

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 秒:

  1. <configuration scan="true" scanPeriod="30 seconds">
  2. ...
  3. </configuration>

注意:如果指定了时间间隔,没有指定时间单位,默认的时间单位为毫秒。

4. 异步输出

使用 Logback 提供的 AsyncAppender 即可实现异步的日志记录。AsyncAppende 类似装饰模式,也就是在不改变类原有基本功能的情况下为其增添新功能。这样,我们就可以把 AsyncAppender 附加在其他的 Appender 上,将其变为异步的。

定义一个异步 Appender ASYNCFILE,包装之前的同步文件日志记录的 FileAppender,就可以实现异步记录日志到文件:

  1. <appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
  2. <appender-ref ref="FILE"/>
  3. </appender>

但在使用 AsyncAppender 我们要注意它的一些参数配置,下面通过源码进行分析:

  1. public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
  2. boolean includeCallerData = false;//是否收集调用方数据
  3. protected boolean isDiscardable(ILoggingEvent event) {
  4. Level level = event.getLevel();
  5. return level.toInt() <= Level.INFO_INT;//丢弃<=INFO级别的日志
  6. }
  7. protected void preprocess(ILoggingEvent eventObject) {
  8. eventObject.prepareForDeferredProcessing();
  9. if (includeCallerData)
  10. eventObject.getCallerData();
  11. }
  12. }
  13. public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {
  14. BlockingQueue<E> blockingQueue;//异步日志的关键,阻塞队列
  15. public static final int DEFAULT_QUEUE_SIZE = 256;//默认队列大小
  16. int queueSize = DEFAULT_QUEUE_SIZE;
  17. static final int UNDEFINED = -1;
  18. int discardingThreshold = UNDEFINED;
  19. boolean neverBlock = false;//控制队列满的时候加入数据时是否直接丢弃,不会阻塞等待
  20. @Override
  21. public void start() {
  22. ...
  23. blockingQueue = new ArrayBlockingQueue<E>(queueSize);
  24. if (discardingThreshold == UNDEFINED)
  25. discardingThreshold = queueSize / 5;//默认丢弃阈值是队列剩余量低于队列长度的20%,参见isQueueBelowDiscardingThreshold方法
  26. ...
  27. }
  28. @Override
  29. protected void append(E eventObject) {
  30. if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
  31. return;
  32. }
  33. preprocess(eventObject);
  34. put(eventObject);
  35. }
  36. private boolean isQueueBelowDiscardingThreshold() {
  37. return (blockingQueue.remainingCapacity() < discardingThreshold);
  38. }
  39. private void put(E eventObject) {
  40. if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
  41. blockingQueue.offer(eventObject);
  42. } else {
  43. putUninterruptibly(eventObject);
  44. }
  45. }
  46. //以阻塞方式添加数据到队列
  47. private void putUninterruptibly(E eventObject) {
  48. boolean interrupted = false;
  49. try {
  50. while (true) {
  51. try {
  52. blockingQueue.put(eventObject);
  53. break;
  54. } catch (InterruptedException e) {
  55. interrupted = true;
  56. }
  57. }
  58. } finally {
  59. if (interrupted) {
  60. Thread.currentThread().interrupt();
  61. }
  62. }
  63. }
  64. }
  • 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. 配置总览

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  3. debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
  4. <configuration scan="true" scanPeriod="60 seconds" debug="false">
  5. <!-- 运行环境,dev:开发,test:测试,pre:预生产,pro:生产 -->
  6. <property name="system_host" value="dev" />
  7. <property file="system.properties" />
  8. <!-- 上下文变量设置,用来定义变量值,其中name的值是变量的名称,value的值时变量定义的值。 通过<property>定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
  9. <property name="CONTEXT_NAME" value="logback-test" />
  10. <!-- 日志文件存放路径设置,绝对路径 -->
  11. <property name="logs.dir" value="/opt/logs" />
  12. <!-- 日志文件存放路径设置,tomcat路径 -->
  13. <property name="logs.dir" value="${catalina.base}/logs" />
  14. <!-- 定义日志文件 相对输入位置 -->
  15. <property name="log_dir" value="log" />
  16. <!-- 日志输出格式设置 -->
  17. <!--
  18. %d{yyyy-MM-dd HH:mm:ss} [%level] - %msg%n
  19. Logger: %logger
  20. Class: %class
  21. File: %file
  22. Caller: %caller
  23. Line: %line
  24. Message: %m
  25. Method: %M
  26. Relative: %relative
  27. Thread: %thread
  28. Exception: %ex
  29. xException: %xEx
  30. nopException: %nopex
  31. rException: %rEx
  32. Marker: %marker
  33. newline:%n
  34. -->
  35. <property name="CUSTOM_LOG_PATTERN"
  36. value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{90} - %msg%n" />
  37. <!-- 上下文名称:<contextName>, 每个logger都关联到logger上下文, 默认上下文名称为“default”。但可以使用<contextName>设置成其他名字,用于区分不同应用程序的记录。
  38. 一旦设置,不能修改。 -->
  39. <contextName>${CONTEXT_NAME}</contextName>
  40. <!-- <appender>是<configuration>的子节点,是负责写日志的组件。 有两个必要属性name和class。name指定appender名称,
  41. class指定appender的实现类。 -->
  42. <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
  43. <!-- 对日志进行格式化。 -->
  44. <encoder>
  45. <pattern>${CUSTOM_LOG_PATTERN}</pattern>
  46. <charset>UTF-8</charset>
  47. </encoder>
  48. </appender>
  49. <appender name="file"
  50. class="ch.qos.logback.core.rolling.RollingFileAppender">
  51. <!-- 按天来回滚,如果需要按小时来回滚,则设置为{yyyy-MM-dd_HH} -->
  52. <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
  53. <fileNamePattern>log/testC.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  54. <!-- 如果按天来回滚,则最大保存时间为30天,30天之前的都将被清理掉 -->
  55. <maxHistory>30</maxHistory>
  56. <!-- 按时间回滚的同时,按文件大小来回滚 -->
  57. <timeBasedFileNamingAndTriggeringPolicy
  58. class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
  59. <maxFileSize>100MB</maxFileSize>
  60. </timeBasedFileNamingAndTriggeringPolicy>
  61. </rollingPolicy>
  62. <!-- 也可以使用下面这个策略-->
  63. <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  64. <!--日志文件保留天数-->
  65. <MaxHistory>30</MaxHistory>
  66. <!--日志文件最大的大小-->
  67. <MaxFileSize>100MB</MaxFileSize>
  68. <!--日志整体最大
  69. 可选的totalSizeCap属性控制所有归档文件的总大小。当超过总大小上限时,将异步删除最旧的存档。
  70. totalSizeCap属性也需要设置maxHistory属性。此外,“最大历史”限制总是首先应用,“总大小上限”限制其次应用。
  71. -->
  72. <totalSizeCap>10GB</totalSizeCap>
  73. </rollingPolicy>
  74. <!-- 过滤器,只记录WARN级别的日志 -->
  75. <!-- 果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
  76. <filter class="ch.qos.logback.classic.filter.LevelFilter">
  77. <!-- 设置过滤级别 -->
  78. <level>WARN</level>
  79. <!-- 用于配置符合过滤条件的操作 -->
  80. <onMatch>ACCEPT</onMatch>
  81. <!-- 用于配置不符合过滤条件的操作 -->
  82. <onMismatch>DENY</onMismatch>
  83. </filter>
  84. <!-- 日志输出格式 -->
  85. <encoder>
  86. <pattern>${CUSTOM_LOG_PATTERN}</pattern>
  87. <charset>UTF-8</charset>
  88. </encoder>
  89. </appender>
  90. <appender name="log_file"
  91. class="ch.qos.logback.core.rolling.RollingFileAppender">
  92. <!-- 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。 -->
  93. <file>${logs.dir}/logback-test.log</file>
  94. <!-- 按照固定窗口模式生成日志文件,当文件大于20MB时,生成新的日志文件。窗口大小是1到3,当保存了3个归档文件后,将覆盖最早的日志 -->
  95. <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
  96. <!-- 必须包含“%i”例如,假设最小值和最大值分别为1和2,命名模式为 mylog%i.log,会产生归档文件mylog1.log和mylog2.log。还可以指定文件压缩选项,例如,mylog%i.log.gz
  97. 或者 没有log%i.log.zip -->
  98. <FileNamePattern>${logs.dir}/logback-test.%i.log</FileNamePattern>
  99. <!-- 窗口索引最小值 -->
  100. <minIndex>1</minIndex>
  101. <!-- 窗口索引最大值 -->
  102. <maxIndex>3</maxIndex>
  103. </rollingPolicy>
  104. <!-- 日志级别过滤器(自定义计算条件) -->
  105. <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
  106. <evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator">
  107. <expression>
  108. e.level.toInt() == WARN.toInt() || e.level.toInt() == INFO.toInt()
  109. </expression>
  110. </evaluator>
  111. <OnMismatch>DENY</OnMismatch>
  112. <OnMatch>NEUTRAL</OnMatch>
  113. </filter>
  114. <!-- 激活滚动的条件。 -->
  115. <triggeringPolicy
  116. class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
  117. <!-- 活动文件的大小,默认值是10MB -->
  118. <maxFileSize>30MB</maxFileSize>
  119. </triggeringPolicy>
  120. <!-- 对记录事件进行格式化。 -->
  121. <encoder>
  122. <pattern>${CUSTOM_LOG_PATTERN}</pattern>
  123. <charset>UTF-8</charset>
  124. </encoder>
  125. </appender>
  126. <!-- 异步输出 -->
  127. <appender name="ASYNC_logback" class="ch.qos.logback.classic.AsyncAppender">
  128. <!-- 控制丢弃日志的阈值,主要是防止队列满后阻塞。默认如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
  129. <!-- <discardingThreshold>0</discardingThreshold> -->
  130. <!-- 更改默认的阻塞队列的深度,该值会影响性能。默认值为256,即内存中最多保存256条日志 -->
  131. <!-- <queueSize>256</queueSize> -->
  132. <!-- 添加附加的appender,最多只能添加一个 -->
  133. <appender-ref ref="log_file" />
  134. </appender>
  135. <!-- 指定包输出路径 -->
  136. <!-- 用来设置某一个 包 或者具体的某一个 类 的日志打印级别、以及指定<appender>, name:用来指定受此logger约束的某一个包或者具体的某一个类。
  137. level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前loger将会继承上级的级别。
  138. additivity:是否向上级logger传递打印信息。默认是true。(这个logger的上级就是上面的root) <logger>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个logger。 -->
  139. <logger name="org.logback.test" level="DEBUG" additivity="true">
  140. <appender-ref ref="stdout" />
  141. </logger>
  142. <!-- 特殊的<logger>元素,是根logger。只有一个level属性,应为已经被命名为"root". level:设置打印级别,大小写无关:TRACE,
  143. DEBUG, INFO, WARN, ERROR, ALL 和 OFF,不能设置为INHERITED或者同义词NULL。默认是DEBUG。 <root>可以包含零个或多个<appender-ref>元素,标识这个appender将会添加到这个loger。 -->
  144. <root>
  145. <level value="WARN" />
  146. <!-- if表达式,需要Janino jar -->
  147. <!-- Janino 2.6.0版本开始,除了janino.jar之外, commons-compiler.jar也需要在类路径中 -->
  148. <if condition='property("system_host").contains("dev")'>
  149. <then>
  150. <appender-ref ref="stdout" />
  151. </then>
  152. </if>
  153. <appender-ref ref="file" />
  154. </root>
  155. </configuration>

参考资料

Logback 的官网上是有一份手册的,非常详细,足足 200 多页,只不过是英文版的。如果英文阅读能力有限的话,可以到 GitHub 上查看翻译的中文版