前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring周边:日志——中

Spring周边:日志——中

作者头像
WEBJ2EE
发布2019-11-26 10:53:45
4250
发布2019-11-26 10:53:45
举报
文章被收录于专栏:WebJ2EEWebJ2EE

有日志么?

日志有价值么?

日志框架了解么?

“日志”这个词最早见于航海领域,是记录航行主要情况的载体文件,内容包括操作指令、气象、潮流、航向、航速、旅客、货物等,是处理海事纠纷或者海难的原始依据之一。尔后延伸到航空领域,黑匣子就是一个重要的航空日志载体,调查空难原因时第一反应是找到黑匣子,并通过解析其中的日志信息来还原空难的事实真相 码出高效:Java开发手册

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

本期内容提要

Apache Commons Logging(JCL)

JUL、Log4j1、Log4j2、Logback

的集成原理

1. 前置知识——SPI

SPI ,全称为 Service Provider Interface,是一种服务发现机制。是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

1.1. 规则

  1. 当服务提供者提供了接口的一种具体实现后,在 jar 包 META-INF/services 目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  2. 接口实现类所在的 jar 包放在主程序的 classpath 中;
  3. 主程序通过 java.util.ServiceLoder 动态装载实现模块,它通过扫描 META-INF/services 目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  4. SPI 的实现类必须携带一个不带参数的构造方法;

1.2. 示例

接口:

代码语言:javascript
复制
package webj2ee.spi;

public interface SPIService {
    void execute();
}

实现类:

代码语言:javascript
复制
package webj2ee.spi;

public interface SPIService {
    void execute();
}
代码语言:javascript
复制
package webj2ee.spi;

public class SPIImpl2 implements SPIService {
    @Override
    public void execute() {
        System.out.println("SPIService Impl 2...");
    }
}

SPI配置:webj2ee.spi.SPIService

代码语言:javascript
复制
webj2ee.spi.SPIImpl1
webj2ee.spi.SPIImpl2

测试类:

代码语言:javascript
复制
import webj2ee.spi.SPIService;

import java.util.ServiceLoader;

public class SPIServiceTest {
    public static void main(String[] args) {
        ServiceLoader<SPIService> services = ServiceLoader.load(SPIService.class);
        for (SPIService s : services) {
            s.execute();
        }
    }
}

1.3. 原理分析

a. ServiceLoader 核心成员变量:

b. ServiceLoader 构造器,内部构建懒加载迭代器;

c. LazyIterator 主体结构;

d. LazyIterator 的 hasNextService() 原理

e. LazyIterator 的 nextService() 原理

2. JCL 深度分析

Apache Commons Logging(JCL) 是 Apache 下的开源项目,是 Apache 提供的日志的门面接口。提供简单的日志实现以及日志解耦功能。

2.1. POM 依赖

代码语言:javascript
复制
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

2.2. 核心接口

JCL 日志门面对外暴露两个接口:

  • org.apache.commons.logging.Log:日志对象
  • org.apache.commons.logging.LogFactory:日志对象工厂
代码语言:javascript
复制
package webj2ee;

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

public class JCL {
    private static Log logger = LogFactory.getLog(JCL.class);

    public static void main(String[] args) {
        logger.info("From jcl's info msg!");
        logger.warn("From jcl's warn msg!");
        logger.error("From jcl's error msg!");
    }
}

JCL 日志对象接口,定义了日志操作的 5 个级别:

TRACE< DEBUG < INFO < WARN < ERROR

2.3. 源码总体构成

2.4. 源码分析——Log

  • org.apache.commons.logging.Log 是日志对象接口;
  • 对日志划分 6 个等级:TRACE < DEBUG < INFO < WARN < ERROR < FATAL;
  • 常用实现类:NoOpLog、SimpleLog、Jdk14Logger、Log4JLogger;

2.4.1. NoOpLog

NoOpLog 实现了 Log 接口,所有实现都是空的,没什么卵用,了解即可。

2.4.2. SimpleLog

