【Log】(一)Java 中的日志框架 JUL、Log4j
【Log】(二)Java 中的日志框架 JCL、SLF
【Log】(三)Java 中的日志框架 logback、log4j2


前言

JUL 用起来简单、方便,不需要引入第三方依赖,适合小型应用。Log4j 同样适用起来简单,但它配置更为强大。那么,我们该如何选择这两个日志框架呢?其实,这根据我们项目需求而定的。

举个简单例子:在项目初期,功能简单,我们可以使用比较简单的日志框架 JUL。因为,它使用起来灵活,不需要导入第三方依赖。随着功能的日益完善,当初的日志框架无法满足现在的需求,那么就得进行日志的升级了,如:切换成 Log4j

如果从 JUL 切换成 Log4j,那么代码一定会受到影响,且之前记录的日志信息也得修改(改动大),这并不是我们程序员希望看到的。为了解决这一问题,Apache 组织站出来了,它将当时主流的日志框架(JULLog4j)统一一套 API。那么,后期在软件开发阶段需要关联一套统一的 API 就可以操作某一个日志实现框架的具体日志记录了。即:日志框架修改了,但这套 API 是没变的。而这套 API 就是指 JCL

1. JCL 学习

1.1 JCL 介绍

JCL:全称 Jakarta Common Logging ,是 Apache 提供的一个通用日志 API。

它为 “所有的 Java 日志实现”提供了一个统一的接口,它自身也提供了一个日志的实现,但功能非常弱(SimpleLog)。所以,一般不会单独使用它。它允许开发者使用不同的具体日志实现工具:Log4jJUL

JCL 有两个基本的抽象类:LogLogFactory(负责创建 Log)

1.2 快速入门

新建一个 Maven 工程,引入一个 commons 依赖,POM 文件:

<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>

1. 入门案例:

public class JclTest {@Testpublic void testQuick() {Log log = LogFactory.getLog(JclTest.class);log.info("info");log.error("error");}}

运行后:

这种格式,由于没有导入日志的实现依赖,就使用默认的 JDK14 Logger(JUL)

2. 导入 Log4j:

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

运行后:

弹出警告:Log4j 没有系统配置。来,咱们加上 log4j.properties 配置文件

# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别=info,使用的 appender 是 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 = [%p]%r  %l %d{yyyy-MM-dd HH:mm:ss} %m%n

运行后:

这种日志格式,就是使用了 log4j

看,修改日志的实现,不需要修改代码。这就是面向接口设计的理念。

3. 总结

我们为什么要使用日志门面:

  1. 面向接口开发,不再依赖具体的实现类,减少代码的耦合
  2. 项目通过导入不同的日志实现类,可以灵活地切换日志框架
  3. 统一 API,方便开发者学习
  4. 统一配置,便于项目日志的管理

1.3 JCL 原理

根据上面案例我们知道:当我们没有引入日志的实现的依赖时,它会使用日志的默认实现 JUL;但当我们引入了 log4j 后,自然就使用 log4j 日志。那么,jcl 到底是如何做到的呢?

1、 通过 LogFactory 动态地加载 Log 实现


接口 Log 有 4 个实现类:JDK14LoggerLog4jLoggerJdk13LumberjackLoggerSimpleLog

通过 LogFactory 动态地获取 Log(哪个实现类)

2、日志门面支持的日志实现数组

private static final String[] classesToDiscover = {"org.apache.commons.logging.impl.Log4JLogger","org.apache.commons.logging.impl.Jdk14Logger","org.apache.commons.logging.impl.Jdk13LumberjackLogger","org.apache.commons.logging.impl.SimpleLog"
};

注意:这个数组中的元素的顺序!!第一个是: Log4JLogger

3、获取具体日志的实现

for(int i=0; i<classesToDiscover.length && result == null; ++i) {result = createLogFromClass(classesToDiscover[i], logCategory, true);
}

循环 jcl 中已经实现了的 4 个类,然后调用方法 createLogFromClass()

重点看方法 createLogFromClass()

// 只看重点逻辑
private Log createLogFromClass(String logAdapterClassName,String logCategory,boolean affectState)throws LogConfigurationException {Class c;Log logAdapter = null;for (;;) {try {c = Class.forName(logAdapterClassName, true, currentCL);} catch (NoClassDefFoundError e) {...break;}constructor = c.getConstructor(logConstructorSignature);Object o = constructor.newInstance(params);if (o instanceof Log) {logAdapterClass = c;logAdapter = (Log) o;break;}}return logAdapter;
}

这个方法的逻辑:通过 Class.forName(String className) 来加载 Log 的实现类(来自于 classesToDiscover 数组),如果此类存在,则通过反射创建其实例并返回;如果不存在,则直接返回。

再看看 for 循环中,如果返回的结果不为 null,则跳出循环,继续往下执行;否则,继续循环。

那么,当我们没有引入 log4j 的依赖时,也是存在 Log4JLogger 的(jcl 的实现类)。并且,它是位于要循环的数组中的第一个元素,但是,它并不会加载成功。因为 Log4JLogger 类中引入了 log4j 的依赖,依赖于 log4j。如:import org.apache.log4j.Logger; 所以,在调用 Class.forName(String className) 时会失败。然后继续循环,尝试加载第二个元素 Jdk14Logger,这个依赖就存在 jcl 依赖中,所以,它就会加载成功。

没有引入 log4j 依赖时,org.apache.commons.logging.impl.Log4JLogger 类:

那么,当我们引入 log4j 依赖时,org.apache.commons.logging.impl.Log4JLogger 类就显得可用了,所以,它会一次加载成功,不用循环数组

JCL 只支持以上4种实现类,如果还想支持其它三方日志框架,就得修改 jcl 源码了。难道使用起来这么困难吗?其实不然,早在2014年,jcl 就被 apache 组织给淘汰了,后期就推出了更加优秀的日志门面框架。

2. SLF 学习

2.1 SLF 介绍

SLF 主要是为了给 java 日志访问提供一套标准、规范的 API 框架。其主要yiyi在于提供接口,具体的实现可以交由其它日志框架。如:logbacklog4jlog4j2

当然,SLF 自己也提供了较为简单的实现,但一般很少用。对于一般的 java 项目而言,日志框架会选择 slf4j-api 作为门面,配上具体的实现框架(log4jlogback 等。即: slf + logbackslf + log4j2),中间使用桥接器完成桥接。SLF 是目前市面上最流行的日志门面。现在的项目中,基本上都是使用 SLF 作为日志系统。

SLF 日志门面主要提供两大功能:

  1. 日志框架的绑定
  2. 日志框架的桥接

2.2 快速入门

新建一个 Maven 工程,引入一个 slf4j-api 依赖,POM 文件:

<!--slf日志门面-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version>
</dependency>
<!--slf内置简单的实现-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.21</version>
</dependency>

入门案例:

public class SlfTest {public static final Logger LOGGER = LoggerFactory.getLogger(SlfTest.class);@Testpublic void testQuick() {LOGGER.error("error");LOGGER.warn("warn");LOGGER.info("info");LOGGER.debug("debug");LOGGER.trace("trace");// 使用占位符String name = "zzc";Integer age = 24;LOGGER.info("用户信息:{}, {}", name, age);}}

运行后,结果如下:

2.3 SLF 的日志绑定

上面的案例是使用了 SLF 绑定了 简单的日志实现。那么,SLF 是如何绑定其它的主流日志框架呢?SLF4J 官网的用户手册 上有详细的介绍:

上面这副图就表明了 SLF 是如何绑定其它的主流日志框架。

如果 Java 应用中需要使用日志记录的话,则首先需要引入 slf4j-api.jar 的依赖,统一日志接口。并且,它需要引入具体的实现,共有三种情况:

