文章目录

  • 系列文章目录
  • 一、什么是日志门面
    • 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)相关推荐

  1. 学习Java日志框架之——搞懂JUL(java.util.logging)

    文章目录 系列文章目录 一.JUL简介 二.JUL组件介绍 三.代码实例 1.入门案例 2.日志级别 (1)默认日志级别源码分析 3.自定义日志级别 4.将日志输出到文件中 5.Logger的父子关系 ...

  2. 学习日志框架之——搞懂logback

    文章目录 系列文章目录 一.logback概述 1.Logback简介 2.Logback中的组件 3.Logback配置文件 4.日志输出格式 二.使用示例 1.依赖导入 2.入门案例 3.配置文件 ...

  3. .NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(下)...

    18 | 日志框架:聊聊记日志的最佳姿势 除了使用 CreateLogger 指定 logger 的名称,实际上还可以借助容器来构造 logger,通常情况下我们会定义自己的类 namespace L ...

  4. .NET Core开发实战(第18课:日志框架:聊聊记日志的最佳姿势)--学习笔记(上)...

    18 | 日志框架:聊聊记日志的最佳姿势 源码链接: https://github.com/witskeeper/geektime/tree/master/samples/LoggingSimpleD ...

  5. 【深度学习计算机指令系统,彻底搞懂指令十大寻址方式】

    系列文章目录 1.<带你深挖计算机底层逻辑,打通你计算机基础知识的任督二脉> 2.<深度学习计算机底层原理,深度剖析存储器> 3.<基于内存全面理解高速缓冲存储器> ...

  6. Java程序员应该搞懂的六个问题

    Java程序员应该搞懂的六个问题 太平洋电脑网 作者:雷少 对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题 ...

  7. java log4j logback jcl_知识总结-Java日志框架Log4j、Log4j2、logback、slf4j、简介

    功能简介 上一篇介绍了为什么打印日志.什么时候打印日志以及怎么打印日志.本篇介绍下在项目开发中常见的日志组件以及关系. 先看一张图 接口:将所有日志实现适配到了一起,用统一的接口调用. 实现:目前主流 ...

  8. 【日志框架-笔记】JUL日志框架的介绍及其使用

    日志框架 JUL日志框架 入门程序 Logger直接的父子继承关系 Formatters 独立的配置文件进行编写日志 使用日志打印异常 学习日志无非就是为了四大目的: 问题诊断,调试程序: 错误定位, ...

  9. Java初学者都应该搞懂的六个问题

    对于这个系列里的问题,每个学Java的人都应该搞懂.当然,如果只是学Java玩玩就无所谓了.如果你认为自己已经超越初学者了,却不很懂这些问题,请将你自己重归初学者行列.内容均来自于CSDN的经典老贴. ...

最新文章

  1. Python2 与 Python3 区别
  2. 「最小细胞」的3D数字模型,计算揭示生命最基本的要求
  3. 一个简单XSS攻击示例及处理
  4. html5设计图的状态栏标准高度,HTML5 canvas自适应手机屏幕宽高度大小
  5. P2839 [国家集训队]middle
  6. Navigation Drawer介绍
  7. 前端技术演进(六):前端项目与技术实践
  8. oracle number 7 5,oracle上机练习6-7及答案
  9. Python字符串isprintable()
  10. Laravel 队列发送邮件
  11. vue-scroller的使用
  12. Linux c/c++ IDE(开发环境)
  13. Windows Azure 客户真实案例:交互式解决方案提供商获得了敏捷性,通过托管服务节省了成本...
  14. STL容器 之 vector
  15. 学习Samba基础命令详解之大话西游01
  16. java 读取 excel 科学计数
  17. 笔记11-继承修饰符
  18. C语言中数值后面跟字母解析
  19. linux c语言 cmos,c语言编写程序读写CMOS内容
  20. 基于JAVA的洗衣店订单管理系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署

热门文章

  1. 翌加科技:抖音昵称如何取比较好
  2. 解决华为手机隐藏虚拟按键Activity被重新创建的问题
  3. Photoneo3D相机以每秒40米的速度捕获运动中的物体图像
  4. Samba实现文件共享
  5. 求字符串长度(指针)
  6. MATLAB程序设计-4.三维绘图
  7. 【C++】C语言的malloc/free和C++的new/delete的异同
  8. 淘宝/天猫获得淘宝app商品详情原数据API,电商数据分析
  9. ESP8266-Arduino编程实例-GP2Y1010AU0F灰尘传感器驱动
  10. 龙舞唐门,京都樱花节光雕投影表演_数字体验_新浪博客