文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

从Slf4j源码角度分析阿里开发手册日志规约

2024-12-03 09:04

关注

本文转载自微信公众号「JAVA前线」,作者IT徐胖子 。转载本文请联系JAVA前线公众号。

 1 日志规约

《阿里巴巴开发手册》日志规约章节有一条强制规定:应用中不可直接使用日志系统(Log4j、Logback)API,而应依赖使用日志框架SLF4J中的API。使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一:

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3. private static final Logger logger = LoggerFactory.getLogger(Abc.class); 

我们在使用日志框架过程中会发现,日志框架种类很多如slf4j、log4j、logback等等,在引入依赖时很容易混淆。那么这些框架是什么关系、应该如何使用就是本文需要回答的问题。

2 实例分析

在编写代码之前我们首先了解slf4j全称,我认为这会对理解这个框架有所帮助:

  1. Simple Logging Facade for Java 

全称的含义就是Java简单日志门面,我们知道有一种设计模式被称为门面模式,其本质是化零为整,通过一个对象将散落在各处的功能整合在一起,这样外部只要通过与这个对象交互,由该对象选择具体实现细节。slf4j就是这样一个门面,应用程序只需要和slf4j进行交互,slf4j选择使用哪一个日志框架的具体实现。

2.1 slf4j-jdk14

(1) 引入依赖

  1.  
  2.   -- slf4j --> 
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.   -- jdk14 --> 
  10.    
  11.     org.slf4j 
  12.     slf4j-jdk14 
  13.     1.7.30 
  14.    
  15.  

 

 

(2) 代码实例

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 输出日志

  1. LogTest 
  2. 三月 14, 2021 11:39:14 上午 com.my.log.test.jdk14.LogTest main 
  3. 信息: info message 
  4. 三月 14, 2021 11:39:14 上午 com.my.log.test.jdk14.LogTest main 
  5. 严重: error message 

2.2 slf4j-simple

(1) 引入依赖

  1.  
  2.   -- slf4j --> 
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.   -- simple --> 
  10.    
  11.     org.slf4j 
  12.     slf4j-simple 
  13.     1.7.30 
  14.    
  15.  

 

 

(2) 代码实例

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 输出日志

  1. [main] INFO com.my.log.test.simple.LogTest - info message 
  2. LogTest 
  3. [main] ERROR com.my.log.test.simple.LogTest - error message 

2.3 logback

(1) 引入依赖

  1.  
  2.   -- slf4j --> 
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.   -- logback --> 
  10.    
  11.     ch.qos.logback 
  12.     logback-core 
  13.     1.2.3 
  14.    
  15.    
  16.     ch.qos.logback 
  17.     logback-classic 
  18.     1.2.3 
  19.    
  20.  

 

 

(2) 代码实例

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 输出日志

  1. 11:40:53.406 [main] INFO com.my.log.test.logbck.LogTest - info message 
  2. LogTest 
  3. 11:40:53.410 [main] ERROR com.my.log.test.logbck.LogTest - error message 

2.4 slf4j-log4j12

(1) 引入依赖

  1.  
  2.   -- slf4j --> 
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.   -- log4j12 --> 
  10.    
  11.     org.slf4j 
  12.     slf4j-log4j12 
  13.     1.7.30 
  14.    
  15.  

 

 

(2) 代码实例

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 日志配置

  1. 'http://jakarta.apache.org/log4j/'
  2.   name="myConsoleAppender" class="org.apache.log4j.ConsoleAppender"
  3.     "org.apache.log4j.PatternLayout"
  4.       name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" /> 
  5.      
  6.     --过滤器设置输出级别 --> 
  7.     "org.apache.log4j.varia.LevelRangeFilter"
  8.       name="levelMin" value="debug" /> 
  9.       name="levelMax" value="error" /> 
  10.       name="AcceptOnMatch" value="true" /> 
  11.      
  12.    
  13.    
  14.     "debug" /> 
  15.     "myConsoleAppender" /> 
  16.    
  17.  

 

 

 

 

 

(4) 输出日志

  1. [14 11:41:39,198 INFO ] [main] log4j.LogTest - info message 
  2. LogTest 
  3. [14 11:41:39,201 ERROR] [main] log4j.LogTest - error message 

3 源码分析

我们发现上述实例中Java代码并没有变化,只是将引用具体日志框架实现进行了替换,例如依赖从simple替换为log4j,具体日志服务实现就替换成了log4j,这到底是怎么实现的?我们通过阅读源码回答这个问题。

3.1 阅读准备

(1) 源码地址

目前最新版本2.0.0-alpha2-SNAPSHOT

  1. https://github.com/qos-ch/slf4j 

(2) 项目结构

我们从项目结构可以看出一些信息:门面是api模块,具体实现包括jdk14、log4j12、simple模块,需要注意logback是同一个作者的另一个项目不在本项目。

(3) 阅读入口

  1. package org.slf4j; 
  2.  
  3. public class NoBindingTest { 
  4.     public void testLogger() { 
  5.         Logger logger = LoggerFactory.getLogger(NoBindingTest.class); 
  6.         logger.debug("hello" + diff); 
  7.         assertTrue(logger instanceof NOPLogger); 
  8.     } 

3.2 源码分析

LoggerFactory.getLogger

  1. public final class LoggerFactory { 
  2.     public static Logger getLogger(Class clazz) { 
  3.         Logger logger = getLogger(clazz.getName()); 
  4.         if (DETECT_LOGGER_NAME_MISMATCH) { 
  5.             Class autoComputedCallingClass = Util.getCallingClass(); 
  6.             if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 
  7.                 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 
  8.                                           autoComputedCallingClass.getName())); 
  9.                 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 
  10.             } 
  11.         } 
  12.         return logger; 
  13.     } 

getLogger(clazz.getName())

  1. public final class LoggerFactory { 
  2.     public static Logger getLogger(String name) { 
  3.         ILoggerFactory iLoggerFactory = getILoggerFactory(); 
  4.         return iLoggerFactory.getLogger(name); 
  5.     } 

getILoggerFactory()

  1. public final class LoggerFactory { 
  2.     public static ILoggerFactory getILoggerFactory() { 
  3.         return getProvider().getLoggerFactory(); 
  4.     } 

getProvider()

  1. public final class LoggerFactory { 
  2.     static SLF4JServiceProvider getProvider() { 
  3.         if (INITIALIZATION_STATE == UNINITIALIZED) { 
  4.             synchronized (LoggerFactory.class) { 
  5.                 if (INITIALIZATION_STATE == UNINITIALIZED) { 
  6.                     INITIALIZATION_STATE = ONGOING_INITIALIZATION; 
  7.                     performInitialization(); 
  8.                 } 
  9.             } 
  10.         } 
  11.         switch (INITIALIZATION_STATE) { 
  12.         case SUCCESSFUL_INITIALIZATION: 
  13.             return PROVIDER; 
  14.         case NOP_FALLBACK_INITIALIZATION: 
  15.             return NOP_FALLBACK_FACTORY; 
  16.         case FAILED_INITIALIZATION: 
  17.             throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 
  18.         case ONGOING_INITIALIZATION: 
  19.             return SUBST_PROVIDER; 
  20.         } 
  21.         throw new IllegalStateException("Unreachable code"); 
  22.     } 

performInitialization()

  1. public final class LoggerFactory { 
  2.     private final static void performInitialization() { 
  3.         bind(); 
  4.         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 
  5.             versionSanityCheck(); 
  6.         } 
  7.     } 

bind()

  1. public final class LoggerFactory { 
  2.     private final static void bind() { 
  3.         try { 
  4.             // 核心代码 
  5.             List providersList = findServiceProviders(); 
  6.             reportMultipleBindingAmbiguity(providersList); 
  7.             if (providersList != null && !providersList.isEmpty()) { 
  8.              PROVIDER = providersList.get(0); 
  9.              PROVIDER.initialize(); 
  10.              INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 
  11.                 reportActualBinding(providersList); 
  12.             } 
  13.             // 省略代码 
  14.         } catch (Exception e) { 
  15.             failedBinding(e); 
  16.             throw new IllegalStateException("Unexpected initialization failure", e); 
  17.         } 
  18.     } 

findServiceProviders()

这是加载具体日志实现的核心方法,使用SPI机制加载所有SLF4JServiceProvider实现类:

  1. public final class LoggerFactory { 
  2.     private static List findServiceProviders() { 
  3.         ServiceLoader serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); 
  4.         List providerList = new ArrayList(); 
  5.         for (SLF4JServiceProvider provider : serviceLoader) { 
  6.             providerList.add(provider); 
  7.         } 
  8.         return providerList; 
  9.     } 

SPI(Service Provider Interface)是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件加载实现类,这样可以在运行时动态为接口替换实现类,通过SPI机制可以为程序提供拓展功能。本文以log4j为例说明使用SPI功能的三个步骤:

(a) 实现接口

  1. public class Log4j12ServiceProvider implements SLF4JServiceProvider 

(b) 配置文件

  1. 文件位置:src/main/resources/META-INF/services/ 
  2. 文件名称:org.slf4j.spi.SLF4JServiceProvider 
  3. 文件内容:org.slf4j.log4j12.Log4j12ServiceProvider 

(c) 服务加载

  1. public final class LoggerFactory { 
  2.     private static List findServiceProviders() { 
  3.         ServiceLoader serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); 
  4.         List providerList = new ArrayList(); 
  5.         for (SLF4JServiceProvider provider : serviceLoader) { 
  6.             providerList.add(provider); 
  7.         } 
  8.         return providerList; 
  9.     } 

只要各种日志实现框架按照SPI约定进行代码编写和配置文件声明,即可以被LoggerFactory加载,slf4j会获取第一个作为实现。

  1. public final class LoggerFactory { 
  2.     private final static void bind() { 
  3.         try { 
  4.             // 使用SPI机制加载具体日志实现 
  5.             List providersList = findServiceProviders(); 
  6.             reportMultipleBindingAmbiguity(providersList); 
  7.             if (providersList != null && !providersList.isEmpty()) { 
  8.                 // 获取第一个实现 
  9.                 PROVIDER = providersList.get(0); 
  10.                 PROVIDER.initialize(); 
  11.                 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 
  12.                 reportActualBinding(providersList); 
  13.             } 
  14.             // 省略代码 
  15.         } catch (Exception e) { 
  16.             failedBinding(e); 
  17.             throw new IllegalStateException("Unexpected initialization failure", e); 
  18.         } 
  19.     } 

分析到这里我们的问题应该可以得到解答:假设我们项目只引入了slf4j和log4j,相当于只有log4j这一个具体实现,那么本项目就会使用log4j框架。如果将log4j依赖换为logback,那么项目在不改动代码的情况下会使用logback框架。

4 文章总结

本文我们从阿里开发手册日志规约出发,首先分析了如何使用不同的日志框架,然后我们从问题出发(不修改代码即可替换具体日志框架)进行slf4j源码阅读,从源码中我们知道实现核心是SPI机制,这个机制可以动态加载具体日志实现。关于SPI源码分析请参看笔者文章JDK SPI机制,希望本文对大家有所帮助。

 

来源:JAVA前线内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