首页 文章
取消

Java日志体系摘要

Java存在好几种日志框架,刚学Java以及初入职场时,可能会一头雾水,只知道调用API,而对背后的日志体系与规范完全不了解。Reference中引用的文章就对Java日志体系做了很好的总结,本文结合自身工作经历对其中的知识点做一些简要记录。

Log Facade与Log Implementation

首先明确两个概念:Log Facade与Log Implementation,它们是日志体系的两大组成部分。

  • Log Facade:日志的抽象层,它是一套规范、标准,与JDBC驱动类似,它只提供一系列对外的统一接口,不提供日志功能的具体实现。主要有common-loggingslf4j

  • Log Implementation:实现了Log Facade接口的具有真正日志功能的框架,主要有java.util.logginglog4jLogbackLog4j2

日志选型

一般而言,我们会选择slf4j + Logback的组合,实际上logback-classic已经包含了logback-coreslf4j-api,所以只引入logback-classic即可。如果对logback-coreslf4j-api有特定版本需求,可以参考下面的写法。

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.11</version>
</dependency>

但实际开发中可能会遇到一些不理想的情况:

  • 某些依赖有自己的日志选型,比如spring使用的组合是common-logging + log4j

  • 有些老代码直接调用某个具体的Log Implementation,改造代码很麻烦

针对以上两种情况,我们需要使用桥接器,桥接器的作用是将一个Log Implementation桥接到另一个Log Implementation,如果代码中直接调用了某个Log Implementation的代码,那么桥接器会把它桥接到目标Log Implementation。比如对于spring,我们可以使用logback-ext-spring

<dependency>
    <groupId>org.logback-extensions</groupId>
    <artifactId>logback-ext-spring</artifactId>
    <version>0.1.5</version>
    <exclusions>
        <exclusion>
            <artifactId>logback-classic</artifactId>
            <groupId>ch.qos.logback</groupId>
        </exclusion>
    </exclusions>
</dependency>

我们引入spring-core的时候,就可以排除掉它自带的commons-logging

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.20</version>
    <exclusions>
        <exclusion>
            <artifactId>commons-logging</artifactId>
            <groupId>commons-logging</groupId>
        </exclusion>
    </exclusions>
</dependency>

而针对第二种情况,假设老代码直接调用了Log4j中的类,那么可以使用log4j-over-slf4j桥接到slf4j

<!-- 代码直接调用log4j会被桥接到slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.36</version>
    <exclusions>
        <exclusion>
            <artifactId>slf4j-api</artifactId>
            <groupId>org.slf4j</groupId>
        </exclusion>
    </exclusions>
</dependency>

实践中的原则

实际开发中,日志选型与依赖管理或许并不需要我们来做,但我们还是有必要知道这些原则。

  • 禁止在代码中直接使用Log Implementation中的类,而应该总是调用Log Facade的API,这样才符合面向接口编程的规范,而且一旦需要更换Log Implementation,只需替换依赖即可。

  • Log Implementation不要混用,一个项目应该只用一个Log Implementation。如果引入了使用其他Log Implementation的依赖或者代码,应该使用桥接器,而不是直接引入其他Log Implementation。

  • Log Implementation的依赖应该设置为<scope>runtime</scope><optional>true</optional>scope的作用是防止开发人员在项目中直接使用Log Implementation中的类;optional的作用是如果别的项目B依赖本项目A,那么A的Log Implementation依赖不会被传递到B。

  • 尽量排除第三方依赖中的Log Implementation。上文在讲日志选型时,代码中频繁出现了exclusions标签,就是意在尽可能排除掉第三方依赖中的Log Implementation,因为我们在一开始就确定了使用slf4j + Logback,如果意外引入了其他Log Implementation,那就违背了第二条原则。

  • 尽量使用logger.debug("start process request, url:{}", url);的方式,不要在debug()中进行运算,例如:logger.debug("start process request, url: " + url);,因为如果日志级别高于DEBUG,那么本条日志根本不会打印,却还是执行了不必要的字符串拼接。

Reference

常用开发库 - 日志类库详解