Logback 指南

转自: https://www.baeldung.com/logback

1. 引言

Logback 是 Java 社区中使用最广泛的日志框架之一。 它是其前身 Log4j 的替代品。 Logback 提供了更快的执行速度,提供了更多的配置选项,以及归档旧日志文件的更大灵活性。

在本教程中,我们将介绍 Logback 的架构,并研究如何使用它来改进我们的应用程序。

2. Logback 架构

Logback 架构由三个类组成:Logger(记录器)、Appender(附加器) 和 Layout(布局)。

Logger 是日志消息的上下文。 这是应用程序与之交互以创建日志消息的类。

Appender 将日志消息放在它们的最终目的地。 一个 Logger 可以有多个 Appender。 我们通常认为 Appender 是附加到文本文件的,但 Logback 比这更强。

Layout 准备要输出的消息。 Logback 支持创建用于格式化消息的自定义类,以及现有的健壮配置选项。

3. 设置

3.1. Maven 依赖

Logback 使用 Simple Logging Facade for Java (SLF4J) 作为其原生接口。 在开始记录消息之前,我们需要将 Logback 和 SLF4J 添加到我们的 pom.xml 中:

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.6</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
<scope>test</scope>
</dependency>

Maven 中央仓库有最新版本的 Logback Core最新版本的 slf4j-api

3.2. Classpath

Logback 还需要把 logback-classic.jar 放在运行时 classpath(类路径)。

我们将其添加到 pom.xml 作为测试依赖项:

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

4. 基本示例和配置

让我们从一个在应用程序中使用 Logback 的简单示例开始。

首先,我们需要一个配置文件。 我们将创建一个名为 logback.xml 的文本文件并将其放在类路径中的某个位置:

1
2
3
4
5
6
7
8
9
10
11
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>

接下来,我们需要一个带有 main 方法的简单类:

1
2
3
4
5
6
7
8
9
public class Example {

private static final Logger logger
= LoggerFactory.getLogger(Example.class);

public static void main(String[] args) {
logger.info("Example log from {}", Example.class.getSimpleName());
}
}

此类创建一个 Logger 并调用 info() 以生成日志消息。

当我们运行 Example 时,我们会看到我们的消息记录到控制台:

1
20:34:22.136 [main] INFO Example - Example log from Example

很容易看出为什么 Logback 如此受欢迎; 我们在分分钟配置完并运行。

这个配置和代码给了我们一些关于它是如何工作的提示:

  1. 我们有一个名为 STDOUTappender,它引用了类名 ConsoleAppender
  2. 有一个模式描述了我们的日志消息的格式。
  3. 我们的代码创建了一个 Logger,我们通过 info() 方法将消息传递给它。

现在我们了解了基础知识,让我们仔细看看。

5. **Logger 上下文

5.1 创建上下文

要将消息记录到 Logback,我们从 SLF4J 或 Logback 初始化一个 Logger:

1
2
private static final Logger logger 
= LoggerFactory.getLogger(Example.class);

然后我们使用它:

1
logger.info("Example log from {}", Example.class.getSimpleName());

这是我们的日志上下文。 当我们创建它时,我们把我们的类传递给了LoggerFactory。 这为 Logger 提供了一个名称(还有一个接受String的重载)。

日志上下文存在于与 Java 对象层次结构非常相似的层次结构中:

  1. 一个logger是一个祖先,当它的名字后跟一个点(.),作为一个后代logger的名字的前缀
  2. 当一个logger和一个子项之间没有祖先时,它就是一个父项

例如,下面的 Example 类位于 com.baeldung.logback 包中。 在 com.baeldung.logback.appenders 包中还有另一个名为 ExampleAppender 的类。

ExampleAppenderLoggerExampleLogger 的子项。

所有的Logger都是预定义的根Logger的后代。

Logger 有一个 Level,可以通过配置或 Logger.setLevel() 设置。 在代码中设置级别会覆盖配置文件。

可能的级别按优先顺序排列:TRACEDEBUGINFOWARNERROR。 每个级别都有一个相应的方法,我们用它来记录该级别的消息。

如果 Logger 没有明确分配级别,它会继承其最近祖先的级别。 根logger默认为 DEBUG。 我们将在下面看到如何覆盖它。

5.2. 使用上下文

让我们创建一个示例程序来演示在日志层次结构中使用上下文:

1
2
3
4
5
6
7
8
9
10
11
12
ch.qos.logback.classic.Logger parentLogger = 
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback");

parentLogger.setLevel(Level.INFO);

Logger childlogger =
(ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.baeldung.logback.tests");

parentLogger.warn("This message is logged because WARN > INFO.");
parentLogger.debug("This message is not logged because DEBUG < INFO.");
childlogger.info("INFO == INFO");
childlogger.debug("DEBUG < INFO");

当我们运行它时,我们会看到以下消息:

1
2
20:31:29.586 [main] WARN com.baeldung.logback - This message is logged because WARN > INFO.
20:31:29.594 [main] INFO com.baeldung.logback.tests - INFO == INFO

我们首先检索一个名为 com.baeldung.logbackLogger,并将其转换为 ch.qos.logback.classic.Logger

在下一条语句中设置级别需要一个 Logback 上下文; 请注意,SLF4J 的抽象logger没有实现 setLevel()

我们将上下文的级别设置为 INFO。 然后我们创建另一个名为 com.baeldung.logback.testslogger

最后,我们在每个上下文中记录两条消息以演示层次结构。 Logback 记录 WARNINFO 消息,并过滤 DEBUG 消息。

现在我们使用一下根logger

1
2
3
4
5
6
7
8
9
10
11
12
ch.qos.logback.classic.Logger logger = 
(ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.baeldung.logback");
logger.debug("Hi there!");

Logger rootLogger =
(ch.qos.logback.classic.Logger)LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logger.debug("This message is logged because DEBUG == DEBUG.");

rootLogger.setLevel(Level.ERROR);

logger.warn("This message is not logged because WARN < ERROR.");
logger.error("This is logged.");

当我们执行这个片段时,我们会看到这些消息:

1
2
3
20:44:44.241 [main] DEBUG com.baeldung.logback - Hi there!
20:44:44.243 [main] DEBUG com.baeldung.logback - This message is logged because DEBUG == DEBUG.
20:44:44.243 [main] ERROR com.baeldung.logback - This is logged.

总而言之,我们从一个 Logger 上下文开始并打印了一条 DEBUG 消息。

然后我们使用其静态定义的名称检索根logger,并将其级别设置为ERROR

最后,我们演示了 Logback 确实过滤了任何优先级小于ERROR的语句。

5.3. 参数化消息

与上面示例片段中的消息不同,大多数有用的日志消息都需要附加String。 这需要分配内存、序列化对象、连接String,并可能在以后清理垃圾。

考虑以下消息:

1
log.debug("Current count is " + count);

无论 Logger 是否记录消息,我们都会产生构建消息的成本。

Logback 通过其参数化消息提供了另一种选择:

1
log.debug("Current count is {}", count);

大括号 {} 将接受任何 Object 并仅在验证需要日志消息后使用其 toString() 方法构建消息。

我们尝试一些不同的参数:

1
2
3
4
5
6
7
8
9
10
String message = "This is a String";
Integer zero = 0;

try {
logger.debug("Logging message: {}", message);
logger.debug("Going to divide {} by {}", 42, zero);
int result = 42 / zero;
} catch (Exception e) {
logger.error("Error dividing {} by {} ", 42, zero, e);
}

这个片段产生:

1
2
3
4
5
6
21:32:10.311 [main] DEBUG com.baeldung.logback.LogbackTests - Logging message: This is a String
21:32:10.316 [main] DEBUG com.baeldung.logback.LogbackTests - Going to divide 42 by 0
21:32:10.316 [main] ERROR com.baeldung.logback.LogbackTests - Error dividing 42 by 0
java.lang.ArithmeticException: / by zero
at com.baeldung.logback.LogbackTests.givenParameters_ValuesLogged(LogbackTests.java:64)
...

我们看到了如何将 StringintInteger 作为参数传入。

此外,当异常作为最后一个参数传递给日志记录方法时,Logback 将为我们打印堆栈跟踪。

6.详细配置

在前面的示例中,我们使用在第 4 节中创建的 11 行配置文件将日志消息打印到控制台。 这是 Logback 的默认行为; 如果找不到配置文件,它会创建一个 ConsoleAppender 并将其与根logger相关联。

6.1. 查找配置信息

可以将配置文件放在classpath中并命名为 logback.xmllogback-test.xml

以下是 Logback 尝试查找配置数据的方式:

  1. 按顺序在类路径中搜索名为 logback-test.xmllogback.groovylogback.xml 的文件
  2. 如果库没有找到这些文件,它将尝试使用 Java 的 [ServiceLoader](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html) 来定位 com.qos.logback.classic.spi.Configurator 的实现者。
  3. 配置自身以将输出直接记录到控制台

注意:当前版本的 Logback 不支持 Groovy 配置,因为没有与 Java 9 兼容的 Groovy 版本。

6.2. 基本配置

让我们仔细看看我们的示例配置。

整个文件在 <configuration> 标记中。

我们看到一个标签声明了一个类型为 ConsoleAppenderAppender,并将其命名为 STDOUT。 嵌套在该标签内的是一个编码器。 它有一个看起来像 sprintf 风格的转义码的模式:

1
2
3
4
5
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

最后,我们看到一个根标签。 这个标签将根 logger 设置为 DEBUG 模式,并将其输出与名为 STDOUTAppender 相关联:

1
2
3
<root level="debug">
<appender-ref ref="STDOUT" />
</root>

6.3. 故障排除配置

Logback 配置文件可能会变得复杂,因此有几种内置的故障排除机制。

要在 Logback 处理配置时查看调试信息,我们可以打开调试日志记录:

1
2
3
<configuration debug="true">
...
</configuration>

Logback 将在处理配置时将状态信息打印到控制台:

1
2
3
4
5
6
7
8
9
10
11
12
23:54:23,040 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback-test.xml] 
at [file:/Users/egoebelbecker/ideaProjects/logback-guide/out/test/resources/logback-test.xml]
23:54:23,230 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender
of type [ch.qos.logback.core.ConsoleAppender]
23:54:23,236 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]
23:54:23,247 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type
[ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
23:54:23,308 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to DEBUG
23:54:23,309 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [STDOUT] to Logger[ROOT]
23:54:23,310 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
23:54:23,313 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@5afa04c - Registering current configuration
as safe fallback point

如果在解析配置文件时遇到警告或错误,Logback 会将状态消息写入控制台。

还有第二种打印状态信息的机制:

1
2
3
4
<configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
...
</configuration>

StatusListener 拦截状态消息并在配置期间以及程序运行时打印它们。

打印所有配置文件的输出,这对于在类路径上定位“流氓”配置文件很有用。

6.4. 自动重新加载配置

在应用程序运行时重新加载日志记录配置是一种强大的故障排除工具。 Logback 使用 scan 参数使这成为可能:

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

默认行为是每 60 秒扫描一次配置文件的更改。 我们可以通过添加 scanPeriod 来修改这个间隔:

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

我们可以以毫秒、秒、分钟或小时为单位指定值。

6.5. 修改Loggers

在上面的示例文件中,我们设置了根logger的级别并将其与控制台 Appender 相关联。

我们可以为任何logger设置级别:

1
2
3
4
5
6
7
8
9
10
11
12
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.baeldung.logback" level="INFO" />
<logger name="com.baeldung.logback.tests" level="WARN" />
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>

让我们将它添加到我们的classpath(类路径)并运行代码:

1
2
3
4
5
6
7
8
9
10
11
12
Logger foobar = 
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.foobar");
Logger logger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback");
Logger testslogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.baeldung.logback.tests");

foobar.debug("This is logged from foobar");
logger.debug("This is not logged from logger");
logger.info("This is logged from logger");
testslogger.info("This is not logged from tests");
testslogger.warn("This is logged from tests");

我们看到这个输出:

1
2
3
00:29:51.787 [main] DEBUG com.baeldung.foobar - This is logged from foobar
00:29:51.789 [main] INFO com.baeldung.logback - This is logged from logger
00:29:51.789 [main] WARN com.baeldung.logback.tests - This is logged from tests

通过不以编程方式设置我们的 Loggers 的级别,配置会设置它们; com.baeldung.foobar 从根logger继承 DEBUG。

logger还从根logger继承 appender-ref。 正如我们将在下面看到的,我们可以覆盖它。

6.6. 变量替换

Logback 配置文件支持变量。 我们在配置脚本内部或外部定义变量。 可以在配置脚本中的任何位置指定变量来代替值。

例如,这是 FileAppender 的配置:

1
2
3
4
5
6
7
8
<property name="LOG_DIR" value="/var/log/application" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_DIR}/tests.log</file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>

