SOFABoot源码解析之日志空间隔离

一、简述

        SOFABoot提供了日志空间隔离能力。

        SOFABoot中间件能自动发现SOFABoot应用的日志实现依赖,并独立打印日志,解耦SOFABoot中间件日志和SOFABoot应用日志。

        SOFABoot通过其sofa-common-tools包提供日志空间隔离功能。

        首先看一下sofa-common-tools包中与日志空间隔离功能相关类的类图:

        通过上述类图可以看出,其抽象日志空间工厂类AbstractLoggerSpaceFactory实现了org.slf4j.ILoggerFactory接口,所以,SOFABoot基于SLF4J(Simple logging Facade forJava)日志组件实现日志空间隔离功能。对于SLF4J,它不同于其他日志类库,不是一个真正的日志实现,而是一个抽象层,它允许你在后台使用任意一个日志类库。相信大多数Java开发人员都使用过,在此不详述。

        SOFABoot日志空间隔离功能实现的基本原理如下:

        SOFABoot通过各种日志空间工厂Builder,根据SOFABoot中间件依赖的日志类库(SOFA支持的日志类库:logback、log4j2、log4j、commons-logging),为每个SOFABoot中间件创建各自的日志空间工厂类(抽象类AbstractLoggerSpaceFactory的匿名实现类)。在每个日志空间工厂类中,每个SOFABoot中间件及SOFABoot应用拥有其独有的日志空间(logback和log4j2使用ch.qos.logback.classic.LoggerContext类,log4j和commons-logging使用org.apache.log4j.spi.LoggerRepository实现类),并使用日志空间管理该中间件使用的所有logger,从而实现各SOFABoot中间件之间,以及其与SOFABoot应用之间的日志空间隔离,彼此独立配置,独立管理,互不影响。

        总结一下,主要包括两个方面的内容:

        1.  根据classpath路径上所包含的日志类库Jar包,自动选择所使用的日志类库;

        2.  根据所选的日志类库,为SOFABoot 应用和中间件创建独立的日志空间,管理各自的logger对象; 

二、使用方式

        (一)编写日志配置文件

        针对每种日志类库,SOFABoot中间件需要提供相应的日志配置文件

        分别编写logback、log4j2、log4j格式的日志配置文件log-conf.xml,然后按照日志类库的别名,分别存储在项目资源主目录src/main/resources下不同子目录中。

        子目录创建规则如下:

        子目录格式:中间件日志空间名称转换路径/log/不同日志类库的名字。

        其中:

        1.  中间件日志空间名称转换路径为把中间件日志空间名称中的.转换为/。例如:com.alipay.sofa.infra转换为路径com/alipay/sofa/infra。

        2.  不同日志类库的名字分别为logback、log4j2、log4j。

        在Infra模块中,日志配置文件的存储路径依次为:

        /com/alipay/sofa/infra/log/logback/log-conf.xml;

        /com/alipay/sofa/infra/log/log4j2/log-conf.xml;

        /com/alipay/sofa/infra/log/log4j /log-conf.xml;

        (二)编写一个Logger工厂类

        此类主要功能是从spaceName的空间里寻找指定名字的logger对象,这些 logger是从该spaceName下的日志实现配置中解析而来。

        例如:在SOFABoot中,Infra模块com.alipay.sofa.infra.log.InfraHealthCheckLoggerFactory,Healthcheck模块com.alipay.sofa.healthcheck.log.SofaBootHealthCheckLoggerFactory,Runtime模块的com.alipay.sofa.runtime.spi.log.SofaRuntimeLoggerFactory。

        以InfraHealthCheckLoggerFactory类为例,详细说明基本实现方式(如果没有其它特殊需求,除了红色代码需要根据具体SOFABoot中间件的日志空间名字修改外,其它保持不变):

