学习Java日志框架之——搞懂日志门面(JCL+SLF4J)
文章目录
- 系列文章目录
- 一、什么是日志门面
- 1、门面模式(外观模式)
- 2、日志门面
- 二、了解JCL
- 1、JCL组件结构
- 2、JCL案例
- (1)JCL默认实现
- (2)导入log4j测试原有程序
- 三、SLF4J简介
- 四、SLF4J基本使用
- 1、入门案例
- 2、动态打印信息
- 3、打印异常信息
- 五、SLF4J集成其他日志实现
- 1、分析
- 2、代码
- 3、slf4j同时集成多个日志实现结果分析
- (1)slf4j-simple日志实现的基础上,又集成了logback
- (2)在logback之后,又集成了slf4j-simple依赖
- (3)只保留logback依赖
- (4)总结
- (5)源码分析
- 4、使用slf4j-nop禁止日志打印
- 5、slf4j集成log4j(使用适配器)
- 6、slf4j集成jul(使用适配器)
- 7、桥接器的使用
- (1)源码分析
- 8、Marker标记的使用
系列文章目录
学习Java日志框架之——搞懂JUL(java.util.logging)
学习Java日志框架之——搞懂log4j
学习Java日志框架之——搞懂日志门面(JCL+SLF4J)
学习日志框架之——搞懂logback
学习日志框架之——log4j2入门
log4j2扩展——打印自定义日志输出格式,将日志输出为json或自定义
一、什么是日志门面
1、门面模式(外观模式)
门面模式(Facade Pattern),是GoF23种设计模式其中之一,也称之为外观模式,其核心为:外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。
外观模式主要是体现了Java中的一种好的封装性。更简单的说,就是对外提供的接口要尽可能的简单。
2、日志门面
常见的日志实现:JUL、log4j、logback、log4j2
常见的日志门面 :JCL、slf4j
出现顺序 :log4j -->JUL–>JCL–> slf4j --> logback --> log4j2
JUL、log4j、logback、log4j2这几种日志框架,每一种日志框架都有自己单独的API,要使用对应的框架就要使用其对应的API,这就大大的增加应用程序代码对于日志框架的耦合性。
为了解决这个问题,就是在日志框架和应用程序之间架设一个沟通的桥梁,对于应用程序来说,无论底层的日志框架如何变,都不需要有任何感知。只要门面服务做的足够好,随意换另外一个日志框架,应用程序不需要修改任意一行代码,就可以直接上线。
二、了解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实例)
1、JCL组件结构
2、JCL案例
添加依赖:
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version>
</dependency>
(1)JCL默认实现
JCL默认的情况下,会使用JUL日志框架做日志的记录操作。
JCL使用原则:如果有log4j,优先使用log4j,如果没有任何第三方日志框架的时候,我们使用的就是JUL。
Log log = LogFactory.getLog(JCLTest01.class);
log.info("info信息");
(2)导入log4j测试原有程序
在集成了log4j环境后,使用的又是log4j,通过测试观察,虽然日志框架发生了变化,但是代码完全没有改变。
我们分析一下LogFactory的getLog执行逻辑:
// org.apache.commons.logging.LogFactory#getLog(java.lang.String)
public static Log getLog(String name) {switch (logApi) {case LOG4J:return Log4jDelegate.createLog(name);case SLF4J_LAL:return Slf4jDelegate.createLocationAwareLog(name);case SLF4J:return Slf4jDelegate.createLog(name);default:// Defensively use lazy-initializing delegate class here as well since the// java.logging module is not present by default on JDK 9. We are requiring// its presence if neither Log4j nor SLF4J is available; however, in the// case of Log4j or SLF4J, we are trying to prevent early initialization// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly// trying to parse the bytecode for all the cases of this switch clause.return JavaUtilDelegate.createLog(name);}
}
我们发现,会通过logApi来判断加载的日志Log。
在LogFactory的静态代码块中,会挨个判断加载的日志类,会根据类的存在与否,依次加载Log4j、slf4j、JUL:
static {ClassLoader cl = LogFactory.class.getClassLoader();try {// Try Log4j 2.x APIcl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");logApi = LogApi.LOG4J;}catch (ClassNotFoundException ex1) {try {// Try SLF4J 1.7 SPIcl.loadClass("org.slf4j.spi.LocationAwareLogger");logApi = LogApi.SLF4J_LAL;}catch (ClassNotFoundException ex2) {try {// Try SLF4J 1.7 APIcl.loadClass("org.slf4j.Logger");logApi = LogApi.SLF4J;}catch (ClassNotFoundException ex3) {// Keep java.util.logging as default}}}
}
三、SLF4J简介
简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。 当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。所以我们可以得出SLF4J最重要的两个功能就是对于日志框架的绑定以及日志框架的桥接。
官方网站: https://www.slf4j.org/
通常,我们依赖的某些组件依赖于SLF4J以外的日志API。我们可能还假设这些组件在不久的将来不会切换到SLF4J。为了处理这种情况,SLF4J附带了几个桥接模块,这些模块会将对log4j,JCL和java.util.logging API的调用重定向为行为,就好像是对SLF4J API进行的操作一样。
四、SLF4J基本使用
1、入门案例
引入依赖:
<!--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>
SLF4J对日志的级别划分trace、debug、info、warn、error五个级别
- trace:日志追踪信息
- debug:日志详细信息
- info:日志的关键信息 默认打印级别
- warn:日志警告信息
- error:日志错误信息
如果在没有任何其他日志实现框架集成的基础之上,slf4j使用的就是自带的框架slf4j-simple,slf4j-simple也必须以单独依赖的形式导入进来。
// 都是slf4j包下的
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
logger.trace("trace信息");
logger.debug("debug信息");
logger.info("info信息");
logger.warn("warn信息");
logger.error("error信息");
2、动态打印信息
我们输出动态的信息时,也可以使用占位符的形式来代替字符串的拼接。
我们有些时候输出的日志信息,需要我们搭配动态的数据,有可能是信息,有可能是数据库表中的数据。总之我们这样做最大的好处就是能够让日志打印变得更加灵活,如果是通过拼接字符串的形式,不仅麻烦,而且更重要的是可读性差。
我们的日志打印是支持以替代符的形式做日志信息拼接的,一般情况下,几乎所有的日志实现产品,都会提供这种基础功能。
// 都是slf4j包下的
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
String name = "zs";
int age = 23;
//logger.info("学生信息-姓名:"+name+";年龄:"+age);
logger.info("学生信息-姓名:{},年龄:{}",name,age);
{}作为占位符,后面的参数代表花括号要替换的值。
3、打印异常信息
一般情况下,我们在开发中的异常信息,都是记录在控制台上(我们开发环境的一种日志打印方式),我们会根据异常信息提取出有用的线索,来调试bug。
但是在真实生产环境中(项目上线),对于服务器或者是系统相关的问题,在控制台上其实也会提供相应的异常或者错误信息的输出,但是这种错误输出方式(输出的时间,位置,格式…)都是服务器系统默认的。
我们可以通过日志技术,选择将异常以日志打印的方式,进行输出,查看输出的时间,位置(控制台,文件),格式,完全由我们自己去进行定义。
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集成其他日志实现
1、分析
(图片来自官网)
SLF4J日志门面,共有3种情况对日志实现进行绑定:
- 1.在没有绑定任何日志实现的基础之上,日志是不能够绑定实现任何功能的,值得大家注意的是,通过我们刚刚的演示,slf4j-simple是slf4j官方提供的。使用的时候,也是需要导入依赖,自动绑定到slf4j门面上。如果不导入,slf4j 核心依赖是不提供任何实现的。
- 2.logback和simple(包括nop)都是slf4j门面时间线后面提供的日志实现,所以API完全遵循slf4j进行的设计。那么我们只需要导入想要使用的日志实现依赖,即可与slf4j无缝衔接。值得一提的是nop虽然也划分到实现中了,但是他是指不实现日志记录。
- 3.log4j和JUL都是slf4j门面时间线前面的日志实现,所以API不遵循slf4j进行设计。需要通过适配桥接的技术,完成的与日志门面的衔接。
2、代码
以下测试均使用同样的java代码,因为主要测试和学习包依赖以及slf4j的基本使用。
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);try {Class.forName("aaa");
} catch (ClassNotFoundException e) {logger.info("具体错误是:",e);
}
3、slf4j同时集成多个日志实现结果分析
(1)slf4j-simple日志实现的基础上,又集成了logback
<!--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><!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
我们查看执行结果:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
[main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:
java.lang.ClassNotFoundException: aaa
通过测试,日志是打印出来了 java.lang.ClassNotFoundException: aaa
但是通过SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]这一句我们可以发现,虽然集成了logback,但是我们现在使用的仍然是slf4j-simple。
只要出现了这个提示:LF4J: Class path contains multiple SLF4J bindings.,在slf4j环境下,证明同时出现了多个日志实现。
(2)在logback之后,又集成了slf4j-simple依赖
<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<!--slf4j 自带的简单日志实现 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.25</version>
</dependency>
此时打印结果:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/org/slf4j/slf4j-simple/1.7.25/slf4j-simple-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
15:38:55.178 [main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:
java.lang.ClassNotFoundException: aaa
我们发现,默认使用的就是logback依赖,但是仍然提示有多个依赖。
(3)只保留logback依赖
<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
执行结果:
15:40:51.974 [main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:
java.lang.ClassNotFoundException: aaaat java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
我们发现,slf4j门面使用的就是logback日志实现,这一次没有多余的提示信息。
所以在实际应用的时候,我们一般情况下,仅仅只是做一种日志实现的集成就可以了。
(4)总结
通过以上测试,我们会发现虽然底层的日志实现变了,但是源代码完全没有改变。
这就是日志门面给我们带来最大的好处,在底层真实记录日志的时候,不需要应用去做任何的了解应用只需要去记slf4j的API就可以了。
值得一提的是,我们虽然底层使用的是log4j做的打印,但是从当前代码使用来看,我们其实使用的仍然是slf4j日志门面,至于日志是log4j打印的(或者是logback打印的)都是由slf4j进行操作的,我们不用操心。
(5)源码分析
我们进入getLogger的源码:
// org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
public static Logger getLogger(Class<?> clazz) {Logger logger = getLogger(clazz.getName()); // 执行重载方法if (DETECT_LOGGER_NAME_MISMATCH) {Class<?> autoComputedCallingClass = Util.getCallingClass();if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),autoComputedCallingClass.getName()));Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");}}return logger;
}
// org.slf4j.LoggerFactory#getLogger(java.lang.String)
public static Logger getLogger(String name) {ILoggerFactory iLoggerFactory = getILoggerFactory(); // 获取Logger工厂实现return iLoggerFactory.getLogger(name);
}
// org.slf4j.LoggerFactory#getILoggerFactory
public static ILoggerFactory getILoggerFactory() {// 双重锁检查,// INITIALIZATION_STATE :默认为0,表示是否被初始化过if (INITIALIZATION_STATE == UNINITIALIZED) {// UNINITIALIZED:0synchronized (LoggerFactory.class) {if (INITIALIZATION_STATE == UNINITIALIZED) {INITIALIZATION_STATE = ONGOING_INITIALIZATION; // ONGOING_INITIALIZATION:1performInitialization(); // 核心初始化方法}}}switch (INITIALIZATION_STATE) {case SUCCESSFUL_INITIALIZATION:return StaticLoggerBinder.getSingleton().getLoggerFactory();case NOP_FALLBACK_INITIALIZATION:return NOP_FALLBACK_FACTORY;case FAILED_INITIALIZATION:throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);case ONGOING_INITIALIZATION:// support re-entrant behavior.// See also http://jira.qos.ch/browse/SLF4J-97return SUBST_FACTORY;}throw new IllegalStateException("Unreachable code");
}
我们进入到performInitialization方法:
// org.slf4j.LoggerFactory#performInitialization
private final static void performInitialization() {bind(); // 绑定if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {versionSanityCheck();}
}// org.slf4j.LoggerFactory#bind
private final static void bind() {try {// N多个日志框架的实现Set<URL> staticLoggerBinderPathSet = null;// skip check under android, see also// http://jira.qos.ch/browse/SLF4J-328if (!isAndroid()) {// 查找所有日志实现staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();// 对于绑定多实现的处理,打印日志报告reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);}// the next line does the bindingStaticLoggerBinder.getSingleton();INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;// 打印最终使用的日志实现reportActualBinding(staticLoggerBinderPathSet);fixSubstituteLoggers();replayEvents();// release all resources in SUBST_FACTORYSUBST_FACTORY.clear();} catch (NoClassDefFoundError ncde) {String msg = ncde.getMessage();if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");Util.report("Defaulting to no-operation (NOP) logger implementation");Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");} else {failedBinding(ncde);throw ncde;}} catch (java.lang.NoSuchMethodError nsme) {String msg = nsme.getMessage();if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {INITIALIZATION_STATE = FAILED_INITIALIZATION;Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");Util.report("Your binding is version 1.5.5 or earlier.");Util.report("Upgrade your binding to version 1.6.x.");}throw nsme;} catch (Exception e) {failedBinding(e);throw new IllegalStateException("Unexpected initialization failure", e);}
}
我们看一下findPossibleStaticLoggerBinderPathSet方法:
// org.slf4j.LoggerFactory#findPossibleStaticLoggerBinderPathSet
static Set<URL> findPossibleStaticLoggerBinderPathSet() {// use Set instead of list in order to deal with bug #138// LinkedHashSet appropriate here because it preserves insertion order// during iteration// 有序不可重复的集合对象Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();try {ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();// 声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现// STATIC_LOGGER_BINDER_PATH:org/slf4j/impl/StaticLoggerBinder.classEnumeration<URL> paths;if (loggerFactoryClassLoader == null) {paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);} else {paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);}// 将path放入LinkedHashSet并返回while (paths.hasMoreElements()) {URL path = paths.nextElement();staticLoggerBinderPathSet.add(path);}} catch (IOException ioe) {Util.report("Error getting resources from path", ioe);}return staticLoggerBinderPathSet;
}
StaticLoggerBinder就是我们slf4j的适配器。
在每个日志的适配器中(log4j、logback、jul等),都有一个StaticLoggerBinder类,如果引入了对应的适配器包,就会查找到该类。
比如说log4j的适配器中StaticLoggerBinder类,会默认会创建Log4jLoggerFactory:
private StaticLoggerBinder() {loggerFactory = new Log4jLoggerFactory();
}
此时我们继续回到bind方法的reportMultipleBindingAmbiguity逻辑,用于打印日志报告:
// org.slf4j.LoggerFactory#reportMultipleBindingAmbiguity
private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {Util.report("Class path contains multiple SLF4J bindings.");for (URL path : binderPathSet) {Util.report("Found binding in [" + path + "]");}Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");}
}
由上分析,在真实生产环境中,slf4j只绑定一个日志实现框架就可以了,绑定多个,默认使用导入依赖的第一个,而且会产生没有必要的警告信息。
4、使用slf4j-nop禁止日志打印
<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!-- 导入nop -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-nop</artifactId><version>1.7.25</version>
</dependency>
<!-- logback依赖 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
将slf4j-nop放在依赖最上面,默认就会使用slf4j-nop(之前我们总结的,日志集成会优先集成依赖的第一种)。
打印结果就会出现:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/org/slf4j/slf4j-nop/1.7.25/slf4j-nop-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/Admin/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.helpers.NOPLoggerFactory]
我们自定义的日志就被禁止了。
5、slf4j集成log4j(使用适配器)
由于log4j是在slf4j之前出品的日志框架实现,所以并没有遵循slf4j的API规范。
如果想要使用slf4j,需要绑定一个适配器,叫做slf4j-log4j12,再导入log4j的实现。
<!--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>
<!-- 导入log4j依赖 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
如果不导入slf4j-log4j12适配实现,会提示以下信息:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
log4j的使用,需要搭配其配置文件,具体log4j的使用请移步:
学习Java日志框架之——搞懂log4j
可以看出,虽然我们使用的是slf4j,但是底层完全是log4j的使用,这就是日志门面的强大之处。
6、slf4j集成jul(使用适配器)
同样要引入jul的适配器。
<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<!--jul适配器-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.25</version>
</dependency>
因为jul是jdk默认实现,所以不需要额外导入包,只需要一个适配器即可。
7、桥接器的使用
当我们老项目使用log4j时:
<!-- 导入log4j依赖 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;Logger logger = LogManager.getLogger(SLF4JTest01.class);
logger.info("info信息");
此时我们项目升级,想使用slf4j+logback的形式,在不触碰java源代码的情况下,需要怎么做?此时桥接器的用处就体现出来了!
将log4j去除,将slf4j日志门面和logback的日志实现依赖加入进来,这样做,没有了log4j环境的支持,编译报错。此时引入log4j的桥接器,原来的代码以及包都不需要修改!新的日志输出,就是logback的输出了。
<!--slf4j 核心依赖-->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>
<!--桥接器-->
<dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.25</version>
</dependency>
在重构之后,就会为我们造成这样一种假象,使用的明明是log4j包下的日志组件资源,但是真正日志的实现,却是使用slf4j门面+logback实现,这就是桥接器给我们带来的效果。
注意:在桥接器加入之后,适配器就没有必要加入了,桥接器和适配器不能同时导入依赖,桥接器如果配置在适配器的上方,则运行报错,不同同时出现,桥接器如果配置在适配器的下方,则不会执行桥接器,没有任何的意义。
(1)源码分析
我们进入到LogManager.getLogger方法,发现该LogManager已经是log4j-over-slf4j包下的了,已经不是log4j包下的了:
// org.apache.log4j.LogManager#getLogger(java.lang.Class)
public static Logger getLogger(final Class clazz) {return Log4jLoggerFactory.getLogger(clazz.getName());
}// org.apache.log4j.Log4jLoggerFactory#getLogger(java.lang.String)
public static Logger getLogger(String name) {org.apache.log4j.Logger instance = log4jLoggers.get(name);if (instance != null) {return instance;} else {Logger newInstance = new Logger(name); // 创建LoggerLogger oldInstance = log4jLoggers.putIfAbsent(name, newInstance);return oldInstance == null ? newInstance : oldInstance;}
}
我们查看Logger的构造方法:
// org.apache.log4j.Logger#Logger
protected Logger(String name) {super(name);
}// org.apache.log4j.Category#Category
Category(String name) {this.name = name;// 下面的逻辑就是通过Slf4j获取的Loggerslf4jLogger = LoggerFactory.getLogger(name);if (slf4jLogger instanceof LocationAwareLogger) {locationAwareLogger = (LocationAwareLogger) slf4jLogger;}
}
8、Marker标记的使用
Marker用于给日志打一个标记,通常用于过滤器来过滤日志。
Logger logger = LoggerFactory.getLogger(SLF4JTest01.class);
Marker marker = MarkerFactory.getMarker("test_marker");logger.info("INFO信息");
logger.info(marker, "INFO信息");
学习Java日志框架之——搞懂日志门面(JCL+SLF4J)相关推荐
- 学习Java日志框架之——搞懂JUL(java.util.logging)
文章目录 系列文章目录 一.JUL简介 二.JUL组件介绍 三.代码实例 1.入门案例 2.日志级别 (1)默认日志级别源码分析 3.自定义日志级别 4.将日志输出到文件中 5.Logger的父子关系 ...
- 学习日志框架之——搞懂logback
文章目录 系列文章目录 一.logback概述 1.Logback简介 2.Logback中的组件 3.Logback配置文件 4.日志输出格式 二.使用示例 1.依赖导入 2.入门案例 3.配置文件 ...
- .NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(下)...
18 | 日志框架:聊聊记日志的最佳姿势 除了使用 CreateLogger 指定 logger 的名称,实际上还可以借助容器来构造 logger,通常情况下我们会定义自己的类 namespace L ...
- .NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(上)...
18 | 日志框架:聊聊记日志的最佳姿势 源码链接: https://github.com/witskeeper/geektime/tree/master/samples/LoggingSimpleD ...
- 【深度学习计算机指令系统,彻底搞懂指令十大寻址方式】
系列文章目录 1.<带你深挖计算机底层逻辑,打通你计算机基础知识的任督二脉> 2.<深度学习计算机底层原理,深度剖析存储器> 3.<基于内存全面理解高速缓冲存储器> ...
- Java程序员应该搞懂的六个问题
Java程序员应该搞懂的六个问题 太平洋电脑网 作者:雷少 对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题 ...
- java log4j logback jcl_知识总结-Java日志框架Log4j、Log4j2、logback、slf4j、简介
功能简介 上一篇介绍了为什么打印日志.什么时候打印日志以及怎么打印日志.本篇介绍下在项目开发中常见的日志组件以及关系. 先看一张图 接口:将所有日志实现适配到了一起,用统一的接口调用. 实现:目前主流 ...
- 【日志框架-笔记】JUL日志框架的介绍及其使用
日志框架 JUL日志框架 入门程序 Logger直接的父子继承关系 Formatters 独立的配置文件进行编写日志 使用日志打印异常 学习日志无非就是为了四大目的: 问题诊断,调试程序: 错误定位, ...
- Java初学者都应该搞懂的六个问题
对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列.内容均来自于CSDN的经典老贴. ...
最新文章
- Python2 与 Python3 区别
- 「最小细胞」的3D数字模型,计算揭示生命最基本的要求
- 一个简单XSS攻击示例及处理
- html5设计图的状态栏标准高度,HTML5 canvas自适应手机屏幕宽高度大小
- P2839 [国家集训队]middle
- Navigation Drawer介绍
- 前端技术演进(六):前端项目与技术实践
- oracle number 7 5,oracle上机练习6-7及答案
- Python字符串isprintable()
- Laravel 队列发送邮件
- vue-scroller的使用
- Linux c/c++ IDE(开发环境)
- Windows Azure 客户真实案例:交互式解决方案提供商获得了敏捷性,通过托管服务节省了成本...
- STL容器 之 vector
- 学习Samba基础命令详解之大话西游01
- java 读取 excel 科学计数
- 笔记11-继承修饰符
- C语言中数值后面跟字母解析
- linux c语言 cmos,c语言编写程序读写CMOS内容
- 基于JAVA的洗衣店订单管理系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
热门文章
- 翌加科技:抖音昵称如何取比较好
- 解决华为手机隐藏虚拟按键Activity被重新创建的问题
- Photoneo3D相机以每秒40米的速度捕获运动中的物体图像
- Samba实现文件共享
- 求字符串长度(指针)
- MATLAB程序设计-4.三维绘图
- 【C++】C语言的malloc/free和C++的new/delete的异同
- 淘宝/天猫获得淘宝app商品详情原数据API,电商数据分析
- ESP8266-Arduino编程实例-GP2Y1010AU0F灰尘传感器驱动
- 龙舞唐门,京都樱花节光雕投影表演_数字体验_新浪博客