文章目录

系列文章目录一、什么是日志门面1、门面模式(外观模式)2、日志门面

二、了解JCL1、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案例

添加依赖:

commons-logging

commons-logging

1.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 API

cl.loadClass("org.apache.logging.log4j.spi.ExtendedLogger");

logApi = LogApi.LOG4J;

}

catch (ClassNotFoundException ex1) {

try {

// Try SLF4J 1.7 SPI

cl.loadClass("org.slf4j.spi.LocationAwareLogger");

logApi = LogApi.SLF4J_LAL;

}

catch (ClassNotFoundException ex2) {

try {

// Try SLF4J 1.7 API

cl.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.slf4j

slf4j-api

1.7.25

org.slf4j

slf4j-simple

1.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.slf4j

slf4j-api

1.7.25

org.slf4j

slf4j-simple

1.7.25

ch.qos.logback

logback-classic

1.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.slf4j

slf4j-api

1.7.25

ch.qos.logback

logback-classic

1.2.3

org.slf4j

slf4j-simple

1.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.slf4j

slf4j-api

1.7.25

ch.qos.logback

logback-classic

1.2.3

执行结果:

15:40:51.974 [main] INFO com.demo.slf4j.test01.SLF4JTest01 - 具体错误是:

java.lang.ClassNotFoundException: aaa

at 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:0

synchronized (LoggerFactory.class) {

if (INITIALIZATION_STATE == UNINITIALIZED) {

INITIALIZATION_STATE = ONGOING_INITIALIZATION; // ONGOING_INITIALIZATION:1

performInitialization(); // 核心初始化方法

}

}

}

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

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

if (!isAndroid()) {

// 查找所有日志实现

staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();

// 对于绑定多实现的处理,打印日志报告

reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);

}

// the next line does the binding

StaticLoggerBinder.getSingleton();

INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;

// 打印最终使用的日志实现

reportActualBinding(staticLoggerBinderPathSet);

fixSubstituteLoggers();

replayEvents();

// release all resources in SUBST_FACTORY

SUBST_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.class

Enumeration 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.slf4j

slf4j-api

1.7.25

org.slf4j

slf4j-nop

1.7.25

ch.qos.logback

logback-classic

1.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.slf4j

slf4j-api

1.7.25

org.slf4j

slf4j-log4j12

1.7.25

log4j

log4j

1.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.slf4j

slf4j-api

1.7.25

org.slf4j

slf4j-jdk14

1.7.25

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

7、桥接器的使用

当我们老项目使用log4j时:

log4j

log4j

1.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.slf4j

slf4j-api

1.7.25

ch.qos.logback

logback-classic

1.2.3

org.slf4j

log4j-over-slf4j

1.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); // 创建Logger

Logger 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获取的Logger

slf4jLogger = 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信息");

参考链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: