• 【从零单排】详解 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

    参考

  • 相关阅读:
    openresty动态转发
    k8s证书永不过期
    ES常用操作
    pandas笔记
    Python判断两个list 内容是否相同忽略顺序
    Promox 创建集群
    docker 发布selemiumgrid分布式测试
    详谈selenium3.x 升级到selenium4.1.2
    antdmobile 移动端使用ImagePicker手机上拍照后反显时图片会旋转90度问题
    谷歌精确搜索
  • 原文地址:https://www.cnblogs.com/maxstack/p/14697235.html
Copyright © 2020-2023  润新知