Java日志

日志门面和日志框架实现

日志门面(Facade)把不同的日志系统的实现进行了抽象化,只提供了统一的日志使用接口。由于它只是一个接口,所以最终日志的格式、记录级别、输出方式等都要通过接口绑定的具体日志框架实现

image-20230612215155120

实际的项目开发中可能会使用到很多不同的框架,而这些框架又使用了不同的日志框架。我们希望所有的框架一律使用日志门面(Slf4j)进行日志打印,这时该怎么去解决?

  • logback直接实现了slf4j-api中的接口,通过接口实现的方式完成了门面的实现。
  • log4j2没有直接实现接口,需要适配器log4j-slf4j-impl

Logback

  1. 添加logback依赖

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

    image-20240807161938032

    可见,依赖中包含了对slf4j-api的依赖。logback实现了slf4j-api中的接口,通过接口实现的方式完成了门面的实现

  2. /resource/logback.xml进行日志配置。

    官网Chapter 3: Configuration (qos.ch)

    Logback can be configured either programmatically or with a configuration script expressed in XML, Groovy or serialized model format.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <configuration>

    <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{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c: %m%n</pattern>
    </encoder>
    </appender>

    <root level="debug">
    <appender-ref ref="STDOUT" />
    </root>
    </configuration>
  3. 编写日志输出,查看依赖以及输出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 导入的是slf4j提供的日志相关类和接口
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class Main {
    private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
    LOGGER.info("logback的日志打印");
    }
    }

    打印效果:

    2024-08-07 16:01:10 [INFO] com.hunter.demo.Main: logback的日志打印


log4j2

单独使用Log4j2日志框架

  1. 添加log4j2的依赖

    1
    2
    3
    4
    5
    6
    <!-- log4j 2 -->
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.23.1</version>
    </dependency>
  2. /resource/log4j2.properties进行日志配置。

    1
    2
    3
    4
    5
    6
    7
    rootLogger.level=info
    rootLogger.appenderRef.stdout.ref=STDOUT

    appender.console.type=Console
    appender.console.name=STDOUT
    appender.console.layout.type=PatternLayout
    appender.console.layout.pattern= %d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n
  3. 编写日志输出,查看依赖以及输出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 导入的都是log4j2自己提供的日志相关类和接口
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;

    public class Main {
    private static final Logger LOGGER = LogManager.getLogger();

    public static void main(String[] args) {
    LOGGER.info("log4j2的日志打印");
    }
    }

    打印效果:

    2024-08-07 16:01:10 [INFO] com.hunter.demo.Main: log4j 2的日志打印


绑定slf4j门面接口

  1. 使用依赖log4j-slf4j-impl

    1
    2
    3
    4
    5
    6
    <!-- 绑定了slf4j日志门面的log4j2日志框架 -->
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j2-impl</artifactId>
    <version>2.23.1</version>
    </dependency>
    image-20240807164618497

    可见,该依赖中包含了对slf4j-api的依赖。

  2. /resource/log4j2.properties进行日志配置。

    1
    2
    3
    4
    5
    6
    7
    rootLogger.level=info
    rootLogger.appenderRef.stdout.ref=STDOUT

    appender.console.type=Console
    appender.console.name=STDOUT
    appender.console.layout.type=PatternLayout
    appender.console.layout.pattern= %d{yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n
  3. 编写日志输出,查看依赖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 导入的是slf4j提供的日志相关类和接口
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class Main {
    private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
    LOGGER.info("log4j2的日志打印");
    }
    }

统一日志标准

项目依赖中使用不同的日志框架

  1. 依赖的项目A使用log4j2框架
  2. 依赖的项目B使用logback框架

所依赖的项目中的日志可以正常打印(使用各自的日志配置进行打印),不会产生冲突

如果日志输出的标准不一致,可以通过在当前项目分别定义对应日志框架的日志配置文件,覆盖依赖的项目中的日志配置文件


项目依赖中都使用slf4j日志门面接口

  1. 依赖的项目A使用绑定了slf4j日志门面的log4j2日志框架

    A 👉 slf4j 👉 log4j2

  2. 依赖的项目B使用logback框架

    B 👉 slf4j 👉 logback

这样会使日志门面接口调用实际实现的时候混乱,因为有多个实现类。会根据类加载顺序指定实际使用的日志框架,并读取相应的日志配置文件。

如果有明确要使用的日志框架,且恰好其加载顺序靠后,要如何指定日志框架

pom.xml中,把非目标日志框架的依赖从主动依赖中**exclude**掉即可。这种情况,日志输出采用的是同一个日志配置文件,所以日志输出标准是一致的。


项目依赖中既有日志框架,又有slf4j日志门面接口

  1. 依赖的项目A使用log4j2框架

  2. 依赖的项目B使用绑定了slf4j日志门面的log4j2日志框架

    B 👉 slf4j 👉 log4j2

  3. 依赖的项目C使用logback框架

    C 👉 slf4j 👉 logback

这个情况,日志门面接口调用实际实现的时候会混乱、日志框架会使用自己的日志配置进行打印。要统一日志标准,需要两步:

  1. 非目标日志框架的依赖从主动依赖中**exclude**掉。

  2. exclude log4j2框架,使依赖中的log4j2也能”实现”slf4j日志门面

    1
    2
    3
    4
    5
    6
    <!-- Log4j 2 API 和 SLF4J 之间的 Apache Log4j 绑定。 -->
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.23.1</version>
    </dependency>

参考

待阅读:手把手教你玩转 SpringBoot 日志 - 爱睡懒觉的我 - 博客园 (cnblogs.com)