1.前言

​说到日志工具,日常工作或学习中肯定听过这些名词:log4j、logback、jdk-logging、slf4j、commons-logging等,它们之间有什么关系,在整个日志体系中又扮演什么角色呢?
​日志框架分为三大类,包括日志门面、日志适配器、日志库。利用门面设计模式,即Facade来进行解耦,使日志使用变得更简单。

日志系列1——slf4j日志框架原理-LMLPHP

2.日志门面

​门面设计模式是面向对象设计模式中的一种,日志框架采用的就是这种模式,类似JDBC的设计理念。它只提供一套接口规范,自身不负责日志功能的实现,目的是让使用者不需要关注底层具体是哪个日志库来负责日志打印机具体的使用细节等。目前用得最为广泛的日志门面有两种:slf4j和commons-logging

3.日志库

​它巨头实现了日志的相关功能,主流的日志库有三个,分别是log4j、log-jdk、logback。最早Java想要记录日志只能通过System.out或System.err来完成,非常不方便。log4j就是为了解决这一问题而提出的,它是最早诞生的日志库。接着JDK也在1.4版本引入了一个日志库java.util.logging.Logger,简称log-jdk。这样市面上就出现了两种日志功能的实现,开发者在使用时需要关注所使用的日志库的具体细节。logback是最晚出现的,它与log4j出自同一个作者,是log4j的升级版且本身就实现了slf4j的接口。

4.日志适配器

日志适配器分为两种场景:

  1. 日志门面适配器,因为slf4j规范是后来提出的,在此之前的日志库是没有实现slf4j的接口的,例如log4j.所以,在工程里要想使用slf4j+log4j的模式,就额外需要一个适配器(slf4j+log4j12)来解决接口不兼容的问

  2. 日志库适配器,在一些老的工程里,一开始为了开发简单而直接使用了日志库API来完成日志打印,随着时间的推移想将原来直接调用日志库的模式改为业界标准的门面模式(例如slf4j+logback组合),但老工程代码里打印日志的地方太多,难以改动,所以需要一个适配器来完成从旧日志库的API到slf4j的路由,这样在不改动原有代码的情况下也能使用slf4j来统一管理日志,而且后续自由替换具体日志库也不是问题。

5.日志库的选用

​如果是新工程,则推荐使用slf4j+logback模式,因为logback自身实现了slf4j的接口,不需要额外引入适配器,另外logback是log4j的升级版,具备比log4j更多的优点,可通过如下配置进行集成:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j-api.version}</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>${logback-core.version}</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback-classic.version}</version>
</dependency>

​如果是老工程,则需要根据所使用的日志库来确定门面适配器,通常情况下老工程使用的都是log4j,因此以log4j日志库为例,可通过如下配置进行集成:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>${slf4j-api.version}</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>${slf4j-log4j12.version}</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>${log4j.version}</version>
</dependency>

​如果老代码直接使用了log4j日志库提供的接口来打印日志,则还需要引入日志库适配器,配置实例如下所示:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>${log4j-over-slf4j.version}</version>
</dependency>

6.logback.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true">
	<property name="application_name" value="web" />
    <property name="LOG_PATH" value="c:" />
	 <!-- 控制台输出 -->
        <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
            <encoder charset="UTF-8">
                <pattern>%date %-5level %logger{80} [%X{trans_id}] - %msg%n</pattern>
                <!-- <pattern>%date [%thread] %-5level %logger{80} - %msg%n</pattern> -->
            </encoder>
        </appender>
        <!-- 时间滚动输出 文件日志 -->
        <appender name="file—debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>debug</level>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <FileNamePattern>${LOG_PATH}/logs/${application_name}/${application_name}_debug.%d{yyyy-MM-dd}_%i.log</FileNamePattern>
                <MaxHistory>100</MaxHistory>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>10mb</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <encoder charset="UTF-8">
                <pattern>%date [%thread] %-5level %logger{80} [%X{trans_id}] - %msg%n</pattern>
            </encoder>
        </appender>
    <!-- 时间滚动输出 文件日志 -->
    <appender name="file—info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/logs/${application_name}/${application_name}_info.%d{yyyy-MM-dd}_%i.log</FileNamePattern>
            <MaxHistory>100</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10mb</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder charset="UTF-8">
            <pattern>%date [%thread] %-5level %logger{80} [%X{trans_id}] - %msg%n</pattern>
        </encoder>
    </appender>
        <!-- 时间滚动输出 level为 ERROR 日志 -->
        <appender name="file—error" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <FileNamePattern>${LOG_PATH}/logs/${application_name}/${application_name}_error.%d{yyyy-MM-dd}_%i.log</FileNamePattern>
                <MaxHistory>100</MaxHistory>
                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>10mb</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
            </rollingPolicy>
            <encoder charset="UTF-8">
                <pattern>%date [%thread] %-5level %logger{80} [%X{trans_id}] - %msg%n</pattern>
            </encoder>
        </appender>


    <Logger name="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" level="DEBUG" additivity="false">
        <appender-ref ref="file—debug" />
    </Logger>

    <appender name="ASYNC-ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>256</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref="file—error"/>
    </appender>

        <root level="info">
            <appender-ref ref="stdout" />
            <appender-ref ref="file—info" />
            <appender-ref ref="file—error" />
        </root>

</configuration>

实例代码如下:

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

​注意,logger对象被定义为static变量,这是因为这个logger与当前类绑定,避免每次都new一个新对象,造成资源浪费,甚至引发OutOfMemoryError问题。

​在使用slf4j+具体日志库模式时,由于slf4j相当于充当api抽象接口,所以我们的日志打印是也是面向接口编程的,当我们需要更换具体的日志库时,我们只需要引入具体的maven依赖就可以了,并对原有的日志库依赖进行移除,而不需要改动代码。至此,slf4j的架构原理讲解完成,之后会对具体的日志库logback的配置文件进行讲解,本章只是先简单给出logback.xml配置文件的基本模板,下一章节《日志系列2——logback配置文件详解》敬请期待。

05-14 16:54