1.  public class InfraHealthCheckLoggerFactory{
2.   
3.      public static final StringINFRASTRUCTURE_LOG_SPACE = "com.alipay.sofa.infra";
4.   
5.      /***
6.       * 获取日志对象
7.       *
8.       * @param clazz 日志的名字
9.       * @return 日志实现
10.      */
11.     public static org.slf4j.LoggergetLogger(Class<?> clazz) {
12.         if (clazz == null) {
13.             return null;
14.         }
15.         returngetLogger(clazz.getCanonicalName());
16.     }
17.  
18.     /**
19.      * 获取日志对象
20.      *
21.      * @param name 日志的名字
22.      * @return 日志实现
23.      */
24.     public static org.slf4j.LoggergetLogger(String name) {
25.         if (name == null || name.isEmpty()) {
26.             return null;
27.         }
28.         returnLoggerSpaceManager.getLoggerBySpace(name, INFRASTRUCTURE_LOG_SPACE);
29.     }
30. }

        设置Infra模块的日志空间名称。一般为含模块名称的包路径,此处为:com.alipay.sofa.infra。

        调用LoggerSpaceManager类getLoggerBySpace方法,从spaceName的空间里寻找指定名字的logger对象。

        (三)使用logger记录日志信息

        当我们需要在SOFABoot中间件的某个类中记录日志时,直接使用该中间件的Logger工厂类获取其日志空间中指定logger名称的logger对象,然后像使用SLF4J的logger一样,记录日志信息。

        例如,在Infra模块SofaBootVersionEndpoint类中,代码如下:

1.  private static final Logger logger =InfraHealthCheckLoggerFactory
2.                                             .getLogger(SofaBootVersionEndpoint.class);

三、源码解析

        以SOFABoot研发框架的基础模块infra-sofa-boot-starter为例,详细描述日志空间隔离功能的实现原理。

        在此提前说明,源码分析主要分析主流程,以及本人认为比较重要的一些内容,对于其它部分,大家可以基于本文档,自行研读。

        还是以SOFABoot自带的sofa-boot-sample为例,它依赖于infra-sofa-boot-starter模块。

        与SpringBoot最主要的特性自动配置AutoConfig一样,SOFABoot也是通过提供infra-sofa-boot-starter为我们自动配置。

        我们只需要在项目sofa-boot-sample的pom.xml文件中,添加infra-sofa-boot-starter到项目依赖中即可。

1.          <dependency>
2.              <groupId>com.alipay.sofa</groupId>
3.              <artifactId>infra-sofa-boot-starter</artifactId>
4.          </dependency>

        而infra-sofa-boot-starter依赖于sofa-common-tools包。

1.          <dependency>
2.              <groupId>com.alipay.sofa.common</groupId>
3.              <artifactId>sofa-common-tools</artifactId>
4.          </dependency>

        通过SpringApplication类run方法,启动项目

1.  @SpringBootApplication
2.  public class DemoApplication {
3.      public static void main(String[] args) {
4.          SpringApplication.run(DemoApplication.class,args);
5.      }
6.  }

        本文主要详述日志空间隔离相关的内容,SOFABoot的启动原理可以参考《启动原理》,在此不在详述。

        由于SOFABoot 基于SpringBoot进行构建,所以完全兼容SpringBoot。作为一个遵循SpringBoot规范的Jar包,也是通过自动配置(spring.factories)和SOFABootInfrastructureSpringContextInitializer(实现了ApplicationContextInitializer接口)完成infra的初始化工作。

        infra-sofa-boot-starter模块的spring.factories文件内容如下:

1.  # Initializers
2.  org.springframework.context.ApplicationContextInitializer=\
3.  com.alipay.sofa.infra.initializer.SOFABootInfrastructureSpringContextInitializer
4.   
5.  # Auto Configure
6.  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
7.  com.alipay.sofa.infra.autoconfigure.SofaBootInfraAutoConfiguration

        在SpringApplication实例初始化的时候,使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer接口实现类,此时会加载SOFABootInfrastructureSpringContextInitializer

        SpringApplication实例初始化完成后,开始执行run方法的逻辑。

        在调用prepareContext方法准备Spring应用上下文过程中,依次调用刚才加载的ApplicationContextInitializer接口的实现类的initialize方法,完成Spring应用上下文的初始化工作。此时,会调用SOFABootInfrastructureSpringContextInitializer的initialize方法,完成Infra相关的初始化工作:

