日志框架Slf4j作用及其实现原理

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 日志框架Slf4j作用及其实现原理

1 设计模式门面模式

设计模式之门面模式与装饰器模式详解和应用:https://blog.csdn.net/ZGL_cyy/article/details/129073521


slf4j是门面模式的典型应用,因此在讲slf4j前,我们先简单回顾一下门面模式,


门面模式,其核心为外部与一个子系统的通信必须通过一个统一的外观对象进行,使得子系统更易于使用。


用一张图来表示门面模式的结构为:


1cacefaf697cdf3bd6131f4ed52c02c2.png


门面模式的核心为Facade即门面对象,门面对象核心为几个点:


知道所有子角色的功能和责任

将客户端发来的请求委派到子系统中,没有实际业务逻辑

不参与子系统内业务逻辑的实现

大致上来看,对门面模式的回顾到这里就可以了,开始接下来对SLF4J的学习。


2 slf4j源码解析

我们为什么要使用slf4j,举个例子:

我们自己的系统中使用了logback这个日志系统
我们的系统使用了A.jar,A.jar中使用的日志系统为log4j
我们的系统又使用了B.jar,B.jar中使用的日志系统为slf4j-simple
这样,我们的系统就不得不同时支持并维护logback、log4j、slf4j-simple三种日志框架,非常不便。

解决这个问题的方式就是引入一个适配层,由适配层决定使用哪一种日志系统,而调用端只需要做的事情就是打印日志而不需要关心如何打印日志,slf4j或者commons-logging就是这种适配层,slf4j是本文研究的对象。


从上面的描述,我们必须清楚地知道一点:slf4j只是一个日志标准,并不是日志系统的具体实现。理解这句话非常重要,slf4j只做两件事情:


提供日志接口

提供获取具体日志对象的方法

slf4j-simple、logback都是slf4j的具体实现,log4j并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12来实现slf4j。


为了更理解slf4j,我们先看例子,再读源码,相信读者朋友会对slf4j有更深刻的认识。


slf4j应用举例


上面讲了,我们先定义一个pom.xml,引入相关jar包:

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.3</version>
  <scope>compile</scope>
</dependency>
...
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jul-to-slf4j</artifactId>
  <version>1.7.30</version>
  <scope>compile</scope>
</dependency>

研究的便是slf4j及其实现logback的关系。


而我们的slf4j便是相当于一个Facade层,所用的日志打印都是通过slf4j来转发,但是具体的功能实现是由logback来实现,当然也可以由别的依赖来实现,比如slf4j-simple。


首先我们看springboot框架默认实现:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestLogger {
    private static final Logger log = LoggerFactory.getLogger(TestLogger.class);
    public static void main(String[] args) {
        log.info("test---------->>>>>>>>>>>><<<<<<<");
    }
}

归根结底,所有的用法都只是片面,我们要理解原理,还是要从源码入手。进入getLogger方法:

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 http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
            }
        }
        return logger;
    }

重点关注

getLogger(clazz.getName())

继续点击:

public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

日志打印的具体实现便在

public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    performInitialization();
                }
            }
        }
        switch(INITIALIZATION_STATE) {
        case 1:
            return SUBST_FACTORY;
        case 2:
            throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case 3:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case 4:
            return NOP_FALLBACK_FACTORY;
        default:
            throw new IllegalStateException("Unreachable code");
        }
    }

日志打印的重点便在于返回

StaticLoggerBinder.getSingleton().getLoggerFactory()

这个对象来实现具体的日志打印工作,那StaticLoggerBinder这个类又是从哪里来,是要干什么的呢?


我们通过实际代码执行可以知道,StaticLoggerBinder便是logback这个jar包提供对slf4j日志接口LoggerFactoryBinder的具体实现,也就是说实际的日志打印slf4j不能执行,只能通过接口的实现类StaticLoggerBinder来进行执行。


这样的好处便在于slf4j相当于只是提供一个接口或者说标准,但是具体的执行可以由其实现类来执行,这样只要是实现了slf4j标准接口的任意日志框架便都可以来执行日志打印。


通过这种方式slf4j可以同时支持多种日志框架,且无需任何配置,只需要引入特定的jar包让其拥有指定全类名的StaticLoggerBinder类即可。


那么这样也会导致另一个问题,如果系统引入了多个同时实现slf4j接口的类,那么系统怎么办,是否会报错?


这种特殊情况,slf4j也有做处理,其处理方式便是通过打印所有的引入实现类,然后由JVM虚拟机选择一个合适的实现类来执行日志打印。


这样既不会影响系统日志执行,也能使程序员通过日志,清楚的看出系统中存在哪些日志的实现类。


其具体代码在上

private static final void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }
    }

在bind执行:

private static final void bind() {
        try {
            String msg;
            try {
                Set<URL> staticLoggerBinderPathSet = null;
                if (!isAndroid()) {
                    staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                    reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
                }
                StaticLoggerBinder.getSingleton();
                INITIALIZATION_STATE = 3;
                reportActualBinding(staticLoggerBinderPathSet);
            } catch (NoClassDefFoundError var7) {
......

重点便在于


findPossibleStaticLoggerBinderPathSet();//找到潜在的slf4j实现类

reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//打印所有的实现类全类名

reportActualBinding(staticLoggerBinderPathSet);//打印实际的实现类全类名


详细方法如下:

static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while(paths.hasMoreElements()) {
                URL path = (URL)paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException var4) {
            Util.report("Error getting resources from path", var4);
        }
        return staticLoggerBinderPathSet;
    }

可以看到,其是通过

STATIC_LOGGER_BINDER_PATH

即,

private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class

也就是指定的全限定类名来进行加载的。


这样就涉及到一个问题, 不同的jar包依赖是可以创建同样的全限定类名的,这样会导致

 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);

出现多个具有同样全限定类名的类被重复找到。


那么在slf4j有多个实现的时候,如何保证其加载指定的实现呢?


实测:如果有多个日志实现的话,默认使用先导入的实现


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
11天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
116 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
1月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
216 3
|
21天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
83 1
|
1月前
|
数据采集 监控 Java
SpringBoot日志全方位超详细手把手教程,零基础可学习 日志如何配置及SLF4J的使用......
本文是关于SpringBoot日志的详细教程,涵盖日志的定义、用途、SLF4J框架的使用、日志级别、持久化、文件分割及格式配置等内容。
127 0
SpringBoot日志全方位超详细手把手教程,零基础可学习 日志如何配置及SLF4J的使用......
|
2月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
45 2
|
1月前
|
SQL XML 监控
SpringBoot框架日志详解
本文详细介绍了日志系统的重要性及其在不同环境下的配置方法。日志用于记录系统运行时的问题,确保服务的可靠性。文章解释了各种日志级别(如 info、warn、error 等)的作用,并介绍了常用的日志框架如 SLF4J 和 Logback。此外,还说明了如何在 SpringBoot 中配置日志输出路径及日志级别,包括控制台输出与文件输出的具体设置方法。通过这些配置,开发者能够更好地管理和调试应用程序。
|
2月前
|
Java
日志框架log4j打印异常堆栈信息携带traceId,方便接口异常排查
日常项目运行日志,异常栈打印是不带traceId,导致排查问题查找异常栈很麻烦。
|
2月前
|
运维 NoSQL Java
SpringBoot接入轻量级分布式日志框架GrayLog技术分享
在当今的软件开发环境中,日志管理扮演着至关重要的角色,尤其是在微服务架构下,分布式日志的统一收集、分析和展示成为了开发者和运维人员必须面对的问题。GrayLog作为一个轻量级的分布式日志框架,以其简洁、高效和易部署的特性,逐渐受到广大开发者的青睐。本文将详细介绍如何在SpringBoot项目中接入GrayLog,以实现日志的集中管理和分析。
224 1
|
3月前
|
存储 监控 Java
Java日志通关(三) - Slf4j 介绍
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第三篇。
|
3月前
|
人工智能 Java Spring
Spring框架下,如何让你的日志管理像‘AI’一样智能,提升开发效率的秘密武器!
【8月更文挑战第31天】日志管理在软件开发中至关重要,不仅能帮助开发者追踪问题和调试程序,还是系统监控和运维的重要工具。在Spring框架下,通过合理配置Logback等日志框架,可大幅提升日志管理效率。本文将介绍如何引入日志框架、配置日志级别、在代码中使用Logger,以及利用ELK等工具进行日志聚合和分析,帮助你构建高效、可靠的日志管理系统,为开发和运维提供支持。
64 0