• Java日志浅谈


    1、Java日志体系概述

    老话都说,Java日志体系百花齐放,各式各类的日志很多并且繁杂,那么本片博客带你理清这些日志。

    Java日志中体系,比较老牌的就是jcl、log4j、jul、logback、slf4j,相信这些日志你都听说过,而且并不陌生。那么我下面来挨个介绍这些日志框架。

    这些日志框架各有各的特色,我们一般开发的业务系统中都是使用指定一个日志框架,但一个高扩展的项目,是不会仅限于使用指定单独一个日志框架的,下面会针对mybatis来解释这个东西。

    1.1、JUL

    叫JUL的原因是这个日志框架所处的包是:java.util.lang下的java.util.logging.Logger。使用方法如下:

    Logger log = Logger.getLogger();
    log.info(消息);
    log.log();
    

    这个没什么好说的,但是需要注意,我们的JUL没有debug,这个比较蛋疼,所以大部分时候我们是不使用JUL的。

    1.2、Log4j

    引入依赖:

    <dependency>
        <groupId>com.att.inno</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.13</version>
    </dependency>
    

    加入log4j.properties:

    ### 设置###
    log4j.rootLogger = debug,stdout,D,E
    
    ### 输出信息到控制抬 ###
    log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target = System.out
    log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
    
    ### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
    log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.D.File = E://logs/log.log
    log4j.appender.D.Append = true
    log4j.appender.D.Threshold = DEBUG 
    log4j.appender.D.layout = org.apache.log4j.PatternLayout
    log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
    
    log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
    log4j.appender.E.File =E://logs/error.log 
    log4j.appender.E.Append = true
    log4j.appender.E.Threshold = ERROR 
    log4j.appender.E.layout = org.apache.log4j.PatternLayout
    log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
    

    使用:

    public static void main(String[] args) {
        Logger logger = Logger.getLogger("log4j");
        logger.debug("哈哈哈哈哈");
    }
    

    这个没什么好说的,很常见的日志。

    1.3、Logback

    引入依赖:

    <dependency>
         <groupId>ch.qos.logback</groupId>
         <artifactId>logback-classic</artifactId>
         <version>1.0.6</version>
    </dependency>
    

    配置文件logback.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="10 seconds" debug="true">
        <property name="LOG_HOME" value="${catalina.home}/logs/loan"/>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
        <root level="debug">
            <appender-ref ref="STDOUT"/>
        </root>
    </configuration>
    

    使用:

    public static void main(String[] args) {
        Logger logback1 = LoggerFactory.getLogger("Logback1");
        logback1.debug("哈哈哈哈哈");
    }
    

    注意,LoggerFactory是sfl4j的,那么你可能会疑问,为什么不是logback的,此处我们看看刚才引入的依赖:

    可以看到,其引入了slf4j,这个我们下面说到slf4j的时候详细说。

    1.4、JCL

    全名:Jakarta Commons-logging(JCL),老牌日志框架了,提供了很简单的日志功能以及其解耦。注意,这里的解耦,我们下面慢慢说:

    先来操作JCL:

    引入依赖:

    <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.2</version>
            </dependency>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging-api</artifactId>
                <version>1.1</version>
            </dependency>
    

    调用执行:

    public static void main(String[] args) {
        Log jcl1 = LogFactory.getLog("jcl1");
        jcl1.info("哈哈哈哈哈");
    }
    

    这个代码没问题,然后你尝试调用debug方法,你会发现无任何日志出现。

    这是因为此时JCL是使用的JUL去打打印日志,而JUL是没有debug级别的,所以当调用debug的时候,没有任何输出日志。

    我们先不忙着去讨论其原因,我们再来看一个现象。

    我们引入log4j的依赖:

    当前的全部依赖如下:

    <dependency>
        <groupId>com.att.inno</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.13</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging-api</artifactId>
        <version>1.1</version>
    </dependency>
    

    然后加入log4j配置文件,这里就不再赘述了。

    然后去调用debug打印日志:

    你会发现,日志出来了,而且你仔细看你的log4j配置文件的打印格式,你会发现这个日志就是log4j的日志。

    那么我们有初步的判断,jcl它本身自己不打印日志,在我们没引入其他第三方日志的时候,其使用的是jul的日志实现去打印日志,而当我们引入第三方日志log4j的时候,就会使用log4j的日志实现去打印。

    其重点的方法在于org.apache.commons.logging.LogFactory.getLog方法,我们进入其源码查看:

    public static Log getLog(String name) throws LogConfigurationException {
            return getFactory().getInstance(name);
        }
    

    此时的getFactory是获取到LogFactory的实现类,返回结果为:org.apache.commons.logging.impl.LogFactoryImpl,我们现在查看其getInstance方法:

    public Log getInstance(String name) throws LogConfigurationException {
        Log instance = (Log) instances.get(name);
        if (instance == null) {
            instance = newInstance(name);
            instances.put(name, instance);
        }
        return instance;
    }
    

    instances是一个集合,里面存放的是已经创建了的指定name的日志对象,如果这个name的日志对象已经创建,则不用再去new了。

    我们这里第一次调用,肯定为null,所以我们进入newInstance方法:

    注意看这个discoverLogImplementation方法,这个方法就是实际的实现代码:

    注意这里有findUserSpecifiedLogClassName这个是如果我们自己指定了非要用某个日志实现类的话,那么这个返回值就会有值,且为我们指定的实现类名,那么这个if里面就会把我们指定的实现类名给new出来,然会给我们。

    但是如果我们没有指定(现在就是没有指定的情况),那么这个if就进不去,继续往下面走:

    我们看这个for循环,其意思就是,当classesToDiscover的长度小于i 并且 result 等于空时,循环继续进行, 那么classesToDiscover这个数组里面放的是什么呢?

    我们来看一下:

    这个数组里面包含了四个元素,每个元素对应某一个日志框架的实现框架,例如第一个里面的Log4jLogger,这个类则可以使用log4j的日志类来进行日志打印 ,相对应下面的三个分别对应jul,jdk1.3以及以前的jul,简单的日志实现,一般来说,我们大多使用log4j。

    那么回到上面的for循环里面来,这里面大概意思就是从上到下挨个实例化,如果实例化成功,那么result则不为null,那么则继续循环下来,直到数组结束或者实例化成功,如果循环结束还没实例化成功,那么则抛出异常。

    此上就是对jcl的解释。

    1.5、slf4j

    slf4j也是目前比较常见的一款日志框架,但注意,slf4j并没有单独的日志实现,仅仅是提供了日志的解耦操作。

    具体解释来看官网:

    注意看这里,官网已经明确说明,slf4j充当各种日志框架的简单外观或抽象,说白了,就是可以让我们自由的选择所需要的日志框架,具体如何让我们能自由的选择,我们下面来解释。

    引入依赖:

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

    运行代码:

    public static void main(String[] args) {
        Logger slf4j1 = LoggerFactory.getLogger("slf4j1");
        slf4j1.debug("哈哈哈哈");
    }
    

    你运行后,会发现,并没有日志,报错信息倒是有:

    意思就是不能加载xxxx类,说直白一点,就是我们没有加入任何绑定器,这里就涉及到我们上面说的“自由选择”,slf4j就是使用“绑定器”来让我们实现自由选择的,此时我们加入一个依赖,这个依赖就是对应log4j的绑定器:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.6</version>
    </dependency>
    

    然后加入log4j.properties文件后,运行上面的代码:

    你会发现其按照log4j配置的格式进行输出日志了,那么这里说明,当我们加入了log4j的绑定器以后,日志就使用了log4j的日志实现,那么这里当我们切换为logback的绑定器以后,那么就会使用了logback的日志实现,那么这就体现出来了,slf4j使用绑定器让我们实现了自由选择。

    2、Mybatis日志源码解析

    注意我们的myabtis作为一个优秀的开源框架,日志方面肯定不会给写死,而是拥有解耦的操作,类似于JCL的操作。

    我们看myabtis依赖中的LogFactory类:

    这个类就是mybatis的日志工厂,注意这个类中,获取log的方法如下:

    注意,当获取log的时候,则使用构造器类去创建一个对象返回回去,那么我们这里就可以来考虑看一下,这个logConstructor是如何被赋值的,掌握这个的话那就知道了myabtis是如何实现日志的。

    我们看上面的是static代码块中:

    传进去的是一个Runnable,我们看看这个tryImplementation方法:

    这里判断,如果logConstructor为null的话,那么则说明当前logConstructor还没有值,那么则需要继续执行,我们现在回到useSlf4jLogging方法中去:

     public static synchronized void useSlf4jLogging() {
        setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
      }
    

    这里我们不需要详细的去看其实现规则,我们只需要知道,如果引入了slf4j的依赖,那么前面我们的logConstructor的值就是对接slf4j的日志实现类,如果没引入slf4j的依赖,那么logConstructor继续为null,那么就继续走static下面的useCommonsLogging方法、useLog4J2Logging方法等,直到logConstructor不为空,或者方法执行完。

    这里大概的就知道了我们myabtis如何去使用日志方案的。

    3、Spring5使用哪种日志

    我们Spring5里面的日志使用的是spring-jcl模块,其自定义了jcl的日志操作,但其本质未变,我们在spring5任何一个类的日志打印操作下,我们能看到终究其会到达我们的spring-jcl模块的这个类的这个方法里面来:

    这里对logApi的值进行switch,如果为LOG4J,那么则返回与log4j对应对接的日志实现类,相应的下面分别为slf4j,jul。

    那么注意,我们这个logApi在哪里赋值的呢?这里是在LogFactory的static代码块中进行初始化的:

    注意这里我们的logApi默认设置为JUL这个值。

    然后在static代码块里面分别的去loadClass这些类(如果引入了相应的依赖,那么则会loadClass成功):log4j、slf4j,如果一直走的catch,那么说明这些依赖都不存在,那么则为默认的JUL。

    到此我们简单的对java日志体系进行了描述,并简单的常见的两个开源框架的日志使用方案进行了描述。

  • 相关阅读:
    SDN第三次作业
    SDN第二次上机作业
    SDN第二次作业
    第七次作业之总结篇
    第八次_计算器重构
    第六次作业之计算器图形界面化
    C++课程 second work _1025
    第五次作业--计算器项目之学习文件读取方式
    C++课程 first work
    第四次作业-计算功能的实现
  • 原文地址:https://www.cnblogs.com/daihang2366/p/15201347.html
Copyright © 2020-2023  润新知