文章目录

  • 日志门面
    • 日志门面概述
      • 门面模式(外观模式)
      • 日志门面
      • 常见的日志框架及日志门面
  • JCL简介
  • SLF4J
    • SLF4J简介
    • SLF4J桥接技术
      • SLF4J特点
      • SLF4J集成日志实现(jul,log4j,logback等)
      • SLF4J的执行原理
      • 通过slf4j日志门面无缝将log4j替换为logback

日志门面

日志门面概述

门面模式(外观模式)

  • 我们先谈一谈GoF23种设计模式其中之一。
  • 门面模式(Facade Pattern),也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用
  • 外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单

日志门面

  • 前面介绍的几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。
  • 为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。

常见的日志框架及日志门面

  • 常见的日志实现:JUL、log4j、logback、log4j2
  • 常见的日志门面 :JCL、slf4j
  • 出现顺序 :log4j -->JUL–>JCL–> slf4j --> logback --> log4j2

JCL简介

  • 全称为Jakarta Commons Logging,是Apache提供的一个通用日志API
  • 用户可以自由选择第三方的日志组件作为具体实现,像log4j,或者jdk自带的jul, common-logging会通过动态查找的机制,在程序运行时自动找出真正使用的日志库。
  • 当然,common-logging内部有一个Simple logger的简单实现,但是功能很弱。所以使用common-logging,通常都是配合着log4j以及其他日志框架来使用。
  • 使用它的好处就是,代码依赖是common-logging而非log4j的API, 避免了和具体的日志API直接耦合,在有必要时,可以更改日志实现的第三方库。
  • JCL 有两个基本的抽象类:
    • Log:日志记录器
    • LogFactory:日志工厂(负责创建Log实例)

使用JCL日志门面, 如果什么第三方日志框架都没有导入, 就默认采用JUL日志(Java自带); 如果在pom文件中导入了log4j的jar包, 就会使用log4j作为日志实现; 因为JCL内部对各种日志进行了优先级排序. 第一位的就是Log4j

@Test
public void test01(){/*我们暂时没有导入第三方的日志框架,例如log4j默认的情况下,会使用JUL日志框架做日志的记录操作JCL使用原则:如果有log4j,优先使用log4j如果没有任何第三方日志框架的时候,我们使用的就是JUL*/Log log = LogFactory.getLog(JCLTest01.class);log.info("info信息");}@Test
public void test02(){/*
导入log4j依赖,继续测试原有程序总结:我们上一个案例,使用的是JUL,但是在集成了log4j环境后,使用的又是log4j通过测试观察,虽然日志框架发生了变化,但是代码完全没有改变日志门面技术的好处:门面技术是面向接口的开发,不再依赖具体的实现类,减少代码的耦合性可以根据实际需求,灵活的切换日志框架统一的API,方便开发者学习和使用统一的配置管理便于项目日志的维护工作查看源码:Log接口的4个实现类JDk13JDK14 正常java.util.loggingLog4j 我们集成的log4jSimple JCL自带实现类(1)查看Jdk14Logger证明里面使用的是JUL日志框架(2)查看Log4JLogger证明里面使用的是Log4j日志框架(3)观察LogFactory,看看如何加载的Logger对象这是一个抽象类,无法实例化需要观察其实现类LogFactoryImpl(4)观察LogFactoryImpl真正加载日志实现使用的就是这个实现类LogFactoryImpl(5)进入getLog进入getInstance找到instance = this.newInstance(name);,继续进入找到instance = this.discoverLogImplementation(name); 表示发现一个日志的实现for(int i = 0; i < classesToDiscover.length && result == null; ++i) {result = this.createLogFromClass(classesToDiscover[i], logCategory, true);}遍历我们拥有的日志实现框架遍历的是一个数组,这个数组是按照log4jjdk14jdk13SimpleLogger的顺序依次遍历表示的是,第一个要遍历的就是log4j,如果有log4j则执行该日志框架如果没有,则遍历出来第二个,使用jdk14的JUL日志框架以此类推result = this.createLogFromClass(classesToDiscover[i], logCategory, true);表示帮我们创建Logger对象在这个方法中,我们看到了c = Class.forName(logAdapterClassName, true, currentCL);是取得该类型的反射类型对象使用反射的形式帮我们创建logger对象constructor = c.getConstructor(this.logConstructorSignature);*/Log log = LogFactory.getLog(JCLTest01.class);log.info("info信息");
}

