一、日志门面说明
当我们的系统变的更加复杂的时候,我们的日志就容易发生混乱。随着系统开发的进行,可能会更新不同的日志框架,造成当前系统中存在不同的日志依赖,让我们难以统一的管理和控制。就算我们强制要求所有的模块使用相同的日志框架,系统中也难以避免使用其他类似 spring,mybatis 等其他的第三方框架,它们依赖于我们规定不同的日志框架,而且他们自身的日志系统就有着不一致性,依然会出来日志
体系的混乱。
所以我们需要借鉴 JDBC 的思想,为日志系统也提供一套门面,那么我们就可以面向这些接口规范来开发,避免了直接依赖具体的日志框架。这样我们的系统在日志中,就存在了日志的门面和日志的实现。
常见的日志门面 :
JCL、slf4j
常见的日志实现:
JUL、log4j、logback、log4j2
日志门面和日志实现的关系:
日志框架出现的历史顺序:
log4j –>JUL–>JCL–> slf4j –> logback –> log4j2
我们为什么要使用日志门面:
-
面向接口开发,不再依赖具体的实现类。减少代码的耦合
-
项目通过导入不同的日志实现类,可以灵活的切换日志框架
-
统一 API,方便开发者学习和使用
-
统一配置便于项目日志的管理
二、JCL 使用
全称为 Jakarta Commons Logging,是 Apache 提供的一个通用日志 API。
它是为 “所有的 Java 日志实现”提供一个统一的接口,它自身也提供一个日志的实现,但是功能非常常弱(SimpleLog)。所以一般不会单独使用它。他允许开发人员使用不同的具体日志实现工具: Log4j, Jdk 自带的日志(JUL)
JCL 有两个基本的抽象类:Log(基本记录器)和 LogFactory(负责创建 Log 实例)。
JCL 入门
1、添加依赖
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency><dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
2、入门代码,基于自身的 SimpleLog
package com.mcode.logger;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.junit.Test;/*** Hello world!*/public class JCLTest {/*** 快速入门JCL* @throws Exception*/@Testpublic void testQuick() throws Exception {// 获取 log日志记录器对象Log log = LogFactory.getLog(JCLTest.class);// 日志记录输出log.info("hello jcl");}}package com.mcode.logger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; /** * Hello world! */ public class JCLTest { /** * 快速入门JCL * @throws Exception */ @Test public void testQuick() throws Exception { // 获取 log日志记录器对象 Log log = LogFactory.getLog(JCLTest.class); // 日志记录输出 log.info("hello jcl"); } }package com.mcode.logger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; /** * Hello world! */ public class JCLTest { /** * 快速入门JCL * @throws Exception */ @Test public void testQuick() throws Exception { // 获取 log日志记录器对象 Log log = LogFactory.getLog(JCLTest.class); // 日志记录输出 log.info("hello jcl"); } }
3、添加 log4j 依赖
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
4、添加 log4j.properties
# 指定 RootLogger 顶级父元素默认配置信息# 指定日志级别=trace,使用的 apeender 为=consolelog4j.rootLogger = trace,console# 指定控制台日志输出的 appenderlog4j.appender.console = org.apache.log4j.ConsoleAppender# 指定消息格式 layoutlog4j.appender.console.layout = org.apache.log4j.PatternLayout# 指定消息格式的内容log4j.appender.console.layout.conversionPattern = [console]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n# 指定 RootLogger 顶级父元素默认配置信息 # 指定日志级别=trace,使用的 apeender 为=console log4j.rootLogger = trace,console # 指定控制台日志输出的 appender log4j.appender.console = org.apache.log4j.ConsoleAppender # 指定消息格式 layout log4j.appender.console.layout = org.apache.log4j.PatternLayout # 指定消息格式的内容 log4j.appender.console.layout.conversionPattern = [console]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n# 指定 RootLogger 顶级父元素默认配置信息 # 指定日志级别=trace,使用的 apeender 为=console log4j.rootLogger = trace,console # 指定控制台日志输出的 appender log4j.appender.console = org.apache.log4j.ConsoleAppender # 指定消息格式 layout log4j.appender.console.layout = org.apache.log4j.PatternLayout # 指定消息格式的内容 log4j.appender.console.layout.conversionPattern = [console]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
5、基于 log4j 的测试结果
JCL 原理
1、通过 LogFactory 动态加载 Log 实现类
- SimpleLog:JCL 的内置日志实现
- Log4JLogger:log4j 日志框架
- Jdk14Logger:JUL(Java Util Logging)
- Jdk13LumberjackLogger:Jdk 老版本的内置日志实现
2、日志门面支持的日志实现数组
public class LogFactoryImpl extends LogFactory {.....private static final String[] classesToDiscover = new String[].{"org.apache.commons.logging.impl.Log4JLogger","org.apache.commons.logging.impl.Jdk14Logger","org.apache.commons.logging.impl.Jdk13LumberjackLogger","org.apache.commons.logging.impl.SimpleLog"};.....}public class LogFactoryImpl extends LogFactory { ..... private static final String[] classesToDiscover = new String[]. { "org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" }; ..... }public class LogFactoryImpl extends LogFactory { ..... private static final String[] classesToDiscover = new String[]. { "org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" }; ..... }
说明:日志工厂其内部有一个加载日志的数组,加载顺序是按照数组顺序来的,这也解释了为什么入门案例中引入了 Log4J,JCL 内部日志框架就从 JUL 切换至 Log4J 了
3、具体实现代码
for(int i = 0; i < classesToDiscover.length && result == null; ++i) {result = this.createLogFromClass(classesToDiscover[i], logCategory, true);}for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); }for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); }
说明:通过日志数组顺序加载日志框架,如果没有找到,则继续加载下一个,否则直接返回
从原理中我们知道,JCL 是通过一个日志数组顺序加载,日志数组中包含:JCL 的内置日志实现 SimpleLog、log4j 、JUL(Java Util Logging)以及 Jdk 老版本的内置日志实现。
✷ 设计缺陷:如果后期又出现了新的日志实现主流框架,如果你想加载的话,就需要修改 JCL 源代码,实现 Log 接口,放入到日志加载数组中,因此 JCl 已经在 2014 年被 apache 淘汰了
三、SLF4J 使用
简单日志门面(Simple Logging Facade For Java) SLF4J 主要是为了给 Java 日志访问提供一套标准、规范的 API 框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 log4j 和 logback 等。
当然 slf4j 自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的 Java 项目而言,日志框架会选择 slf4j-api 作为门面,配上具体的实现框架(log4j、logback 等),中间使用桥接器完成桥接。
官方网站:
SLF4J 是目前市面上最流行的日志门面。现在的项目中,基本上都是使用 SLF4J 作为我们的日志系统。
SLF4J 日志门面主要提供两大功能:
-
日志框架的绑定
-
日志框架的桥接
application 下面的 SLF4J API 表示 slf4j 的日志门面,包含三种情况:
-
若是只导入 slf4j 日志门面没有导入对应的日志实现框架,那么日志功能将会是默认关闭的,不会进行日志输出的。
-
蓝色图里 Logback、slf4j-simple、slf4j-nop 出来的比较晚就遵循了 slf4j 的 API 规范,也就是说只要导入对应的实现就默认实现了对应的接口,来实现开发。
-
对于中间两个日志实现框架 log4j(slf4j-log4j12)、JUL(slf4j-jdk14)由于出现的比 slf4j 早,所以就没有遵循 slf4j 的接口规范,所以无法进行直接绑定,中间需要加一个适配层(Adaptationlayer),通过对应的适配器来适配具体的日志实现框架,其对应的适配器其实就间接的实现了 slf4j-api 的接口规范。
注意:在图中对于 logback 需要引入两个 jar 包,不过在 maven 中有一个传递的思想,当配置 logback-classic 时就会默认传递 core 信息,所以我们只需要引入 logback-classic 的 jar 包即可。
配合自身简单日志实现(slf4j-simple)
若想使用自身的日志实现框架,需要引入第三方jar
包slf4j-simple
(slf4j 自带实现类):
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>2.0.7</version></dependency><!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency><!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency>
其中该坐标包含了对应的slf4j-api
的依赖,可以不用手动导入slf4j-api
。
测试
package com.mcode.logger;import org.junit.Test;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class SLF4JTest{//获取Logger实例private static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class);@Testpublic void testQuick(){//打印日志记录LOGGER.error("error");LOGGER.warn("warn");LOGGER.info("info");LOGGER.debug("debug");LOGGER.trace("trace");//占位符输出String name = "mcode";int age = 20;LOGGER.info("报错,name:{},age:{}",name,age);//打印堆栈信息try {int i = 5/0;}catch (Exception e){LOGGER.error("报错",e);}}}package com.mcode.logger; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SLF4JTest { //获取Logger实例 private static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class); @Test public void testQuick(){ //打印日志记录 LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); //占位符输出 String name = "mcode"; int age = 20; LOGGER.info("报错,name:{},age:{}",name,age); //打印堆栈信息 try { int i = 5/0; }catch (Exception e){ LOGGER.error("报错",e); } } }package com.mcode.logger; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SLF4JTest { //获取Logger实例 private static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class); @Test public void testQuick(){ //打印日志记录 LOGGER.error("error"); LOGGER.warn("warn"); LOGGER.info("info"); LOGGER.debug("debug"); LOGGER.trace("trace"); //占位符输出 String name = "mcode"; int age = 20; LOGGER.info("报错,name:{},age:{}",name,age); //打印堆栈信息 try { int i = 5/0; }catch (Exception e){ LOGGER.error("报错",e); } } }
结果
默认日志等级为INFO
,能够实现占位符输出,并且可以在日志等级方法中传入异常实例,来打印对应的日志信息。
配置logback日志实现
引入logback-classic
的jar包,其中包含有slf4j-api
以及logback-core
的依赖,所以只需要引入该依赖即可:
配置Log4J日志实现(需适配器)
首先添加日志框架实现依赖
之前介绍,对于Log4j
、JUL
这些比较早出现的日志实现框架需要有对应的适配层,在这里我们引入对应的适配器slf4j-log412
的依赖坐标: