slf4j

第五十三章 SLF4J日志库

SLF4J是目前最为流行的Java日志库。它的全名为Simple Logging Facade for Java。它仅是Java日志功能的外观层(Facade);它并没有实现任何记录日志的功能。因此,在实际项目中,开发人员还需要整合一个日志实现库(例如:log4j2或者Logback)。因为SLF4J提供了统一的接口,所以,开发人员不需要直接使用log4j2或者Logback。我们将在本章中着重介绍SLF4J的原理和使用方法。

1. 简介

SLF4J可以与log4j2、Logback、Java标准库JUL(java.util.logging)等许多流行的日志库结合在一起使用。SLF4J通过一个"绑定库"来绑定具体的日志库。如下图所示,如果SLF4J和Logback结合使用的话,在发布Java项目时还需要logback-classic.jar和logback-core.jar。

图一 SLF4J应用结构图

图一 SLF4J应用结构图

使用SLF4J的优势有:

  1. SLF4J独立于日志实现库。因此,使用SLF4J的程序可以和流行的log4j2、JUL、或者Logback整合。
  2. 在完成业务逻辑时,不需要考虑使用哪个日志实现库。日志实现库的选择可在发布产品时决定。
  3. SLF4J提供方便的日志记录接口和方法。
  4. SLF4J提供工具帮助开发人员将应用程序迁移至SLF4J。

如果项目使用SLF4J和Logback日志库并且使用Maven管理时,需要添加如下依赖关系。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>2.0.0-alpha1</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.3.0-alpha5</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-core</artifactId>
  <version>1.3.0-alpha5</version>
</dependency>

如果项目使用SLF4J和Logback日志库并且使用Gradle管理时,需要添加如下依赖关系。

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.slf4j:slf4j-api:2.0.0-alpha1'
  implementation 'ch.qos.logback:logback-classic:1.3.0-alpha5'
  implementation 'ch.qos.logback:logback-core:1.3.0-alpha5'
}

如果当SLF4J没有绑定任何记录库时,SLF4J并不会输出任何日志记录。

2. 使用方法

2.1 基本概念

SLF4J代码库包含五个重要的概念。

  1. LoggerFactory类用于创建Logger对象的工厂类。
  2. Logger类用于记录日志消息。
  3. Handler类用于处理日志消息。当Logger对象收到一条日志消息时,将会交给Handler对象处理。
  4. Formatter类用于格式化日志消息。
  5. Filter类用于过滤日志消息

图二 日志消息处理流程图

图二 日志消息处理流程图

2.2 日志级别

SLF4J支持以下日志级别。

级别名称级别描述
FATAL用于记录特别严重的错误信息,这些错误可能导致应用程序停止服务。
ERROR用于记录运行时的错误。
WARN用于记录警告信息。这些信息用于提示开发人员,应用程序可能会出现错误。
INFO用于记录事件信息。这些信息用于记录应用程序发生的重要事件。
DEBUG用于记录调试信息。例如,记录应用程序的逻辑处理流程。
TRACE用于记录细节信息,较为不重要的信息。

2.3 SLF4J + Logback配置

SLF4J并不需要任何配置就可以使用。但是当SLF4J与Logback结合使用时,开发人员需要为Logback提供配置信息。

2.3.1 Logback配置

Logback支持通过配置文件设置配置信息和在程序中设置配置信息两种方式。如果开发人员使用配置文件的方式配置Logback,那么,开发人员需要准备一个XML格式的配置文件。当应用程序初始化时,Logback会按照如下顺序查找并读取这个配置文件。

  1. 在CLASSPATH路径下查找logback-test.xml配置文件。
  2. 如果步骤1失败,则在CLASSPATH路径下查找logback.groovy配置文件。
  3. 如果步骤2失败,则在CLASSPATH路径下查找logback.xml配置文件。
  4. 如果步骤3失败,则在CLASSPATH路径下查找META-INF/services/ch.qos.logback.classic.spi.Configurator。
  5. 如果步骤4失败,Logback会使用默认的配置。

值得注意的是,如果开发人员使用Maven管理项目,并且将logback-test.xml放在src/test/resources目录下,logback-test.xml文件可能不会包含在最终的包中。在这种情况下,开发人员需要使用另一个文件,例如:logback.xml,用于打包和产品发布。

Logback还提供了通过系统参数来设置配置文件的路径。如下面的例子所示,在运行Java程序时,开发人员可以使用logback.configurationFile来设置配置文件路径。

java -Dlogback.configurationFile=/path/to/logback.xml Example

Logback的配置文件logback.xml有着灵活的结构。下面的例子展示了一个基本的配置例子。在logback.xml文件中,其根节点为<configuration>。根结点可以包含0个或者多个<appender>节点,0个或者多个<logger>节点,和最多一个<root>节点。

<appender>节点用于配置日志消息处理类,其功能类似于Handler。<appender>节点包含0个或者1个<layout>节点,0个或者多个<encoder>,和0个或者多个<filter>。它们用于格式化和过滤日志消息。

