学习Java日志框架之——搞懂日志门面(JCL+SLF4J)
创始人
2025-06-01 19:14:16
0

文章目录

  • 一、什么是日志门面
    • 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)源码分析

一、什么是日志门面

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

添加依赖:

commons-loggingcommons-logging1.2

(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、入门案例

引入依赖:


org.slf4jslf4j-api1.7.25


org.slf4jslf4j-simple1.7.25

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


org.slf4jslf4j-api1.7.25


org.slf4jslf4j-simple1.7.25

ch.qos.logbacklogback-classic1.2.3

我们查看执行结果:

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


org.slf4jslf4j-api1.7.25


ch.qos.logbacklogback-classic1.2.3


org.slf4jslf4j-simple1.7.25

此时打印结果:

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


org.slf4jslf4j-api1.7.25


ch.qos.logbacklogback-classic1.2.3

执行结果:

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 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 findPossibleStaticLoggerBinderPathSet() {// use Set instead of list in order to deal with bug #138// LinkedHashSet appropriate here because it preserves insertion order// during iteration// 有序不可重复的集合对象Set staticLoggerBinderPathSet = new LinkedHashSet();try {ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();// 声明了枚举类的路径,经过if else判断,以获取系统中都有哪些日志实现// STATIC_LOGGER_BINDER_PATH:org/slf4j/impl/StaticLoggerBinder.classEnumeration 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 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禁止日志打印


org.slf4jslf4j-api1.7.25


org.slf4jslf4j-nop1.7.25


ch.qos.logbacklogback-classic1.2.3

将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的实现。


org.slf4jslf4j-api1.7.25


org.slf4jslf4j-log4j121.7.25


log4jlog4j1.2.17

如果不导入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的适配器。


org.slf4jslf4j-api1.7.25


org.slf4jslf4j-jdk141.7.25

因为jul是jdk默认实现,所以不需要额外导入包,只需要一个适配器即可。

7、桥接器的使用

在这里插入图片描述

当我们老项目使用log4j时:


log4jlog4j1.2.17

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的输出了。


org.slf4jslf4j-api1.7.25

ch.qos.logbacklogback-classic1.2.3


org.slf4jlog4j-over-slf4j1.7.25

在重构之后,就会为我们造成这样一种假象,使用的明明是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;}
}

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...