  1. 没有引入具体的实现:那么,日志功能将不能起作用
  2. 引入了一类实现(上图的蓝色框:slf4j-logbackslf4j-simpleslf4j-nop),由于它们的设计比 SLF 要晚,所以默认遵循 SLF 的规范,只需要导入它们的依赖就可使用
  3. 引入了另一类实现(log4jjdk14),它们的设计比 SLF 要早,在设计之初,并没有遵循 SLF 的规范,无法直接进行绑定。所以,需要添加一个适配层 Adaptation layer。通过适配器进行适配,从而间接地遵循了 SLF 的规范。

使用 SLF 的日志绑定流程:

1、添加 slf4j-api 的依赖
2、使用 slf4j 的 API 在项目中进行统一的日志记录
3、 绑定具体的日志实现

1、绑定了已经实现sfl4j的日志框架,直接添加对应的依赖
2、绑定了没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖

4、slf4j 有且仅有一个日志实现框架的绑定(如果出现多个,默认使用第一个依赖日志实现)

接下来就对剩下的日志实现进行案例演示

logback:

<!--logback-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>

logback-classic 包含了 logback-core 的依赖。

【注意】:引入了 logback-classic 的依赖后,得去掉的 slf4j-simple 的依赖(入门案例时引入的)

入门案例代码不变,运行后:

nop:

slf4j-nop 是日志的开关,引入它后,表示系统关闭日志系统

<!-- nop 日志的开关-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-nop</artifactId><version>1.7.2</version>
</dependency>

运行后,控制台并不会输出日志信息

log4j:

<!--绑定log4j实现,需要导入适配器-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version>
</dependency>
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>

需要引入 log4j.properties,这里就拿之前的就行。

运行结果如下:

jdk14:

<!--绑定jdk14实现-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.25</version>
</dependency>

运行结果如下:

2.4 SLF 的日志绑定原理

LoggerFactoryLogger 是根据 LoggerFactory 获取的实例

public static Logger getLogger(String name) {ILoggerFactory iLoggerFactory = getILoggerFactory();return iLoggerFactory.getLogger(name);
}

LoggerFactory#getILoggerFactory():获取具体的工厂实例

public static ILoggerFactory getILoggerFactory() {if (INITIALIZATION_STATE == 0) {//...performInitialization();}switch(INITIALIZATION_STATE) {//...case 3:return StaticLoggerBinder.getSingleton().getLoggerFactory();//...
}

LoggerFactory#bind():绑定所有的实现类

private static final void bind() {String msg;try {Set<URL> staticLoggerBinderPathSet = null;if (!isAndroid()) {// 1.找出所有的日志实现staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();// 2.如果实现类大于1,则会记录下来reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);}// 3.进行日志实现类org.slf4j.impl.StaticLoggerBinder.class加载StaticLoggerBinder.getSingleton();INITIALIZATION_STATE = 3;reportActualBinding(staticLoggerBinderPathSet);//...} catch (NoClassDefFoundError var2) {// ...}
}

说明:

  1. findPossibleStaticLoggerBinderPathSet();:通过类加载器在类路径下(包括 jar 包)查找包含org.slf4j.impl.StaticLoggerBinder.class 类的路径。即:日志实现的 jar 就包含
  2. reportMultipleBindingAmbiguity():如果引入了多于1个的日志实现,则会记录下来
  3. StaticLoggerBinder.getSingleton();:调用此方法时,会导致类加载,并返回日志实现的单例实例(即使引入了多个日志实现的类,那也只会返回同一个实例)。

2.5 SLF 的日志桥接器

Java 工程中使用的日志框架依赖于 SLF 以外的 API。为了使日志框架看上去是 SLF API 的实现,将使用 SLF 附带了几个桥接模块。这些模块将对 Log4j、jcl 的调用重定向,就好像它们是对 SLF API 一样。

桥接解决的项目中日志的遗留问题,当系统中存在之前的日志 API,可以通过桥接转换到 SLF 的实现。

步骤:

  1. 先去除之前老的日志框架的依赖
  2. 添加 SLF 提供的桥接组件
  3. 为项目添加 SLF 的具体实现

案例 ------ log4j:

假若之前,工程中使用的是 log4j

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

然后,在类路径下添加:log4j.properties

添加测试代码:

public class Log4jTest {@Testpublic void testQuick() {Logger logger = Logger.getLogger(com.zzc.log.log4j.Log4jTest.class);logger.fatal("fatal");logger.error("error");logger.warn("warn");logger.info("info");logger.debug("debug");logger.trace("trace");}}

运行结果后:

现在,我需要在工程中使用 SLF API(SLF + logback),而不使用 log4j

引入依赖:

<!--slf日志门面-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.26</version>
</dependency>
<!--logback-->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<!--配置log4j的桥接器-->
<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.24</version>
</dependency>

测试代码不变

运行结果如下:

输出格式:logback(用的它的实现)

【注意】:桥接器和适配器不能一起使用。否则,就是出现 栈溢出。

【Log】(二)Java 中的日志框架 JCL、SLF相关推荐

  1. java中jcl_Java日志框架——JCL

    JCL,全称为"Jakarta Commons Logging",也可称为"Apache Commons Logging". 一.JCL原理 1.基本原理 JC ...

  2. JAVA中的日志框架-log4j的使用

    JAVA日志-使用log4j 1. log4j.jar下载 windows下载地址: http://www.apache.org/dyn/closer.cgi/logging/log4j/1.2.15 ...

  3. Spring全家桶中的日志框架

    Spring全家桶中的日志框架 spring-jcl spring-jcl是spring的日志框架,spring-jcl底层使用的日志框架是有优先级的优先级为:LOG4J2 级是最高的,其次是SLF4 ...

  4. java中的日志处理

    java中的日志处理简介 在Java中我们可以使用自定义的.可扩展的日志处理方式.我们不仅可以使用Java中java.util.logging包提供的基本的日志相关的API来进行日志的处理,也可以使用 ...

  5. C#项目中使用日志框架Log4net

    C#项目中使用日志框架Log4net 背景 准备条件 日志服务简单封装 使用封装的服务类记录日志 配置文件App.config/Web.config 背景 无论是软件的开发期间还是发布后的运维期间,日 ...

  6. Java中的日志级别

    昨天校招面试被问到了Java中的日志等级,当时也慌的一批,只说出了其中的三个,在这里细心为大家总结一下. java中⽇志级别有7 个级别:  severe.Warning.info.config.fi ...

  7. java中logger_Java日志系统---Logger之简单入门

    Java 中自带的日志系统,今天抽空了解了一点,算是入了门,所以将自己的一些心得记录下来,以备日后查看,有兴趣的朋友,看到此文章,觉得有错误或需要添加的地方,请在下方评论留言,大家可以共同进步,谢谢: ...

  8. java中的集合框架_JAVA中的集合框架(上)List

    第一节 JAVA中的集合框架概述 集合的概念,现实生活中:很多事物凑在一起就是一个集合:数学中的集合:具有相同属性事物的总体:JAVA中的集合:是一种工具类,就像是容器,储存任意数量的具有共同属性的对 ...

  9. Java 中的 Swing 框架现在是不是被淘汰了?

    关于java中的Swing框架,我先说下如下的观点. 1 只要是用java开发的商业项目,就指着来挣钱的项目,都不会用Swing框架. 2 所以对java初学者来说,根本没必要学swing,甚至连类似 ...

最新文章

  1. python如何安装torch_PyTorch安装与基本使用详解
  2. C#中DataTable使用以及对行与列的赋值
  3. 【Linux】vim简单配置
  4. P1991-无线通讯网【最小生成树,瓶颈生成树】
  5. gsensor 车辆碰撞算法_AEB安全模型(一)——基于碰撞时间的安全模型
  6. 动态规划——编辑距离
  7. Oracle AWR报告提取方法
  8. PHP 抽象工厂模式(Kit模式)
  9. Jersey实现Restful服务
  10. //18. 定义一个基类BaseClass,从它派生出类DerivedClass,BaseClass有成员函数fn1()、fn2(),fn1()是虚函数,DerivedClass也有成员函数fn1()
  11. [深度学习]基于TensorFlow的基本深度学习模型
  12. 将谷歌网盘的文件搬运到百度网盘
  13. 用ffmpeg进行音频格式转换、剪切、合并、音量调整等
  14. 写给还在迷茫中的朋友,一名6年程序员的工作感悟!!!
  15. PR曲线,ROC曲线和AUC的区别
  16. str(n)cpy的注意事项以及memset的简单使用
  17. mysql报错3009_MySQL修改密码方法汇总
  18. 【camera】数字成像系统—初识
  19. 看看人家那智能在线爬虫系统,那叫一个优雅(附源码)!
  20. 宝山区一居民家中天然气爆炸 气浪发飙破窗而出

热门文章

  1. linux时间同步命令shell,LINUX时间同步脚本或命令
  2. Spring IOC循环依赖
  3. T分布与标准正态分布的图形及峰度问题
  4. NFC Bank card 以移动浦发联名挂件卡解析qPBOC应用
  5. lisp实战文库_autolisp教程pdf
  6. 密码学与网络安全第七版部分课后习题答案
  7. 小程序逆向加密步骤小结
  8. apache配置https,并且强制使用HTTPS
  9. Ubuntu18 USB网卡驱动安装踩坑记录
  10. 索尼推新款Walkman 同iPod再爭高下