JCL 内置的简单日志记录器,实现了 Log 接口,日志的输出目标是 System.err;

  • SimpleLog 本身提供了几个简单的控制参数;
  • SimpleLog 构造器,主要负责解析日志记录器的级别;
  • SimpleLog 的日志格式控制;
  • SimpleLog 默认输出到 System.err;

commons-logging.properties:控制 JCL 使用 SimpleLog 作为日志记录器;

代码语言:javascript
复制
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog

simplelog.properties:SimpleLog 的参数配置;

代码语言:javascript
复制
## default level
org.apache.commons.logging.simplelog.defaultlog=INFO

## webj2ee.JCL's log level
org.apache.commons.logging.simplelog.log.webj2ee.JCL=WARN

## enable showdatetime
org.apache.commons.logging.simplelog.showdatetime=true
org.apache.commons.logging.simplelog.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS zzz

## enable showlogname;
org.apache.commons.logging.simplelog.showShortLogname=false
org.apache.commons.logging.simplelog.showlogname=true

测试程序:

代码语言:javascript
复制
package webj2ee;

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

public class JCL {
    private static Log logger = LogFactory.getLog(JCL.class);

    public static void main(String[] args) {
        logger.info("From jcl's info msg!");
        logger.warn("From jcl's warn msg!");
        logger.error("From jcl's error msg!");
    }
}

输出结果:

2.4.3. Jdk14Logger

JCL 内置的 JUL(Java Util Logging)日志记录适配器,将日志记录的工作委托给 JUL 处理 ;

  • Jdk14Logger 与 JUL 、JCL 的关系;
  • Jdk14Logger 构造器,通过 getLogger() 方法获取 JUL 的日志记录器;
  • Jdk14Logger 最终使用 JUL 的 logp 方法记录日志;
  • JUL 与 JCL 的日志级别映射关系;

2.4.4. Log4JLogger

JCL 内置的 Log4j1 日志记录适配器,将日志记录的工作委托给 Log4j1 处理 ;

  • Log4JLogger 与 Log4j、JCL 的关系;
  • Log4JLogger 构造器,通过 getLogger() 方法获取 Log4j 的日志记录器;

2.5. 源码分析——LogFactory

2.5.1. LogFactory 基本关系回顾

2.5.2. LogFactory 最常用方法

上述获取Log的过程大致分成2个阶段

  • 获取 LogFactory;
  • 通过 LogFactory 的 getInstance 接口获取 Log;

2.5.3. 获取 LogFactory 的过程解析

1. LogFactory 的关键常量

2. LogFactory实现类查找过程:

  1. 尝试通过系统属性【org.apache.commons.logging.LogFactory】值获取;
  2. 尝试通过 SPI 模式【META-INF/services/org.apache.commons.logging.LogFactory】获取;
  3. 尝试通过JCL配置文件【commons-logging.properties】中配置的【org.apache.commons.logging.LogFactory】属性值获取;
  4. 如果还找不到,就使用 JCL 默认的 LogFactory 实现类 【org.apache.commons.logging.impl.LogFactoryImpl】;

其实大多数情况下,使用的都是 JCL 默认提供的 LogFactory 实现类 LogFactoryImpl。

2.5.4. LogFactoryImpl 获取 Log 的过程解析

1. LogFactoryImpl 的关键常量

2. LogFactoryImpl.getInstance 的关键路径

  1. 从 commons-logging.properties 配置文件中寻找属性为 org.apache.commons.logging.Log 对应的 Log 实现类;
  2. 从系统属性中寻找属性为 org.apache.commons.logging.Log 对应的 Log 实现类;
  3. 如果还没招到,则按照 classesToDiscover 中定义的顺序寻找;

3. JCL 与 JUL 集成

从前面分析可知, JCL 通过自动发现,即可完成与 JUL 的集成;

POM 配置:只需要引入JCL;

代码语言:javascript
复制
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

测试程序:

代码语言:javascript
复制
package webj2ee;

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

public class JCL {
    private static Log logger = LogFactory.getLog(JCL.class);

