【从零单排】详解 Log4j + Slf4j 等日志框架(上篇)

上篇

  • 首先,本文会简单介绍日志框架是什么。
  • 其次,本文会讲解 JUL + JCL + Log4j + Slf4j 等如何实际使用。

下篇

  • 然后,本文会探讨当项目中不同 jar 包使用不同日志框架时的兼容性问题。
  • 最后,本文会介绍一个关于日志框架的实际 Debug 的例子。

日志框架 - 综述

打 log 最原始的方法,就是用 System.out.println(msg),直接输出到 console 看。

但是,实际生产实践中,这样有几个问题:

  • 日志太大,想看的东西容易被刷掉。在 console 中也不便于分析查询。
  • 除了 msg 之外,我还想要知道其它信息,比如:时间,这个 msg 来自哪个类等等。

解决方案是:我们不采用System.out,而是使用FileOutputStream,直接输出到一个文件中。并且,默认把 class 名字也当做参数传进去。

以上解决方案,催生出了许多日志框架。主流的日志框架使用有这些:

  • Log4j 1/2
  • Slf4j + Logback
  • Slf4j + Slf4j simple
  • Slf4j + Log4j 1/2
  • Apache Commons Logging + Log4j 1/2

这里,需要说明的是:

日志框架 - 架构

我们使用日志框架的架构如下:

P1

日志框架,相当于一个黑盒子,我们不需要,(大多数情况下)也不关心里面的具体实现,我们和它的交集,主要在3个地方:

  • 引入正确的 jar 依赖
  • 使用正确的配置文件 (如,log4j.properties)
  • 在代码中正确地使用

一个合格的日志框架,需要解决如下问题:

  • WHAT 日志输出什么内容,是 INFO 还是 ERROR ?
  • WHERE 日志输出到哪里,是 console 还是 file ?
  • HOW 日志输出采用什么格式,比如时间戳怎么控制,一个文件的的最大 size 是多少?

以 Log4j 为例,以上分别对应到其核心模块:

  • Logger / Filter
  • Appender
  • Layout

这些,都是体现在配置文件中的。

JDK Logging (JUL)

使用 JDK 自带的 java.util.logging,(不需要额外引入 dependency )
没有配置文件的情况下,会默认输出到控制台。

import java.util.logging.Logger;

public class JdkLogApp {
    public static void main(String[] args) {
        Logger logger = Logger.getGlobal();
        logger.info("JdkLogApp - Info");
        logger.warning("JdkLogApp - Warn");
        logger.severe("JdkLogApp - Severe");
    }
}

输出如下:

四月 24, 2021 10:55:13 上午 JdkLogApp main
信息: JdkLogApp - Info
四月 24, 2021 10:55:13 上午 JdkLogApp main
警告: JdkLogApp - Warn
四月 24, 2021 10:55:13 上午 JdkLogApp main
严重: JdkLogApp - Severe

注:如果有额外的配置文件,可以通过-Djava.util.logging.config.file=<config-file-name>传入。

(引用自参考1)

Apache Commons Logging (JCL)

Apache 提供的日志模块:org.apache.commons.logging,可以挂载不同的日志系统。

The Apache Commons Logging (JCL) provides a Log interface that is intended to be both light-weight and an independent abstraction of other logging toolkits.

实战:JCL 搭配 JDK Logging

实际使用中,首先引入依赖

<!-- common logging -->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

然后使用LogFactory.getLog获取Log对象

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class CommonLogApp {
    public static void main(String[] args) {
        Log log = LogFactory.getLog(CommonLogApp.class);
        log.info("CommonLogApp - Info");
        log.warn("CommonLogApp - Warn");
        log.error("CommonLogApp - Error");
    }
}

结果和使用 JDK Logging 一样,是输出到控制台的。

实战:JCL 搭配 Log4j2

配置 dependency 如下:(另外,还需搭配 log4j2.xml,见下面 Log4j2 章节)

<!-- bridge -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.11.1</version>
</dependency>

<!-- log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>

实战:JCL 搭配 Log4j1

配置 dependency 如下:(另外,还需搭配 log4j.properties,见下面 Log4j 章节)

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

(引用自参考1,2)

Log4j 1

依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

配置 log4j.properties

# output path variable
log = D:/codebase/LogTestSystem/logdir

# Define the root logger with appender file
log4j.rootLogger = INFO, testlog

# Define the file appender
log4j.appender.testlog=org.apache.log4j.FileAppender
log4j.appender.testlog.File=${log}/log.out