SLF4J

SLF4J简介

  • 简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等
  • 当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。
  • 所以我们可以得出 SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接
    官方网站: https://www.slf4j.org/

SLF4J桥接技术

  • 通常,我们依赖的某些组件(Log4j、JUL)依赖于SLF4J以外的日志API; (因为Log4j、JUL比SLF4j出现的还早, 肯定没有遵循SLF4J日志门面的API)
  • 如果这些组件将来不会切换到SLF4J的规则。为了处理这种情况,SLF4J附带了几个桥接模块,这些模块会将对log4j,JCL和java.util.logging API的调用重定向为行为,就好像是对SLF4J API进行的操作一样

SLF4J特点

<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
@Test
public void test01(){/*入门案例SLF4J对日志的级别划分trace、debug、info、warn、error五个级别trace:日志追踪信息debug:日志详细信息info:日志的关键信息 默认打印级别warn:日志警告信息error:日志错误信息在没有任何其他日志实现框架集成的基础之上slf4j使用的就是自带的框架slf4j-simpleslf4j-simple也必须以单独依赖的形式导入进来<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.25</version></dependency>*/Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);logger.trace("trace信息");logger.debug("debug信息");logger.info("info信息");logger.warn("warn信息");logger.error("error信息");
}@Test
public void test02(){/*我们输出动态的信息时也可以使用占位符的形式来代替字符串的拼接我们有些时候输出的日志信息,需要我们搭配动态的数据有可能是信息,有可能是数据库表中的数据总之我们这样做最大的好处就是能够让日志打印变得更加灵活如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性查我们的日志打印是支持以替代符的形式做日志信息拼接的一般情况下,几乎所有的日志实现产品,都会提供这种基础功能*/Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);String name = "zs";int age = 23;//logger.info("学生信息-姓名:"+name+";年龄:"+age);//logger.info("学生信息-姓名:{},年龄:{}",new Object[]{name,age});logger.info("学生信息-姓名:{},年龄:{}",name,age);}@Test
public void test03(){/*日志对于异常信息的处理一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式)我们会根据异常信息提取出有用的线索,来调试bug但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题在控制台上其实也会提供相应的异常或者错误信息的输出但是这种错误输出方式(输出的时间,位置,格式...)都是服务器系统默认的我们可以通过日志技术,选择将异常以日志打印的方式,进行输出查看输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义*///System.out.println(123);Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);try {Class.forName("aaa");} catch (ClassNotFoundException e) {//打印栈追踪信息//e.printStackTrace();logger.info("XXX类中的XXX方法出现了异常,请及时关注信息");//e是引用类型对象,不能根前面的{}做有效的字符串拼接//logger.info("具体错误是:{}",e);//我们不用加{},直接后面加上异常对象e即可logger.info("具体错误是:",e);}
}

SLF4J集成日志实现(jul,log4j,logback等)

@Test
public void test04(){/*集成其他日志实现之前观察官网图SLF4J日志门面,共有3种情况对日志实现进行绑定1.在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的值得大家注意的是,通过我们刚刚的演示,slf4j-simple是slf4j官方提供的使用的时候,也是需要导入依赖,自动绑定到slf4j门面上如果不导入,slf4j 核心依赖是不提供任何实现的2.logback和simple(包括nop)(图中蓝色部分)都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接值得一提的是nop虽然也划分到实现中了,但是他是指不实现日志记录(后续课程)3.log4j和JUL (图中青色部分)都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计通过适配桥接的技术,完成的与日志门面的衔接试着将logback日志框架集成进来测试1:在原有slf4j-simple日志实现的基础上,又集成了logback通过测试,日志是打印出来了 java.lang.ClassNotFoundException: aaa通过这一句可以发现SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]虽然集成了logback,但是我们现在使用的仍然是slf4j-simple事实上只要出现了这个提示SLF4J: Class path contains multiple SLF4J bindings. // 表示有slf4j有多个日志实现在slf4j环境下,证明同时出现了多个日志实现如果先导入logback依赖,后导入slf4j-simple依赖那么默认使用的就是logback依赖注意: 如果有多个日志实现的话,默认使用先导入的实现测试:将slf4j-simple注释掉只留下logback,那么slf4j门面使用的就是logback日志实现值得一提的是,这一次没有多余的提示信息所以在实际应用的时候,我们一般情况下,仅仅只是做一种日志实现的集成就可以了<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>通过这个集成测试,我们会发现虽然底层的日志实现变了,但是源代码完全没有改变这就是日志门面给我们带来最大的好处在底层真实记录日志的时候,不需要应用去做任何的了解应用只需要去记slf4j的API就可以了值得一提的是,我们虽然底层使用的是log4j做的打印,但是从当前代码使用来看我们其实使用的仍然是slf4j日志门面,至于日志是log4j打印的(或者是logback打印的)都是由slf4j进行操作的,我们不用操心*/Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);try {Class.forName("aaa");} catch (ClassNotFoundException e) {logger.info("具体错误是:",e);}}@Test
public void test05(){/*使用slf4j-nop表示不记录日志在我们使用slf4j-nop的时候首先还是需要导入实现依赖这个实现依赖,根据我们之前所总结出来的日志日志实现种类的第二种与logback和simple是属于一类的通过集成依赖的顺序而定所以如果想要让nop发挥效果,禁止所有日志的打印那么就必须要将slf4j-nop的依赖放在所有日志实现依赖的上方<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-nop</artifactId><version>1.7.25</version></dependency>*/Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);try {Class.forName("aaa");} catch (ClassNotFoundException e) {logger.info("具体错误是:",e);}}@Test
public void test06(){/*接下来我们来绑定log4j由于log4j是在slf4j之前出品的日志框架实现所以并没有遵循slf4j的API规范(之前集成的logback,是slf4j之后出品的日志框架实现logback就是按照slf4j的标准指定的API,所以我们导入依赖就能用)如果想要使用,需要绑定一个适配器叫做slf4j-log4j12再导入log4j的实现<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version></dependency>导入log4j依赖 <dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>测试:log4j:WARN No appenders could be found for logger (com.bjpowernode.slf4j.test01.SLF4JTest01).log4j:WARN Please initialize the log4j system properly.log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.虽然日志信息没有打印出来,那么根据警告信息可以得出:使用了log4j日志实现框架提示appender没有加载,需要在执行日志之前做相应的加载工作(初始化)我们可以将log4j的配置文件导入使用测试结果为log4j的日志打印,而且格式和级别完全是遵循log4j的配置文件进行的输出*//*Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);logger.trace("trace信息");logger.debug("debug信息");logger.info("info信息");logger.warn("warn信息");logger.error("error信息");}@Test
public void test07(){/*接下来我们来适配JDK14与上一个测试log4j导入适配器一样JUL也是slf4j之前出品的日志实现框架所以也需要相应的适配器: slf4j-jdk14<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.25</version></dependency>适配器导入之后,JUL日志实现是不用导入依赖的因为JUL,是JDK内置的从测试结果来看,是JUL的日志打印,默认是info级别日志的输出*/Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);logger.trace("trace信息");logger.debug("debug信息");logger.info("info信息");logger.warn("warn信息");logger.error("error信息");
}

SLF4J的执行原理