1.  public voidinitialize(ConfigurableApplicationContext applicationContext) {
2.          //init log
3.          this.logInit(applicationContext.getEnvironment());
4.   
5.          InfraHealthCheckLoggerFactory.getLogger(
6.              SOFABootInfrastructureSpringContextInitializer.class).info(
7.              "SOFABoot InfrastructureStarting!");
8.      }

        首先,调用logInit方法,根据application.properties文件中以logging为前缀的日志相关属性设置系统属性,如logging.path、logging.level等。

        然后,以SOFABootInfrastructureSpringContextInitializer类的class对象为参数,调用InfraHealthCheckLoggerFactory类getLogger方法:

1.      public static org.slf4j.LoggergetLogger(Class<?> clazz) {
2.          if (clazz == null) {
3.              return null;
4.          }
5.          returngetLogger(clazz.getCanonicalName());
6.      }

        以全限定类名作为参数,调用getLogger方法从Infra模块的日志空间中获取名为com.alipay.sofa.infra.initializer.SOFABootInfrastructureSpringContextInitializer的Logger实例:

1.      public static org.slf4j.Logger getLogger(Stringname) {
2.          //"com/alipay/boot/healthcheck/log"中获取 health check 的日志配置并寻找对应logger对象,log 为默认添加的后缀
3.          if (name == null || name.isEmpty()) {
4.              return null;
5.          }
6.          return LoggerSpaceManager.getLoggerBySpace(name, INFRASTRUCTURE_LOG_SPACE);
7.      }
        调用LoggerSpaceManager类的getLoggerBySpace方法,获取指定日志空间中指定名字的Logger实例。此处,logger名称为com.alipay.sofa.infra.initializer.SOFABootInfrastructureSpringContextInitializer,日志空间名称为com.alipay.sofa.infra
1.      public static Logger getLoggerBySpace(Stringname, String spaceName) {
2.          return getLoggerBySpace(name, newSpaceId(spaceName),
3.              Collections.<String, String>emptyMap());
4.      }

        增加日志属性集合参数Map,调用getLoggerBySpace方法: 

1.      public static LoggergetLoggerBySpace(String name, SpaceId spaceId,
2.                                            Map<String,String> properties) {
3.          //init first
4.          init(spaceId, properties);
5.          returnMultiAppLoggerSpaceManager.getLoggerBySpace(name, spaceId);
6.      }

        初始化MultiAppLoggerSpaceManager实例,主要是创建日志空间SpaceInfo实例,设置其properties属性,最后缓存spacesMap,以便后续使用。

        调用MultiAppLoggerSpaceManager类getLoggerBySpace方法:

1.      public static LoggergetLoggerBySpace(String name, SpaceId spaceId) {
2.          return getLoggerBySpace(name, spaceId,MultiAppLoggerSpaceManager.class.getClassLoader());
3.      }

        增加日志空间类加载器参数,调用getLoggerBySpace方法:

1.      public static LoggergetLoggerBySpace(String name, SpaceId spaceId, ClassLoader spaceClassloader) {
2.          AbstractLoggerSpaceFactoryabstractLoggerSpaceFactory = getILoggerFactoryBySpaceName(
3.              spaceId, spaceClassloader);
4.          return abstractLoggerSpaceFactory.getLogger(name);
5.      }

        (一)创建日志空间工厂类

        调用getILoggerFactoryBySpaceName方法,根据中间件使用的日志类库创建日志空间工厂类:

1.      private static AbstractLoggerSpaceFactorygetILoggerFactoryBySpaceName(SpaceId spaceId,
2.                                                                             ClassLoaderspaceClassloader) {
3.          //该判断,线程安全不是必须的(最多产生个TemporaryILoggerFactory实例,而且[未初始化]应该基本只发生在启动场景,此时也就基本就是单一线程),以便减少同步开销
4.          if (!isSpaceInitialized(spaceId)) {
5.              //temporary factory, and cachesupport
6.              returnTemporaryILoggerFactoryPool.get(spaceId, spaceClassloader);
7.          }
8.   
9.          AbstractLoggerSpaceFactoryiLoggerFactory = NOP_LOGGER_FACTORY;
10.         if (!isSpaceILoggerFactoryExisted(spaceId)){
11.             synchronized(MultiAppLoggerSpaceManager.class) {
12.                 if(!isSpaceILoggerFactoryExisted(spaceId)) {
13.                     iLoggerFactory = createILoggerFactory(spaceId,spaceClassloader);
14.                     spacesMap.get(spaceId).setAbstractLoggerSpaceFactory(iLoggerFactory);
15.                 }
16.             }
17.         } else {
18.             iLoggerFactory =spacesMap.get(spaceId).getAbstractLoggerSpaceFactory();
19.         }
20.         return iLoggerFactory;
21.     }

        主要看一下createILoggerFactory方法:

1.  private static AbstractLoggerSpaceFactorycreateILoggerFactory(SpaceId spaceId,
2.                                                                     ClassLoaderspaceClassloader) {
3.          if(System.getProperty(SOFA_MIDDLEWARE_LOG_DISABLE_PROP_KEY) != null
4.              &&Boolean.TRUE.toString().equalsIgnoreCase(
5.                  System.getProperty(SOFA_MIDDLEWARE_LOG_DISABLE_PROP_KEY))){
6.              ……
7.              return NOP_LOGGER_FACTORY;
8.          }
9.          // set log props
10.         LogEnvUtils.processGlobalSystemLogProperties();
11.  
12.         // do create
13.         try {
14.             if(LogEnvUtils.isLogbackUsable(spaceClassloader)) {
15.                 String isLogbackDisable =System
16.                     .getProperty(LOGBACK_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
17.                 if (isLogbackDisable != null
18.                     &&Boolean.TRUE.toString().equalsIgnoreCase(isLogbackDisable)) {
19.                     ……               
20.                 } else {
21.                     ……                   
22. LoggerSpaceFactoryBuilderloggerSpaceFactory4LogbackBuilder = new LoggerSpaceFactory4LogbackBuilder(
23.                         spacesMap.get(spaceId));
24.                     returnloggerSpaceFactory4LogbackBuilder.build(spaceId.getSpaceName(),
25.                         spaceClassloader);
26.                 }
27.             }
28.  
29.             if(LogEnvUtils.isLog4j2Usable(spaceClassloader)) {
30.                 String isLog4j2Disable = System.getProperty(LOG4J2_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
31.                 if (isLog4j2Disable != null
32.                     &&Boolean.TRUE.toString().equalsIgnoreCase(isLog4j2Disable)) {
33.                      ……
34.                 } else {
35.                     ……
36.                     LoggerSpaceFactoryBuilderloggerSpaceFactory4Log4j2Builder = new LoggerSpaceFactory4Log4j2Builder(
37.                         spacesMap.get(spaceId));
38.                     return loggerSpaceFactory4Log4j2Builder.build(spaceId.getSpaceName(),
39.                         spaceClassloader);
40.                 }
41.             }
42.  
43.             if(LogEnvUtils.isLog4jUsable(spaceClassloader)) {
44.                 String isLog4jDisable =System.getProperty(LOG4J_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
45.                 if (isLog4jDisable != null
46.                     &&Boolean.TRUE.toString().equalsIgnoreCase(isLog4jDisable)) {
47.                      ……
48.                 } else {
49.                     ……
50.                     LoggerSpaceFactoryBuilderloggerSpaceFactory4Log4jBuilder = newLoggerSpaceFactory4Log4jBuilder(spacesMap.get(spaceId));
51.                     returnloggerSpaceFactory4Log4jBuilder.build(spaceId.getSpaceName(),
52.                         spaceClassloader);
53.                 }
54.             }
55.  
56.             if(LogEnvUtils.isCommonsLoggingUsable(spaceClassloader)) {
57.                 //此种情形:commons-logging 桥接到 log4j 实现,默认日志实现仍然是 log4j
58.                 String isLog4jDisable = System
59.                     .getProperty(LOG4J_COMMONS_LOGGING_MIDDLEWARE_LOG_DISABLE_PROP_KEY);
60.                 if (isLog4jDisable != null
61.                     &&Boolean.TRUE.toString().equalsIgnoreCase(isLog4jDisable)) {
62.                     ……
63.                 } else {
64.                     ……
65.                     LoggerSpaceFactoryBuilderloggerSpaceFactory4Log4jBuilder = new LoggerSpaceFactory4CommonsLoggingBuilder(
66.                         spacesMap.get(spaceId));
67.  
68.                     return loggerSpaceFactory4Log4jBuilder.build(spaceId.getSpaceName(),
69.                         spaceClassloader);
70.                 }
71.             }
72. ……
73.         } catch (Throwable e) {
74. ……
75.         }
76.         return NOP_LOGGER_FACTORY;
77.     }

        该方法判断sofa.middleware.log.disable属性是否为true。如果为true,则表示禁止中间件使用自己的日志空间,使用默认的NOP日志空间,NOP使用名为com.alipay.sofa.common.log的日志记录器logger。如果为false,创建新的日志空间工厂。

        此处sofa.middleware.log.disable为false,所以开始创建新的日志空间工厂。

        首先,在系统变量中设置全局日志相关的配置变量,如:file.encoding、logging.path、loggingRoot等。

        然后,判断中间件或应用所使用的日志类库,创建相应的日志空间工厂。具体的判断逻辑为:以日志空间类SpaceInfo实例所使用的类加载器对应的classpath中是否存在指定类型的日志类库为准。

        此处需要注意的是,如果在classpath路径上同时存在了logback、log4j、log4j2、commons-logging类库,则这些类库的优先级为logback、log4j2、log4j、commons-logging,即优先使用logback,然后log4j2,依次类推。

通过设置某个日志类库的禁用标志(上述类库的禁用标志分别为logback.middleware.log.disablelog4j2.middleware.log.disable、log4j2.middleware.log.disable、log4j.commons.middleware.log.disable)来禁止使用某个日志类库。默认都为false,表示都处于可以使用状态。如果都为true,表示都处于禁止使用状态,则使用默认的日志空间NOP,同上述sofa.middleware.log.disable为true时的情况。

        以logback类库为例,使用日志环境工具类LogEnvUtils的isLog4jUsable方法,判断是否使用logback类库:

1.      public static booleanisLogbackUsable(ClassLoader spaceClassloader) {
2.          AssertUtil.notNull(spaceClassloader);
3.          try {
4.              return spaceClassloader.loadClass("ch.qos.logback.classic.LoggerContext")!= null;
5.          } catch (ClassNotFoundException e) {
6.              return false;
7.          }
8.      }

        由于Infra模块classpath路径上存在logback类库,所以此处判断结果为true。

        接下来,判断logback.middleware.log.disable属性是否为true。如果为true,则表示禁止中间件使用logback日志类库,则继续判断是否使用log4j2日志类库,依次类推。此处为false,则使用logback日志类库。

        为名称为com.alipay.sofa.infra的日志空间创建类型为logback的日志空间Builder,即LoggerSpaceFactory4LogbackBuilder实例。

        在此需要注意的是,不同类型的日志类库,对应的Builder不同,具体如下:

        1. logback:LoggerSpaceFactory4LogbackBuilder

        2. log4j2:LoggerSpaceFactory4Log4j2Builder;

        3. log4j:LoggerSpaceFactory4Log4jBuilder;

        4. commons-logging:LoggerSpaceFactory4CommonsLoggingBuilder;

        虽然为不同类型的日志类库创建日志空间工厂类的具体方法不同,但大同小异。在此以LoggerSpaceFactory4LogbackBuilder类为例,详细说明logback日志空间工厂类的创建方法。对于其它类型的Builder,就不同详述了。

        调用LoggerSpaceFactory4LogbackBuilder类build方法,为名字为com.alipay.sofa.infra日志空间构建日志空间工厂类:

1.      public AbstractLoggerSpaceFactorybuild(String spaceName, ClassLoader spaceClassloader) {
2.          ……
3.          //load config file
4.          URL configFileUrl = getSpaceLogConfigFileURL(spaceClassloader,spaceName);
5.   
6.          // set default logging.level
7.          specifySpaceLogConfigProperites(spaceName);
8.   
9.          return doBuild(spaceName, spaceClassloader,configFileUrl);
10.  
11.     }

        首先,加载中间件的日志配置文件,并返回配置文件的URL。

        此处采用项目依赖方式,所以logback日志配置文件的URL为:

1.  file:/D:/SOFA_Workspace/sofa-boot-master/infra-sofa-boot-starter/target/classes/com/alipay/sofa/infra/log/logback/log-conf.xml

        以logback为例,其配置文件参考内容为:

1.  <configuration>
2.      <!-- logback appenders -->
3.   
4.      <!-- 按照每天生成日志文件 -->
5.      <appendername="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
6.          <append>true</append>
7.          <!-- 过滤器,只记录 error 级别的日志 -->
8.          <filterclass="ch.qos.logback.classic.filter.ThresholdFilter">
9.              <level>error</level>
10.         </filter>
11.         <!-- 日志名称 -->
12.         <file>${logging.path}/infra/common-error.log</file>
13.         <!-- 每天生成一个日志文件,保存30天的日志文件 -->
14.         <rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
15.             <!--日志文件输出的文件名:按天回滚 daily -->
16.             <FileNamePattern>${logging.path}/infra/common-error.log.%d{yyyy-MM-dd}</FileNamePattern>
17.             <!--日志文件保留天数-->
18.             <MaxHistory>30</MaxHistory>
19.         </rollingPolicy>
20.         <encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
21.             <pattern>%d %-5p %-32t -%m%n</pattern>
22.             <!-- 编码 -->
23.             <charset>${file.encoding}</charset>
24.         </encoder>
25.     </appender>
26.  
27.     <appender name="ROOT-APPENDER"class="ch.qos.logback.core.rolling.RollingFileAppender">
28.         <append>true</append>
29.         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
30.             <level>${logging.level.com.alipay.sofa.infra}</level>
31.         </filter>
32.         <file>${logging.path}/infra/common-default.log</file>
33.         <!-- 每天生成一个日志文件,保存30天的日志文件 -->
34.         <rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
35.             <!--日志文件输出的文件名:按天回滚 daily -->
36.             <FileNamePattern>${logging.path}/infra/common-default.log.%d{yyyy-MM-dd}</FileNamePattern>
37.             <!--日志文件保留天数-->
38.             <MaxHistory>30</MaxHistory>
39.         </rollingPolicy>
40.         <encoderclass="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
41.             <pattern>%d %-5p %-32t -%m%n</pattern>
42.             <!-- 编码 -->
43.             <charset>${file.encoding}</charset>
44.         </encoder>
45.     </appender>
46.  
47.     <rootlevel="${logging.level.com.alipay.sofa.infra}">
48.         <appender-refref="ROOT-APPENDER"/>
49.         <appender-refref="ERROR-APPENDER"/>
50.     </root>
51. </configuration>

        上述内容为logback标准配置,在此不详述。

        其次,调用specifySpaceLogConfigProperites方法,整理SpaceInfo的properties属性集合,规则如下:

        1.   如果System.properties 与 SpaceInfo.properites 都含有某个配置,那么以System.properties为准,SpaceInfo.properites中重复定义会被抛弃;

        2.   如果系统属性中无logging.path.com.alipay.sofa.infra属性值,但存在logging.path属性值,并且SpaceInfo属性中无logging.path.com.alipay.sofa.infra属性值,则在SpaceInfo.properites中设置日志路径为系统属性中logging.path的值。此处为logging.path.com.alipay.sofa.infra=./logs;

        3.   如果系统属性中无logging.level.com.alipay.sofa.infra属性值,并且SpaceInfo属性中也无logging.level.com.alipay.sofa.infra属性值,则在SpaceInfo.properites中设置logging.level.com.alipay.sofa.infra的日志级别为默认的日志级别INFO。

        最后,调用LoggerSpaceFactory4LogbackBuilder类doBuild方法: 

1.  public AbstractLoggerSpaceFactorydoBuild(String spaceName, ClassLoader spaceClassloader,
2.                                                URLurl) {
3.          final LoggerContext loggerContext = newLoggerContext();
4.   
5.          for (Map.Entry entry :getProperties().entrySet()) {
6.              //from Map<String,String>
7.              loggerContext.putProperty((String)entry.getKey(), (String) entry.getValue());
8.          }
9.   
10.         if (url != null) {
11.             try {
12.                 newContextInitializer(loggerContext).configureByResource(url);
13.             } catch (JoranException e) {
14.                 throw newIllegalStateException("Logback loggerSpaceFactory build error", e);
15.             }
16.         }
17.         return newAbstractLoggerSpaceFactory(getLoggingToolName()) {
18.  
19.             @Override
20.             public Logger setLevel(String loggerName,AdapterLevel adapterLevel) throws Exception {
21.                 ch.qos.logback.classic.LoggerlogbackLogger = (ch.qos.logback.classic.Logger) this
22.                     .getLogger(loggerName);
23.                 ch.qos.logback.classic.LevellogbackLevel = this.toLogbackLevel(adapterLevel);
24.                 logbackLogger.setLevel(logbackLevel);
25.                 return logbackLogger;
26.             }
27.  
28.             @Override
29.             public Logger getLogger(String name) {
30.                 returnloggerContext.getLogger(name);
31.             }
32.  
33.             privatech.qos.logback.classic.Level toLogbackLevel(AdapterLevel adapterLevel) {
34.                 if (adapterLevel == null) {
35.                     throw newIllegalStateException("AdapterLevel is NULL when adapter tologback.");
36.                 }
37.                 switch (adapterLevel) {
38.                     case TRACE:
39.                         returnch.qos.logback.classic.Level.TRACE;
40.                     case DEBUG:
41.                         returnch.qos.logback.classic.Level.DEBUG;
42.                     case INFO:
43.                         returnch.qos.logback.classic.Level.INFO;
44.                     case WARN:
45.                         returnch.qos.logback.classic.Level.WARN;
46.                     case ERROR:
47.                         returnch.qos.logback.classic.Level.ERROR;
48.                     default:
49.                         throw newIllegalStateException(adapterLevel
50.                                                         +" is unknown when adapter to logback.");
51.                 }
52.             }
53.         };
54.     }

        此方法开始为名称为com.alipay.sofa.infra的日志空间构建日志空间工厂类,主要步骤如下:

        1.   为SOFABoot中间件创建LoggerContext实例,该实例为SOFABoot中间件的日志空间,管理该中间件所有logger;

        2.   根据SpaceInfo的properties属性集合,设置LoggerContext实例的属性集合;

        3.   如果日志配置文件URL不为null,则加载日志配置文件,并初始化LoggerContext实例;

        4.   创建抽象类AbstractLoggerSpaceFactory的匿名实现类,类型为logback。该匿名类重写了AbstractLoggerSpaceFactory的setLevel和getLogger方法。在这两个方法中,使用logback相关的API完成设置日志级别和获取日志记录器logger的操作。

        (二)获取日志记录器logger

        再回看一下MultiAppLoggerSpaceManager类getLoggerBySpace方法:

1.      public static LoggergetLoggerBySpace(String name, SpaceId spaceId, ClassLoader spaceClassloader) {
2.          AbstractLoggerSpaceFactoryabstractLoggerSpaceFactory = getILoggerFactoryBySpaceName(
3.              spaceId, spaceClassloader);
4.          return abstractLoggerSpaceFactory.getLogger(name);
5.      }

        获取到AbstractLoggerSpaceFactory的实现类(此处为基于logback的实现)以后,调用其getLogger方法,获取名为com.alipay.sofa.infra.initializer.SOFABootInfrastructureSpringContextInitializer的日志记录器。

        最后,在看看SOFABootInfrastructureSpringContextInitializer的initialize方法:

1.  public voidinitialize(ConfigurableApplicationContext applicationContext) {
2.          //init log
3.          this.logInit(applicationContext.getEnvironment());
4.   
5.          InfraHealthCheckLoggerFactory.getLogger(
6.              SOFABootInfrastructureSpringContextInitializer.class).info(
7.              "SOFABoot InfrastructureStarting!");
8.      }

        调用logger的info方法,记录Infra模块启动开始日志,内容为SOFABoot Infrastructure Starting!。

        至此,Infra模块使用其自有的logback类型日志空间记录日志的功能解析完毕。

猜你喜欢

转载自blog.csdn.net/beyondself_77/article/details/80845288