# Define the layout for file appender
log4j.appender.testlog.layout=org.apache.log4j.PatternLayout
log4j.appender.testlog.layout.conversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] - [%c] %m%n

使用

import org.apache.log4j.Logger;

public class Log4jApp {
    static Logger log = Logger.getLogger(Log4jApp.class);
    public static void main(String[] args) {
        log.debug("Debug");
        log.info("Info");
        log.error("Error");
    }
}

Log4j 2

依赖

<!-- log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
<!-- NOTE: log4j-core itself contains log4j-api -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>

配置 log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Properties>
        <!-- 定义日志格式 -->
        <Property name="log.pattern">%d{MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}%n%msg%n%n</Property>
        <!-- 定义文件名变量 -->
        <Property name="file.debug.filename">logdir/log2.out</Property>
        <Property name="file.debug.pattern">logdir/log2.out.%i.gz</Property>
    </Properties>
    <!-- 定义Appender,即目的地 -->
    <Appenders>
        <!-- 定义输出到屏幕 -->
        <Console name="console" target="SYSTEM_OUT">
            <!-- 日志格式引用上面定义的log.pattern -->
            <PatternLayout pattern="${log.pattern}" />
        </Console>
        <!-- 定义输出到文件,文件名引用上面定义的file.debug.filename -->
        <RollingFile name="debug" bufferedIO="true" fileName="${file.debug.filename}" filePattern="${file.debug.pattern}">
            <PatternLayout pattern="${log.pattern}" />
            <Policies>
                <!-- 根据文件大小自动切割日志 -->
                <SizeBasedTriggeringPolicy size="1 MB" />
            </Policies>
            <!-- 保留最近10份 -->
            <DefaultRolloverStrategy max="10" />
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="info">
            <!-- 对info级别的日志,输出到console -->
            <AppenderRef ref="console" level="info" />
            <!-- 对error级别的日志,输出到err,即上面定义的RollingFile -->
            <AppenderRef ref="debug" level="debug" />
        </Root>
    </Loggers>
</Configuration>

使用

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

public class Log4j2App {
    static Logger log = LogManager.getLogger();
    public static void main(String[] args) {
        log.debug("Debug");
        log.info("Info");
        log.error("Error");
    }
}

Slf4j + Logback

依赖

<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- logback -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.1.7</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.1.7</version>
</dependency>

配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 应用名称 -->
    <property name="APP_NAME" value="logbackfile" />

    <!--日志文件的保存路径,首先查找系统属性-Dlog.dir,如果存在就使用其;否则,在当前目录下创建名为 logdir 目录做日志存放的目录 -->
    <property name="LOG_HOME" value="${log.dir:-logdir}/${APP_NAME}" />

    <!-- 日志输出格式 -->
    <property name="ENCODER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" />
    <contextName>${APP_NAME}</contextName>

    <!-- 控制台日志:输出全部日志到控制台 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <Pattern>${ENCODER_PATTERN}</Pattern> </encoder>
    </appender>

    <!-- 文件日志:输出全部日志到文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/output.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern>
    </encoder>
    </appender>

    <!-- 错误日志:用于将错误日志输出到独立文件 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory> </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${ENCODER_PATTERN}</pattern> </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter>
    </appender>

    <!-- 独立输出的同步日志 -->
    <appender name="SYNC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/sync.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory> </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${ENCODER_PATTERN}</pattern>
        </encoder>
    </appender>

    <logger name="log.sync" level="DEBUG" addtivity="true">
        <appender-ref ref="SYNC_FILE" />
    </logger>

    <root>
        <level value="DEBUG" />
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />
        <appender-ref ref="ERROR_FILE" />
    </root>

</configuration>

使用

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

public class Slf4j2LogbackApp {
    private static final Logger logger = LoggerFactory.getLogger(Slf4j2LogbackApp.class);
    public static void main(String[] args) {
        logger.debug("Debug");
        logger.info("Info");
        logger.error("Error");
    }
}

Slf4j + Log4j1

依赖

<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j to log4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

配置参考 log4j.properties
使用参考 Slf4j + Logback

Slf4j + Log4j2

依赖

<!-- slf4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

<!-- slf4j to log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.11.1</version>
</dependency>

<!-- log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>

配置参考 log4j2.xml
使用参考 Slf4j + Logback

参考

原文地址:https://www.cnblogs.com/maxstack/p/14697235.html