    public static void main(String[] args) {
        logger.info("From jcl's info msg!");
        logger.warn("From jcl's warn msg!");
        logger.error("From jcl's error msg!");
    }
}

JVM 启动参数:使用自定义的 JUL 配置;

代码语言:javascript
复制
-Djava.util.logging.config.file=D:/jul.properties

D:/jul.properties:

代码语言:javascript
复制
handlers= java.util.logging.ConsoleHandler
.level= INFO

java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

java.util.logging.SimpleFormatter.format=[JUL] %4$s: %5$s [%1$tc]%n

运行效果:

4. JCL 与 Log4j1 集成

从前面分析可知, JCL 通过自动发现,即可完成与 Log4j1 的集成,且自动发现的优先级最高;

POM配置:引入 JCL 和 Log4j1.x;

代码语言:javascript
复制
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

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

测试程序:

代码语言:javascript
复制
package webj2ee;

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

public class JCL {
    private static Log logger = LogFactory.getLog(JCL.class);

    public static void main(String[] args) {
        logger.info("From jcl's info msg!");
        logger.warn("From jcl's warn msg!");
        logger.error("From jcl's error msg!");
    }
}

log4j.properties:使用自定义的 Log4j 配置;

代码语言:javascript
复制
log4j.rootLogger=INFO, Console

#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender 
log4j.appender.Console.layout=org.apache.log4j.PatternLayout 
log4j.appender.Console.layout.ConversionPattern=[LOG4J1] %d [%t] %-5p [%c] - %m%n

5. JCL 与 Log4j2 集成

从前面分析可知, JCL 自身没有 Log4j2 的适配器,所以引入 Log4j2 提供的 JCL 适配器。

POM配置:引入JCL、Log4j2、log4j-jcl

代码语言:javascript
复制
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.12.1</version>
</dependency>

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.12.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>

测试程序:

代码语言:javascript
复制
package webj2ee;

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

public class JCL {
    private static Log logger = LogFactory.getLog(JCL.class);

    public static void main(String[] args) {
        logger.info("From jcl's info msg!");
        logger.warn("From jcl's warn msg!");
        logger.error("From jcl's error msg!");
    }
}

log4j2.xml:使用自定义的 Log4j2 配置文件;

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[LOG4J2] %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>

    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

运行结果:

集成原理分析

log4j-jcl 干了什么?

  1. log4j-jcl 通过 SPI 形式,控制 JCL 使用 log4j-jcl 中的 LogFactory 的实现类 org.apache.logging.log4j.jcl.LogFactoryImpl
  2. org.apache.logging.log4j.jcl.LogFactoryImpl 则再通过 适配器 模式,最终返回的是 org.apache.logging.log4j.jcl.Log4jLog 日志记录器,它会将日志记录工作委托给 Log4j2 实现。

6. JCL 与 Logback 集成

  • JCL 自身没有到 Logback 的适配器;
  • Logback 默认实现的是 SLF4J 接口,通过 SLF4J-API 访问;
  • 所以 JCL 需要将日志记录请求中转给 SLF4J,然后 SLF4J 再中转给 Logback;

POM配置:引入JCL、SLF4J-API、Logback、jcl-over-slf4j

代码语言:javascript
复制
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.29</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.29</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>

测试程序:

代码语言:javascript
复制
package webj2ee;

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

public class JCL {
    private static Log logger = LogFactory.getLog(JCL.class);

    public static void main(String[] args) {
        logger.info("From jcl's info msg!");
        logger.warn("From jcl's warn msg!");
        logger.error("From jcl's error msg!");
    }
}

logback.xml:使用自定义的 Logback 配置;

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[LOGBACK] %d{HH:mm:ss.SSS} [[%thread]] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

运行结果:

集成原理分析

jcl-over-slf4j 干了什么?

显然,还是老路子,通过 SPI 机制使用自定义的 LogFactory 实现类,然后实例化中转到 SLF4J 的 Log 实现类;

参考:

《码出高效:Java 开发手册》 《Java 开发手册 1.5.0》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档