使用日志门面的原因
目前经常用的日志框架技术有:JUL、Log4j、log4j2、logback用来记录日志信息 ,之前我们讲过,我们学习不同的日志框架。他们的API是不同的,这样难以进行有效的记忆,同时在我们的生产环境下,如果我们已经选择使用了一款日志框架,但根据需求的改变而选择使用了另一种.那么我们用来操作日志的源码都需要发生变化,因此,此时需要一写接口,我们只需要在源码中调用调用接口,接口去调用需要的日志框架。这些接口就是我们说是日志门面。
日志门面介绍
为了将各个服务框架中自带的日志进行统一门面处理,使用门面技术。
常用日志门面技术:JCL、slf4j,用来解决 应用程序 在使用各种日志框架对应API时候导致的耦合性,因此提出一套门面技术,开发人员只需调用门面接口即可。
日志门面技术的好处
门面技术是面向接口的开发,不再依赖具体的实现类,减少代码的耦合性可以根据实际需求,灵活的切换日志框架,统一的API,方便开发者学习和使用,统一的配置管理便于项目日志的维护工作。
JCL
1、JCL简介
全称为Jakarta Commons Logging,是Apache提供的一个通用日志AP1。就是common-logging.jar包。用户可以自由选择第三方的日志组件作为具体实现,像 log4j或者 jdk自带的jul,common-logging 会通过动态查找的机制,在程序运行时自动找出真正使用的日志库(log4j或者 jdk自带的jul)。
当然,common-logging 内部有一个 Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其他日志框架来使用。
使用JCL它的好处就是,代码依赖是common-logging而非依赖 log4j 的 API,避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库。
JCL有两个基本的抽象类:
- Log:日志记录器
- LogFactory:日志工厂(负责创建Log 实例)
Log 的继承体系与 LogFactory 的继承体系
2、入门案例
1、创建测试类,如下:
public class JCLTest {
@Test
public void test() {
Log log = LogFactory.getLog(JCLTest.class);
//日志级别
log.fatal("fatal");//严重错误,一般会造成系统崩溃并终止运行
log.error("error"); // 错误信息,不会影响系统运行
log.warn("warn");//警告信息,可能会发生问题
log.info("info");// 运行信息,数据连接、网络连接、I0操作等等
log.debug("debug");// 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
log.trace("trace"); //追踪信息,记录程序所有的流程信息
}
}
2、在pom文件中添加依赖,在不依赖 log4j 实现类框架情况下,如下:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
3、在不依赖 log4j 实现类框架情况下,运行结果如下:
注意:JCL是一个日志门面类API,内设各种接口,但在没有依赖日志实现框架的情况下,自身带有一个SimpleLog实现类,但功能较弱,因此一般不用。
4、在pom文件中添加依赖,在 依赖 log4j 实现类框架情况下如下:
<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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
5、不创建log4j.properties配置文件,运行结果如下:
说明JCL在有依赖日志实现的框架情况下,会自动依赖日志框架,具体实现流程如下:
- 执行 Log log = LogFactory.getLog(Log4jTest.class);
- 进入 getFactory().getInstance(clazz)方法;
- 进入 instance = newInstance(name);方法;
- 进入 LogFactoryImp类中的discoverLogImplementation(name)方法;
- 执行 createLogFromClass(classesToDiscover[i], logCategory, true);
- classesToDiscover的值如图所示:
过程就是JCL运行时动态查找具体日志框架,依次去判断类路径中是否存在Log4J、JDK14、jdk13、Simplelog依赖。加载顺序首先加载的就是Log4J,如果不存在则继续加载其他。
通过源码 及 Log 的继承体系与 LogFactory 的继承体系我们发现,Log接口的4个实现类
- Log4jLogger: 我们集成的log4j
- JDK14Logger: JDK自带JUL(java.util.logging)
- JDk13Logger: JDK自带JUL(java.util.logging),JDK14之前的版本使用
- SimpleLogger: JCL自带实现类
3、JCL案例完整代码
1、在pom文件中添加依赖,如下:
<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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
2、创建测试类,如下:
public class JCLTest {
@Test
public void test() {
Log log = LogFactory.getLog(JCLTest.class);
//日志级别
log.fatal("fatal");//严重错误,一般会造成系统崩溃并终止运行
log.error("error"); // 错误信息,不会影响系统运行
log.warn("warn");//警告信息,可能会发生问题
log.info("info");// 运行信息,数据连接、网络连接、I0操作等等
log.debug("debug");// 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
log.trace("trace"); //追踪信息,记录程序所有的流程信息
}
}
3、在resources目录下创建 log4j.propertites配置文件,如下:
# 配置 Root 顶级父元素
# 指定日志输出级别为=debug ,使用的appender为=console、file、dailyFile,rollingFile# 注意:appender选项console、file、dailyFile,rollingFile,使用哪个,选择哪个
log4j.rootLogger = debug,console# 指定 控制台日志
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定 消息格式
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定 消息格式的内容 %p:消息级别、%r:消息用的时间、
# %c:类全名、%t:线程名、%F:文件名 %L:行号
# [%p]%r %c %t %F %L %d{yyyy-MM-dd HH:mm:ss} = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss}
log4j.appender.console.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss} %m%n
# 指定 日志输出到文件
# 指定 日志输出到数据库Mysql以上四个appender可以参考:Java的常用日志技术详解_爱吃面的猫的博客-CSDN博客
4、测试结果如下:
SLF4J
1、SLF4J简介
SEL4J简单日志门面,全称为(Simple Logging Facade For Java),是最常用门面技术,SLF4J主要是为了给Java日志访问提供一套标准、规范的 API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如 log4j和 logback 等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback 等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。
2、入门案例
01、在pom文件中添加slf4j依赖和slf4j自带的简单日志实现包,依赖添加如下:
<dependencies>
<!-- slf4j核心依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j 自带的日志简单实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
02、创建测试类,代码如下:
public class SLF4JTest {
@Test
public void test() {
Logger log = LoggerFactory.getLogger(SLF4JTest.class);
//日志级别:默认打印级别是info,所以打印info级别及一下级别信息
log.error("error"); // 错误信息,不会影响系统运行
log.warn("warn");//警告信息,可能会发生问题
log.info("info");// 运行信息,数据连接、网络连接、I0操作等等
log.debug("debug");// 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
log.trace("trace"); //追踪信息,记录程序所有的流程信息
}
}
03、在没有任何其他日志实现框架集成的基础之上,slf4j使用的就是自带的框架slf4j-simple,但slf4j-simple也必须以单独依赖的形式导入进来,运行结果如下:
3、集成日志实现框架的方式
3.1 日志时间线
日志出现的时间顺序,即日志时间线
- 常见的日志实现:JUL、log4j、logback、log4j2
- 常见的日志门面:JCL、slf4j
- 时间线: log4j --> JUL --> JCL --> slf4j --> logback --> log4j2
3.2 日志实现集成
查看SLF4J日志官网的用户手册,观察日志实现的方式,如下图:
通过上图发现,slf4j集成日志框架的有以下几种方式:
集成日志实现框架 | 描述 | 导入依赖 |
/dev/null | 没有集成任何日志框架,日志是不能够绑定实现任何功能的 slf4j核心依赖是不提供任何实现的。 | <!-- slf4j核心依赖 --> |
slf4j-simple | 是slf4j时间线后提供的日志实现 的API完全遵循slf4j进行的设计 是slf4j-simple是slf4j官方提供 与slf4j无缝衔接,导入依赖,自动绑定到slf4j门面上,无需适配器 | <!-- slf4j 自带的日志简单实现 --> |
Logback | slf4j门面时间线后面提供的日志实现 API完全遵循slf4j进行的设计 与slf4j无缝衔接,导入依赖,自动绑定到slf4j门面上,无需适配器 | <!-- slf4j 集成 logback,logback在slf4j时间线后出现,API遵循slf4j设计,无缝连接 --> |
Nop | slf4j门面时间线后面提供的日志实现 API完全遵循slf4j进行的设计 与slf4j无缝衔接,导入依赖,自动绑定到slf4j门面上,无需适配器 虽然也划分到实现中,但它是指不实现日志记录 | <!-- slf4j 的不记录日志,即禁止日志打印 --> |
log4j | slf4j时间线前的日志实现 API不遵循slf4j进行的设计, 通过适配桥接技术与slf4j衔接 适配器是slf4j-log4j12 | <!-- 导入 log4j 适配器 --> |
JUL | slf4j时间线前的日志实现 API不遵循slf4j进行的设计, 通过适配桥接技术与slf4j衔接 适配器是slf4j-jdk14 | <!-- 导入 JUL 适配器 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.32</version> </dependency> |
4、集成 log4j 案例
1、导入 log4j 适配器、 slf4j 依赖和 log4j 依赖
因为log4j和JUL是在slf4j时间线之前,log4j和JUL的API不遵循slf4j设计,所以 slf4j集成log4j和JUL需要适配器,在pom文件中导入 log4j 适配器:
<dependencies>
<!-- slf4j核心依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- 导入 log4j 适配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j 集成 log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
2、配置 log4j.properties
为了测试方式,只配置控制台的appender,FileAppender及其他appender根据需要自行配置。
# 配置 Root 顶级父元素
# 指定日志输出级别为=debug ,使用的appender为=console、file、
log4j.rootLogger = debug,console# 指定 控制台日志
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定 消息格式
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定 消息格式的内容
log4j.appender.console.layout.conversionPattern = [%p]%r %l %d{yyyy-MM-dd HH:mm:ss} %m%n
3、创建测试类
public class SLF4JTest {
@Test
public void test() {
Logger log = LoggerFactory.getLogger(SLF4JTest.class);
//日志级别:默认打印级别是info,所以打印info级别及一下级别信息
log.error("error"); // 错误信息,不会影响系统运行
log.warn("warn");//警告信息,可能会发生问题
log.info("info");// 运行信息,数据连接、网络连接、I0操作等等
log.debug("debug");// 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
log.trace("trace"); //追踪信息,记录程序所有的流程信息
try {
Class.forName("aaa");
} catch (ClassNotFoundException e) {
log.info("类中出现错误信息",e);
}
}
}
4、运行测试类,结果如下:
5、集成 logback 案例
1、导入 logback 依赖
因为 logback 是在 slf4j 时间线之后出现,所以logback的API完全遵循 slf4j 进行设计,并且会与 slf4j 无缝连接,导入依赖会自动绑定到 slf4j 上面。
<dependencies>
<!-- slf4j核心依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j 集成 logback,logback在slf4j时间线后出现,API遵循slf4j设计,无缝连接 -->
<!-- 依赖具有传递性 ,logback导入 logback-classic 即可 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2、创建测试类,如果需要配置文件,则是logback.xml格式
public class SLF4JTest {
@Test
public void test() {
Logger log = LoggerFactory.getLogger(SLF4JTest.class);
//日志级别:默认打印级别是info,所以打印info级别及一下级别信息
log.error("error"); // 错误信息,不会影响系统运行
log.warn("warn");//警告信息,可能会发生问题
log.info("info");// 运行信息,数据连接、网络连接、I0操作等等
log.debug("debug");// 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
log.trace("trace"); //追踪信息,记录程序所有的流程信息
try {
Class.forName("aaa");
} catch (ClassNotFoundException e) {
log.info("类中出现错误信息",e);
}
}
}
3、运行测试类,测试结果如下
6、集成 nop 案例(了解)
禁止日志记录,即禁止日志打印。
1、导入 nop 依赖
<dependencies>
<!-- slf4j核心依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<!-- slf4j 的不记录日志,即禁止日志打印 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2、创建测试类,如果需要配置文件,则是logback.xml格式
public class SLF4JTest {
@Test
public void test() {
Logger log = LoggerFactory.getLogger(SLF4JTest.class);
//日志级别:默认打印级别是info,所以打印info级别及一下级别信息
log.error("error"); // 错误信息,不会影响系统运行
log.warn("warn");//警告信息,可能会发生问题
log.info("info");// 运行信息,数据连接、网络连接、I0操作等等
log.debug("debug");// 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
log.trace("trace"); //追踪信息,记录程序所有的流程信息
try {
Class.forName("aaa");
} catch (ClassNotFoundException e) {
log.info("类中出现错误信息",e);
}
}
}
3、运行测试类,测试结果如下
注意:依赖多个日志实现类,则默认按先导入的依赖的作为slf4j日志实现。