有日志么?
日志有价值么?
日志框架了解么?
“日志”这个词最早见于航海领域,是记录航行主要情况的载体文件,内容包括操作指令、气象、潮流、航向、航速、旅客、货物等,是处理海事纠纷或者海难的原始依据之一。尔后延伸到航空领域,黑匣子就是一个重要的航空日志载体,调查空难原因时第一反应是找到黑匣子,并通过解析其中的日志信息来还原空难的事实真相 码出高效: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.2. 示例
接口:
package webj2ee.spi;
public interface SPIService {
void execute();
}
实现类:
package webj2ee.spi;
public interface SPIService {
void execute();
}
package webj2ee.spi;
public class SPIImpl2 implements SPIService {
@Override
public void execute() {
System.out.println("SPIService Impl 2...");
}
}
SPI配置:webj2ee.spi.SPIService
webj2ee.spi.SPIImpl1
webj2ee.spi.SPIImpl2
测试类:
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 依赖
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2.2. 核心接口
JCL 日志门面对外暴露两个接口:
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
2.4.1. NoOpLog
NoOpLog 实现了 Log 接口,所有实现都是空的,没什么卵用,了解即可。
2.4.2. SimpleLog
JCL 内置的简单日志记录器,实现了 Log 接口,日志的输出目标是 System.err;
commons-logging.properties:控制 JCL 使用 SimpleLog 作为日志记录器;
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
simplelog.properties:SimpleLog 的参数配置;
## 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
测试程序:
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 处理 ;
2.4.4. Log4JLogger
JCL 内置的 Log4j1 日志记录适配器,将日志记录的工作委托给 Log4j1 处理 ;
2.5. 源码分析——LogFactory
2.5.1. LogFactory 基本关系回顾
2.5.2. LogFactory 最常用方法
上述获取Log的过程大致分成2个阶段
2.5.3. 获取 LogFactory 的过程解析
1. LogFactory 的关键常量
2. LogFactory实现类查找过程:
其实大多数情况下,使用的都是 JCL 默认提供的 LogFactory 实现类 LogFactoryImpl。
2.5.4. LogFactoryImpl 获取 Log 的过程解析
1. LogFactoryImpl 的关键常量
2. LogFactoryImpl.getInstance 的关键路径
3. JCL 与 JUL 集成
从前面分析可知, JCL 通过自动发现,即可完成与 JUL 的集成;
POM 配置:只需要引入JCL;
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
测试程序:
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 配置;
-Djava.util.logging.config.file=D:/jul.properties
D:/jul.properties:
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;
<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>
测试程序:
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 配置;
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;
<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>
测试程序:
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 配置文件;
<?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 干了什么?
6. JCL 与 Logback 集成
POM配置:引入JCL、SLF4J-API、Logback、jcl-over-slf4j;
<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>
测试程序:
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 配置;
<?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》