在配置的顶部,我们声明了一个名为 LOG_DIR 的属性。 然后我们将它用作 appender 定义中文件路径的一部分。

属性在配置脚本的 <property> 标记中声明,但它们也可从外部来源获得,例如系统属性。 我们可以省略本例中的属性声明,并在命令行中设置 LOG_DIR 的值:

1
java -DLOG_DIR=/var/log/application com.baeldung.logback.LogbackTests

我们用 ${propertyname} 指定属性的值。 Logback 将变量实现为文本替换。 变量替换可以发生在配置文件中可以指定值的任何位置。

7. Appenders (附加器)

Loggers 将 LoggingEvents 传递给 AppendersAppender 执行实际的日志记录工作。 我们通常将日志记录视为文件或控制台的内容,但 Logback 的功能远不止于此。 Logback-core 提供了几个有用的 appender

7.1. 控制台Appender

我们已经看到了 ConsoleAppender 的实际应用。 尽管它的名字,ConsoleAppender 将消息附加到 System.outSystem.err

它使用 OutputStreamWriter 来缓冲 I/O,因此将其定向到 System.err 不会导致无缓冲写入。

7.2. FileAppender

FileAppender 将消息附加到文件中。 它支持广泛的配置参数。 让我们在我们的基本配置中添加一个文件附加程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<configuration debug="true">
<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>
</appender>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>tests.log</file>
<append>true</append>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>

<logger name="com.baeldung.logback" level="INFO" />
<logger name="com.baeldung.logback.tests" level="WARN">
<appender-ref ref="FILE" />
</logger>

<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>

FileAppender 通过 <file> 配置了文件名。 <append> 标签指示 Appender 追加到现有文件而不是截断它。 如果我们多次运行测试,我们会看到日志输出附加到同一个文件中。

如果我们从上面重新运行测试,来自 com.baeldung.logback.tests 的消息会发送到控制台和名为 tests.log 的文件。 后代 logger 继承了根 loggerConsoleAppender 的关联及其与 FileAppender 的关联。 附加程序是累积的。

我们可以覆盖这个行为:

1
2
3
4
5
6
7
<logger name="com.baeldung.logback.tests" level="WARN" additivity="false" > 
<appender-ref ref="FILE" />
</logger>

<root level="debug">
<appender-ref ref="STDOUT" />
</root>

addivity 设置为 false 会禁用默认行为。 测试不会输出日志到控制台,它的任何后代也不会。

7.3. 滚动文件附加器

通常,将日志消息附加到同一个文件不是我们需要的行为。 我们希望文件根据时间、日志文件大小或两者的组合来“滚动”。

为此,我们有 RollingFileAppender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<property name="LOG_FILE" value="LogFile" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover -->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>