@Test
public void test08(){/*绑定多个日志实现,会出现警告信息通过源码来看看其原理(看看slf4j的执行原理)进入到getLogger看到Logger logger = getLogger(clazz.getName());进入重载的getLoggerILoggerFactory iLoggerFactory = getILoggerFactory(); 用来取得Logger工厂实现的方法进入getILoggerFactory()看到以双重检查锁的方式去做判断执行performInitialization(); 工厂的初始化方法进入performInitialization()bind()就是用来绑定具体日志实现的方法进入bind()看到Set集合Set<URL> staticLoggerBinderPathSet = null;因为当前有可能会有N多个日志框架的实现看到staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();进入findPossibleStaticLoggerBinderPathSet()看到创建了一个有序不可重复的集合对象LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现看到Enumeration paths;if (loggerFactoryClassLoader == null) {paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);} else {paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);}我们主要观察常量STATIC_LOGGER_BINDER_PATH通过常量我们会找到类StaticLoggerBinder这个类是以静态的方式绑定Logger实现的类来自slf4j-JDK14的适配器进入StaticLoggerBinder看到new JDK14LoggerFactory();进入JDK14LoggerFactory类的无参构造方法看到java.util.logging.Logger.getLogger("");使用的就是jul的Logger接着观察findPossibleStaticLoggerBinderPathSet看到以下代码,表示如果还有其他的日志实现while(paths.hasMoreElements()) {URL path = (URL)paths.nextElement();将路径添加进入staticLoggerBinderPathSet.add(path);}回到bind方法表示对于绑定多实现的处理reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);如果出现多日志实现的情况则会打印Util.report("Class path contains multiple SLF4J bindings.");总结:在真实生产环境中,slf4j只绑定一个日志实现框架就可以了绑定多个,默认使用导入依赖的第一个,而且会产生没有必要的警告信息*/Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);logger.trace("trace信息");logger.debug("debug信息");logger.info("info信息");logger.warn("warn信息");logger.error("error信息");
}

通过slf4j日志门面无缝将log4j替换为logback

不修改任何源代码; 扔使用log4j的API

@Test
public void test09(){/*需求:假设我们项目一直以来使用的是log4j日志框架但是随着技术和需求的更新换代log4j已然不能够满足我们系统的需求我们现在就需要将系统中的日志实现重构为 slf4j+logback的组合在不触碰java源代码的情况下,将这个问题给解决掉首先将所有关于其他日志实现和门面依赖全部去除仅仅只留下log4j的依赖测试的过程中,只能使用log4j相关的组件此时需要将日志替换为slf4j+logback我们既然不用log4j了,就将log4j去除将slf4j日志门面和logback的日志实现依赖加入进来这样做,没有了log4j环境的支持,编译报错这个时候就需要使用桥接器来做这个需求了桥接器解决的是项目中日志的重构问题,当前系统中存在之前的日志API,可以通过桥接转换到slf4j的实现桥接器的使用步骤:1.去除之前旧的日志框架依赖<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>2.添加slf4j提供的桥接组件log4j相关的桥接器<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.25</version></dependency>桥接器加入后,代码编译就不报错了测试:日志信息输出输出格式为logback证明了现在使用的确实是slf4j门面+logback实现在重构之后,就会为我们造成这样一种假象使用的明明是log4j包下的日志组件资源但是真正日志的实现,却是使用slf4j门面+logback实现这就是桥接器给我们带来的效果注意:在桥接器加入之后,适配器就没有必要加入了桥接器和适配器不能同时导入依赖桥接器如果配置在适配器的上方,则运行报错,不同同时出现桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义*/Logger logger = LogManager.getLogger(SLF4JTest01.class);logger.info("info信息");
}@Test
public void test10(){/*在配置了桥接器之后,底层就是使用slf4j实现的日志分析其中原理通过getLogger进入Log4jLoggerFactoryLogger newInstance = new Logger(name); 新建logger对象进入构造方法protected Logger(String name) {super(name);}点击进入父类的构造方法Category(String name) {this.name = name;this.slf4jLogger = LoggerFactory.getLogger(name);if (this.slf4jLogger instanceof LocationAwareLogger) {this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;}}在这个Category构造方法中,核心代码this.slf4jLogger = LoggerFactory.getLogger(name);LoggerFactory来自于org.slf4j*/Logger logger = LogManager.getLogger(SLF4JTest01.class);
}