<logger>节点可用于基于日志级别的过滤。<root>节点则设置logger的根结点(root logger)。根据Java工程中包的等级结构(Package Structure),开发人员可以为每个包设置logger。因此,从概念上看,"java"包的logger是"java.util"包的logger的父亲。"java.util"包的logger是"java.util.Vector"类的logger的父亲。因此,logger的根节点就是这个logger树的顶层根结点。每个logger可以设置一个级别。如果logger未设置级别的话,它将从它的父亲继承。以此类推,直到logger的根节点。

每条日志消息也会设置一个日志级别(例如:INFO、DEBUG等),只有当日志的级别不低于logger的级别时,日志消息才会被记录。换句话说,如果日志消息的级别低于logger的级别,该条日志消息会被丢弃。日志级别的高低如下所示。

TRACE < DEBUG < INFO < WARN < ERROR < FATAL

因此,当将logger的级别设置在INFO时,TRACE和DEBUG级别的日志消息会被丢弃。在日志文件中只会出现INFO、WARN和ERROR级别的消息。

如下是一个logback.xml的简单示例。该配置文件仅设置了logger根节点的级别为debug,并且使用<appender-ref>将其与<appender>关联起来。其意义是,但凡由logger根节点输出的日志消息都由这个<appender>格式化输出。

<configuration debug="true"> 
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- encoders are by default assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <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>

小水滴提供了使用SLF4J+Logback日志功能的项目模板,方便感兴趣的读者下载试用:MavenGradle

2.4 使用示例

SLF4J的使用方法非常简单。首先,使用LoggerFactory类的静态方法getLogger()创建Logger对象。getLogger()方法接收一个名称或者一个Class<?>类对象。然后,开发人员可以使用Logger类中的info()、warn()、trace()、error()和debug()方法记录相应级别的日志消息。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

  public static void main(String[] args) {
    logger.info("This is an SLF4J example.");
  }
}

如果结合Logback日志库,上述的程序记录的日志消息如下。这条消息包含了日志时间戳、日志级别、类名、所在的方法名和日志消息。

2020-10-13 19:22:23,639 INFO com.littlewaterdrop.Example [main] This is an SLF4J example.

开发人员常常使用当前类来创建Logger对象。如在上例中,在Example类中使用Example.class创建Logger对象。SLF4J提供了一个方便的功能,如果开发人员将系统属性slf4j.detectLoggerNameMismatch设置为true,那么,SLF4J会检查getLogger()方法的参数。如果参数的类与所在的类不同的话,SLF4J会报告一条警告消息"Detected logger name mismatch."。

package com.littlewaterdrop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Example {
  public static void main(String[] args) {
   System.setProperty("slf4j.detectLoggerNameMismatch", "true");
  
    Logger logger = LoggerFactory.getLogger(String.class);
    logger.info("This is an SLF4J example.");
  }
}

假设我们将String的类对象传入getLogger()方法后,上述程序会生成如下提示消息。

SLF4J: Detected logger name mismatch. Given name: "java.lang.String"; computed name: "com.littlewaterdrop.Example".

2.5 {}占位符({}-Placeholder)

使用{}占位符能帮助开发人员方便的生成记录字符串。例如,开发人员可以在记录日志时传入参数,SLF4J会按照顺序将参数替代{}占位符。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Example {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(Example.class);
    logger.info("This is an SLF4J example in class {}.", "Example");
  }
}

上述程序会输出如下类似的日志记录。

2020-10-13 19:22:23,639 INFO com.littlewaterdrop.Example [main] This is an SLF4J example in class Example.

Logger类的info()、debug()、error()、warn()、trace()方法接收多个参数,参数类型为Object。

void info(String msg);
void info(String format, Object arg);
void info(String format, Object... arguments);
void info(String format, Object arg1, Object arg2);
void debug(String msg);
void debug(String format, Object arg);
void debug(String format, Object... arguments);
void debug(String format, Object arg1, Object arg2);
void error(String msg);
void error(String format, Object arg);
void error(String format, Object... arguments);
void error(String format, Object arg1, Object arg2);
void warn(String msg);
void warn(String format, Object arg);
void warn(String format, Object... arguments);
void warn(String format, Object arg1, Object arg2);
void trace(String msg);
void trace(String format, Object arg);
void trace(String format, Object... arguments);
void trace(String format, Object arg1, Object arg2);

2.6 记录Throwable

SLF4J还提供了方便的接口记录Throwable对象。这个方法可用于抓捕和记录异常对象的信息,非常有用。

void info(String msg, Throwable t);
void debug(String msg, Throwable t);
void error(String msg, Throwable t);
void warn(String msg, Throwable t);
void trace(String msg, Throwable t);

例如:在下面的例子中,开发人员直接将Exception对象传入logger.error()方法。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Example {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(AnotherClass.class);

    try {
      Thread.sleep(1000);
    } catch (IllegalArgumentException | InterruptedException ex) {
      logger.error("Sleep interruped.", ex);
    }
  }
}

3. 小结

本章介绍了SLF4J的基本原理和使用方法。因为SLF4J是日志外观层,独立于日志记录库。因此,开发人员在开发时,无需在源代码中明确指出使用哪个日志库。使用的日志库在程序发布时才需确定下来。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.