<!-- keep 30 days' worth of history capped at 3GB total size -->
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>

RollingFileAppender 有一个 RollingPolicy。 在此示例配置中,我们看到了 TimeBasedRollingPolicy

FileAppender 类似,我们为这个 appender 配置了一个文件名。 我们声明了一个属性并使用了它,因为我们将在下面重用文件名。

我们在 RollingPolicy 中定义了一个 fileNamePattern。 这种模式不仅定义了文件的名称,还定义了滚动它们的频率。 TimeBasedRollingPolicy 检查模式并在最精细定义的周期滚动。

例如:

1
2
3
4
5
6
7
8
<property name="LOG_FILE" value="LogFile" />
<property name="LOG_DIR" value="/var/logs/application" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/%d{yyyy/MM}/${LOG_FILE}.gz</fileNamePattern>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>

生效的日志文件是 /var/logs/application/LogFile。该文件在每个月初滚动到 /Current Year/Current Month/LogFile.gz 并且 RollingFileAppender 创建一个新的活动文件。

当归档文件的总大小达到 3GB 时,RollingFileAppender 会按照先进先出的原则删除归档。

有一周、小时、分钟、秒甚至毫秒的代码。 Logback 在这里有一个参考

RollingFileAppender 还内置了对压缩文件的支持。它压缩了我们滚动的文件,因为我们将它们命名为 LogFile.gz
TimeBasedPolicy 并不是我们滚动文件的唯一选择。 Logback 还提供 SizeAndTimeBasedRollingPolicy,它将根据当前日志文件大小和时间滚动。它还提供了一个 FixedWindowRollingPolicy,每次启动logger时都会滚动日志文件名。

我们也可以编写自己的 [RollingPolicy](https://logback.qos.ch/manual/appenders.html#onRollingPolicies)

7.4.自定义附加器

我们可以通过扩展 Logback 的基本 appender 类的其中一个来创建自定义 appender

8. Lyouts(布局)

布局格式日志消息。与 Logback 的其余部分一样,布局是可扩展的,我们可以创建自己的。但是,默认的 PatternLayout 提供了大多数应用程序需要的布局。

到目前为止,我们在所有示例中都使用了 PatternLayout

1
2
3
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>

此配置脚本包含 PatternLayoutEncoder 的配置。 我们将一个编码器传递给我们的 Appender,这个编码器使用 PatternLayout 来格式化消息。

<pattern> 标签中的文本定义了日志消息的格式。 **PatternLayout 实现了用于创建模式的大量转换词和格式修饰符。**

让我们分解一下 PatternLayout% 识别转换词,因此我们的模式中的转换生成:

  • %d{HH:mm:ss.SSS} – 带有小时、分钟、秒和毫秒的时间戳
  • [%thread] – 生成日志消息的线程名称,用方括号括起来
  • %-5level – 日志事件的级别,填充为 5 个字符
  • %logger{36}logger的名称,截断为 35 个字符
  • %msg%n – 后跟平台相关行分隔符的日志消息

所以我们看到类似这样的消息:

1
21:32:10.311 [main] DEBUG com.baeldung.logback.LogbackTests - Logging message: This is a String

可以在此处找到转换词和格式修饰符的详尽列表。

9. 结论

在这篇内容广泛的文章中,我们介绍了在应用程序中使用 Logback 的基础知识。

我们查看了 Logback 架构中的三个主要组件:LoggerAppenderLayout。 Logback 有强大的配置脚本,我们用它来操作组件来过滤和格式化消息。 我们还讨论了用于创建、翻转、组织和压缩日志文件的两个最常用的文件附加程序。

像往常一样,可以在 GitHub 上找到代码片段。