日志框架(3) : 日志门面、JCL介绍、SLF4J介绍相关推荐

  1. Spring Boot与日志 ——日志框架、日志配置||SLF4j使用||SpringBoot日志关系||切换日志框架

    1.日志框架 SLF4j使用 1.如何在系统中使用SLF4j 以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法: 给系统里面导入slf4j的jar和 lo ...

  2. SpringBoot和日志框架:缘由,日志框架的选择,使用,自定义配置,日志框架切换

    日志框架 缘由 如果像我们初学者,想知道代码运行到哪里了,一般都是会System.out.println() 进行输出到控制台查看代码运行的情况,好知道代码错误在哪里 但是大型的系统里面,如果有很多的 ...

  3. [转载]java日志框架log4j详细配置及与slf4j联合使用教程

    一.log4j基本用法 首先,配置log4j的jar,maven工程配置以下依赖,非maven工程从maven仓库下载jar添加到"build path" 1 2 3 4 5 &l ...

  4. java日志框架log4j详细配置及与slf4j联合使用教程

    最后更新于2017年02月09日 一.log4j基本用法 首先,配置log4j的jar,maven工程配置以下依赖,非maven工程从maven仓库下载jar添加到"build path&q ...

  5. 日志门面和日志框架(SpringBoot日志实现)

    一.Springboot日志实现简介 SpringBoot是现今市场上最火爆用来简化spring开发的框架,springboot日志也是开发常用的日志系统.SpringBoot默认就是使用SLF4J作 ...

  6. 日志框架-1 jul与log4j与logback介绍

    背景 日志框架一直没进行过细致的梳理,对其理解仅留在基本使用的程度:周五自测小功能时暴露了技术漏洞

  7. ZAP日志框架lumberjack日志归档库的分析使用

    本次我们从官方例程的角度出发,来分析学习如何让zap日志框架动起来 一. Zap官方例程 1.加糖版 logger, _ := zap.NewProduction() defer logger.Syn ...

  8. C语言编写的一个简单通用的日志框架----支持日志等级,日志颜色,打印到文件等设置

    日常工作中常常需要输出日志进行调试,本文提供了一个C语言编写的日志框架.可以输出日志到文件或者是终端,支持日志等级设置,不同日志等级可以设置不同的颜色,另外还可以设置进程名.模块名.函数名等的输出,能 ...

  9. C++plog库,轻量级日志框架(日志库)

    文章目录 为何建议程序员在项目中使用第三方日志库 第三方日志库plog的优势 plog主要头文件及使用方法解释 plog/Log.h plog/Appenders/ColorConsoleAppend ...

最新文章

  1. Apache2 httpd.conf 配置详解(一)
  2. unity3d Json解析工具类
  3. 卷积神经网络CNN(3)—— FCN(Fully Convolutional Networks)要点解释
  4. 库克:10年内可能离开苹果
  5. 计算机安全的加密技术,计算机安全加密技术研究(4篇)(共14695字).doc
  6. python class用法理解_带你全面理解python中self的用法
  7. 关于使用IDEA导入项目后依赖报错的解决方案
  8. BZOJ4355: Play with sequence(吉司机线段树)
  9. 使用Java制作一款简单的小游戏
  10. 【AI视野·今日Robot 机器人论文速览 第二十三期】Tue, 28 Sep 2021
  11. 大一java实训报告1500字_java实训报告两篇
  12. 科学计算与Matlab笔记:第4章:Matlab绘图
  13. python画频率直方图_用matplotlib画直方图(histogram)
  14. centos下sqlite3安装
  15. 如何将cgs2000的数据匹配到天地图坐标脱密的数据上
  16. HTTP 传输大文件的几种方案
  17. pinia中onAction
  18. 模块化UPS:卖点?买点?
  19. layerui 时间段搜索+后台处理
  20. 解决React中路由跳转报错:Cannot read property ‘push’ of undefined

热门文章

  1. 分数构造方法java,Java--构造方法
  2. 【已解决】535 Login Fail. Please enter your authorization code to login. More information in http://servi
  3. Javascript-style属性
  4. ElasticSearch权限控制实战
  5. 数据科学与大数据技术专业领域的实用工具
  6. OrgChart页面模仿编程简单记录
  7. ubuntu配置网卡
  8. Gitlab全量迁移
  9. Python获取《姜子牙》和《哪吒》的票房对比和评价分析
  10. ie型lfsr_LFSR:线性反馈移位寄存器及其应用