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日志体系进行了描述,并简单的常见的两个开源框架的日志使用方案进行了描述。

原文地址:https://www.cnblogs.com/daihang2366/p/15201347.html