文章目录

  • 一、什么是日志门面
  • 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标记的使用

一、什么是日志门面

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组件结构

学习Java日志框架之——搞懂日志门面(JCL+SLF4J)_java

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

引入依赖:

<!--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、分析

(图片来自官网)

学习Java日志框架之——搞懂日志门面(JCL+SLF4J)_java_02


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: 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<URL> 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<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.class
        Enumeration<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、桥接器的使用

学习Java日志框架之——搞懂日志门面(JCL+SLF4J)_API_03

当我们老项目使用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); // 创建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信息");