<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從SLF4J源碼角度分析阿里開發(fā)手冊日志規(guī)約

          共 20478字,需瀏覽 41分鐘

           ·

          2021-03-20 10:13


          JAVA前線 


          歡迎大家關注公眾號「JAVA前線」查看更多精彩分享,主要包括源碼分析、實際應用、架構思維、職場分享、產品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學習


          1 日志規(guī)約

          《阿里巴巴開發(fā)手冊》日志規(guī)約章節(jié)有一條強制規(guī)定:應用中不可直接使用日志系統(tǒng)(Log4j、Logback)API,而應依賴使用日志框架SLF4J中的API。使用門面模式的日志框架,有利于維護和各個類的日志處理方式統(tǒng)一:

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

          我們在使用日志框架過程中會發(fā)現,日志框架種類很多如slf4j、log4j、logback等等,在引入依賴時很容易混淆。那么這些框架是什么關系、應該如何使用就是本文需要回答的問題。


          2 實例分析

          在編寫代碼之前我們首先了解slf4j全稱,我認為這會對理解這個框架有所幫助:

          Simple Logging Facade for Java

          全稱的含義就是Java簡單日志門面,我們知道有一種設計模式被稱為門面模式,其本質是化零為整,通過一個對象將散落在各處的功能整合在一起,這樣外部只要通過與這個對象交互,由該對象選擇具體實現細節(jié)。slf4j就是這樣一個門面,應用程序只需要和slf4j進行交互,slf4j選擇使用哪一個日志框架的具體實現。


          2.1 slf4j-jdk14

          (1) 引入依賴

          <dependencies>
            <!-- slf4j -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-api</artifactId>
              <version>1.7.30</version>
            </dependency>
            
            <!-- jdk14 -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-jdk14</artifactId>
              <version>1.7.30</version>
            </dependency>
          </dependencies>

          (2) 代碼實例

          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;

          public class LogTest {
              private final static Logger logger = LoggerFactory.getLogger(LogTest.class);
              public static void main(String[] args) {
                  logger.info("info message");
                  System.out.println("LogTest");
                  logger.error("error message");
              }
          }

          (3) 輸出日志

          LogTest
          三月 14, 2021 11:39:14 上午 com.my.log.test.jdk14.LogTest main
          信息: info message
          三月 14, 2021 11:39:14 上午 com.my.log.test.jdk14.LogTest main
          嚴重: error message

          2.2 slf4j-simple

          (1) 引入依賴

          <dependencies>
            <!-- slf4j -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-api</artifactId>
              <version>1.7.30</version>
            </dependency>
            
            <!-- simple -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-simple</artifactId>
              <version>1.7.30</version>
            </dependency>
          </dependencies>

          (2) 代碼實例

          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;

          public class LogTest {
              private final static Logger logger = LoggerFactory.getLogger(LogTest.class);
              public static void main(String[] args) {
                  logger.info("info message");
                  System.out.println("LogTest");
                  logger.error("error message");
              }
          }

          (3) 輸出日志

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

          2.3 logback

          (1) 引入依賴

          <dependencies>
            <!-- slf4j -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-api</artifactId>
              <version>1.7.30</version>
            </dependency>
            
            <!-- logback -->
            <dependency>
              <groupId>ch.qos.logback</groupId>
              <artifactId>logback-core</artifactId>
              <version>1.2.3</version>
            </dependency>
            <dependency>
              <groupId>ch.qos.logback</groupId>
              <artifactId>logback-classic</artifactId>
              <version>1.2.3</version>
            </dependency>
          </dependencies>

          (2) 代碼實例

          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;

          public class LogTest {
              private final static Logger logger = LoggerFactory.getLogger(LogTest.class);
              public static void main(String[] args) {
                  logger.info("info message");
                  System.out.println("LogTest");
                  logger.error("error message");
              }
          }

          (3) 輸出日志

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

          2.4 slf4j-log4j12

          (1) 引入依賴

          <dependencies>
            <!-- slf4j -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-api</artifactId>
              <version>1.7.30</version>
            </dependency>
            
            <!-- log4j12 -->
            <dependency>
              <groupId>org.slf4j</groupId>
              <artifactId>slf4j-log4j12</artifactId>
              <version>1.7.30</version>
            </dependency>
          </dependencies>

          (2) 代碼實例

          import org.slf4j.Logger;
          import org.slf4j.LoggerFactory;

          public class LogTest {
              private final static Logger logger = LoggerFactory.getLogger(LogTest.class);
              public static void main(String[] args) {
                  logger.info("info message");
                  System.out.println("LogTest");
                  logger.error("error message");
              }
          }

          (3) 日志配置

          <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
            <appender name="myConsoleAppender" class="org.apache.log4j.ConsoleAppender">
              <layout class="org.apache.log4j.PatternLayout">
                <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" />
              </layout>
              <!--過濾器設置輸出級別 -->
              <filter class="org.apache.log4j.varia.LevelRangeFilter">
                <param name="levelMin" value="debug" />
                <param name="levelMax" value="error" />
                <param name="AcceptOnMatch" value="true" />
              </filter>
            </appender>
            <root>
              <priority value="debug" />
              <appender-ref ref="myConsoleAppender" />
            </root>
          </log4j:configuration>

          (4) 輸出日志

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

          3 源碼分析

          我們發(fā)現上述實例中Java代碼并沒有變化,只是將引用具體日志框架實現進行了替換,例如依賴從simple替換為log4j,具體日志服務實現就替換成了log4j,這到底是怎么實現的?我們通過閱讀源碼回答這個問題。


          3.1 閱讀準備

          (1) 源碼地址

          目前最新版本2.0.0-alpha2-SNAPSHOT

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

          (2) 項目結構

          我們從項目結構可以看出一些信息:門面是api模塊,具體實現包括jdk14、log4j12、simple模塊,需要注意logback是同一個作者的另一個項目不在本項目。

          (3) 閱讀入口

          package org.slf4j;

          public class NoBindingTest {
              public void testLogger() {
                  Logger logger = LoggerFactory.getLogger(NoBindingTest.class);
                  logger.debug("hello" + diff);
                  assertTrue(logger instanceof NOPLogger);
              }
          }

          3.2 源碼分析

          LoggerFactory.getLogger

          public final class LoggerFactory {
              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;
              }
          }

          getLogger(clazz.getName())

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

          getILoggerFactory()

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

          getProvider()

          public final class LoggerFactory {
              static SLF4JServiceProvider getProvider() {
                  if (INITIALIZATION_STATE == UNINITIALIZED) {
                      synchronized (LoggerFactory.class{
                          if (INITIALIZATION_STATE == UNINITIALIZED) {
                              INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                              performInitialization();
                          }
                      }
                  }
                  switch (INITIALIZATION_STATE) {
                  case SUCCESSFUL_INITIALIZATION:
                      return PROVIDER;
                  case NOP_FALLBACK_INITIALIZATION:
                      return NOP_FALLBACK_FACTORY;
                  case FAILED_INITIALIZATION:
                      throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
                  case ONGOING_INITIALIZATION:
                      return SUBST_PROVIDER;
                  }
                  throw new IllegalStateException("Unreachable code");
              }
          }

          performInitialization()

          public final class LoggerFactory {
              private final static void performInitialization() {
                  bind();
                  if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
                      versionSanityCheck();
                  }
              }
          }

          bind()

          public final class LoggerFactory {
              private final static void bind() {
                  try {
                      // 核心代碼
                      List<SLF4JServiceProvider> providersList = findServiceProviders();
                      reportMultipleBindingAmbiguity(providersList);
                      if (providersList != null && !providersList.isEmpty()) {
                       PROVIDER = providersList.get(0);
                       PROVIDER.initialize();
                       INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                          reportActualBinding(providersList);
                      }
                      // 省略代碼
                  } catch (Exception e) {
                      failedBinding(e);
                      throw new IllegalStateException("Unexpected initialization failure", e);
                  }
              }
          }

          findServiceProviders()

          這是加載具體日志實現的核心方法,使用SPI機制加載所有SLF4JServiceProvider實現類:

          public final class LoggerFactory {
              private static List<SLF4JServiceProvider> findServiceProviders() {
                  ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
                  List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
                  for (SLF4JServiceProvider provider : serviceLoader) {
                      providerList.add(provider);
                  }
                  return providerList;
              }
          }

          SPI(Service Provider Interface)是一種服務發(fā)現機制,本質是將接口實現類的全限定名配置在文件中,并由服務加載器讀取配置文件加載實現類,這樣可以在運行時動態(tài)為接口替換實現類,通過SPI機制可以為程序提供拓展功能。本文以log4j為例說明使用SPI功能的三個步驟:

          (a) 實現接口

          public class Log4j12ServiceProvider implements SLF4JServiceProvider

          (b) 配置文件

          文件位置:src/main/resources/META-INF/services/
          文件名稱:org.slf4j.spi.SLF4JServiceProvider
          文件內容:org.slf4j.log4j12.Log4j12ServiceProvider

          (c) 服務加載

          public final class LoggerFactory {
              private static List<SLF4JServiceProvider> findServiceProviders() {
                  ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
                  List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
                  for (SLF4JServiceProvider provider : serviceLoader) {
                      providerList.add(provider);
                  }
                  return providerList;
              }
          }

          只要各種日志實現框架按照SPI約定進行代碼編寫和配置文件聲明,即可以被LoggerFactory加載,slf4j會獲取第一個作為實現。

          public final class LoggerFactory {
              private final static void bind() {
                  try {
                      // 使用SPI機制加載具體日志實現
                      List<SLF4JServiceProvider> providersList = findServiceProviders();
                      reportMultipleBindingAmbiguity(providersList);
                      if (providersList != null && !providersList.isEmpty()) {
                          // 獲取第一個實現
                          PROVIDER = providersList.get(0);
                          PROVIDER.initialize();
                          INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                          reportActualBinding(providersList);
                      }
                      // 省略代碼
                  } catch (Exception e) {
                      failedBinding(e);
                      throw new IllegalStateException("Unexpected initialization failure", e);
                  }
              }
          }

          分析到這里我們的問題應該可以得到解答:假設我們項目只引入了slf4j和log4j,相當于只有l(wèi)og4j這一個具體實現,那么本項目就會使用log4j框架。如果將log4j依賴換為logback,那么項目在不改動代碼的情況下會使用logback框架。


          4 文章總結

          本文我們從阿里開發(fā)手冊日志規(guī)約出發(fā),首先分析了如何使用不同的日志框架,然后我們從問題出發(fā)(不修改代碼即可替換具體日志框架)進行slf4j源碼閱讀,從源碼中我們知道實現核心是SPI機制,這個機制可以動態(tài)加載具體日志實現。關于SPI源碼分析請參看筆者文章JDK SPI機制,希望本文對大家有所幫助。



          JAVA前線 


          歡迎大家關注公眾號「JAVA前線」查看更多精彩分享,主要包括源碼分析、實際應用、架構思維、職場分享、產品思考等等,同時也非常歡迎大家加我微信「java_front」一起交流學習


          瀏覽 48
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日日骚av一区二区三区 | 国产精品嫩草久久久久yw193 | 一级片视频在线播放 | 来个欧美操逼的 | 久久午夜鲁丝 |