spring boot 源码解析27-JavaLoggingSystem及LoggingSystem生命周期详解

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

前言

现在我们来说说spring boot 中对于log相关的源码,因为我们现在需要对LoggersEndpoint进行分析,可是LoggersEndpoint中使用到了log相关的类,因此我们需要先对是如何实现Log的进行分析,之后再来看看LoggersEndpoint的实现

spring boot 中有关log的代码在org.springframework.boot.logging包下,如图:

20191017100458\_1.png

其中log的类图如下(只画出主要的类):

20191017100458\_2.png

解析

本文先解析JavaLoggingSystem的实现.

LoggingSystem

LoggingSystem–>logging框架的基本抽象

  1. 定义了如下几个字段:

        // 系统属性名,用该属性来配置使用哪个LoggingSystem的实现
        public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
    
        // 如果SYSTEM_PROPERTY对应的属性值为none,则意味着不希望使用LoggingSystem-->此时获得的是NoOpLoggingSystem,什么都不会进行打印
        public static final String NONE = "none";
    
        // 根节点logger的名字,LoggingSystem的实现应该使其作为根节点logger的名字,而不去管底层的实现
        public static final String ROOT_LOGGER_NAME = "ROOT";
    
        // 默认支持的类,key-->logger框架的类,value--> 对应的LoggingSystem 实现
        private static final Map SYSTEMS;
    
        static {
            Map systems = new LinkedHashMap();
            systems.put("ch.qos.logback.core.Appender",
                    "org.springframework.boot.logging.logback.LogbackLoggingSystem");
            systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
                    "org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
            systems.put("java.util.logging.LogManager",
                    "org.springframework.boot.logging.java.JavaLoggingSystem");
            SYSTEMS = Collections.unmodifiableMap(systems);
        }
  2. 定义了1个抽象方法–>重新设置日志系统以减少输出,该方法应该在initialize方法前调用以减少日志的噪声直到日志系统初始化完毕,如下:

        public abstract void beforeInitialize();
  3. 定义了如下几个方法:

    1. initialize–>初始化日志系统,代码如下:

          public void initialize(LoggingInitializationContext initializationContext,
              String configLocation, LogFile logFile) {
          }
    2. cleanUp–>清除资源,代码如下:

          public void cleanUp() {
          }
    3. getShutdownHandler–>返回1个Runnable –> 当jvm退出时来处理日志系统的关闭,默认返回null,表明不需要进行处理.代码如下:

          public Runnable getShutdownHandler() {
              return null;
          }
    4. getSupportedLogLevels–>返回该日志系统支持的日志级别,默认是所有都支持,代码如下:

              public Set getSupportedLogLevels() {
                  return EnumSet.allOf(LogLevel.class);
              }

      LogLevel 定义如下:

          public enum LogLevel {
              TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
          }
    5. setLogLevel–>为给定的logger设置日志级别.代码如下:

          public void setLogLevel(String loggerName, LogLevel level) {
              throw new UnsupportedOperationException("Unable to set log level");
          }
    6. getLoggerConfigurations–>返回当前LoggingSystem中的所有的logger,代码如下:

          public List getLoggerConfigurations() {
                  throw new UnsupportedOperationException("Unable to get logger configurations");
              }
    7. getLoggerConfiguration–>返回给定的logger所对应的LoggerConfiguration,代码如下:

          public LoggerConfiguration getLoggerConfiguration(String loggerName) {
                  throw new UnsupportedOperationException("Unable to get logger configuration");
              }
    8. get–> 获得LoggingSystem.代码如下:

          public static LoggingSystem get(ClassLoader classLoader) {
          // 1. 获得环境变量中key为org.springframework.boot.logging.LoggingSystem所对应的值
          String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
          // 2. 如果配置的值为none,则返回NoOpLoggingSystem,否则,对其进行实例化
          if (StringUtils.hasLength(loggingSystem)) {
              if (NONE.equals(loggingSystem)) {
                  return new NoOpLoggingSystem();
              }
              return get(classLoader, loggingSystem);
          }
          // 3. 否则,遍历SYSTEMS,如果在当前类路径下存在所配置的类,则直接实例化,返回,默认使用的是LogbackLoggingSystem
          for (Map.Entry entry : SYSTEMS.entrySet()) {
              if (ClassUtils.isPresent(entry.getKey(), classLoader)) {
                  return get(classLoader, entry.getValue());
              }
          }
          // 4. 如果没有获取到,则抛出IllegalStateException
          throw new IllegalStateException("No suitable logging system located");
          }
      1. 获得环境变量中key为org.springframework.boot.logging.LoggingSystem所对应的值
      2. 如果配置的值为none,则返回NoOpLoggingSystem,否则,对其进行实例化
      3. 否则,遍历SYSTEMS,如果在当前类路径下存在所配置的类,则直接实例化,返回,默认使用的是LogbackLoggingSystem
      4. 如果没有获取到,则抛出IllegalStateException

      其中, NoOpLoggingSystem 代码如下:

          static class NoOpLoggingSystem extends LoggingSystem {
      
          @Override
          public void beforeInitialize() {
      
          }
      
          @Override
          public void setLogLevel(String loggerName, LogLevel level) {
      
          }
      
          @Override
          public List getLoggerConfigurations() {
              return Collections.emptyList();
          }
      
          @Override
          public LoggerConfiguration getLoggerConfiguration(String loggerName) {
              return null;
          }
          }

AbstractLoggingSystem

AbstractLoggingSystem–>实现了initialize方法

  1. 字段如下:

        protected static final Comparator CONFIGURATION_COMPARATOR = new LoggerConfigurationComparator(
                ROOT_LOGGER_NAME);
    
        private final ClassLoader classLoader;

    其中LoggerConfigurationComparator的作用是在进行排序时,将root logger 排在第1位,其他的按照字典顺序排序.代码如下:

        public int compare(LoggerConfiguration o1, LoggerConfiguration o2) {
            if (this.rootLoggerName.equals(o1.getName())) {
                return -1;
            }
            if (this.rootLoggerName.equals(o2.getName())) {
                return 1;
            }
            return o1.getName().compareTo(o2.getName());
        }

    构造器如下:

        public AbstractLoggingSystem(ClassLoader classLoader) {
            this.classLoader = classLoader;
        }
  2. initialize 方法实现如下:

        public void initialize(LoggingInitializationContext initializationContext,
                String configLocation, LogFile logFile) {
            // 1. 如果配置有configLocation,则调用initializeWithSpecificConfig
            if (StringUtils.hasLength(configLocation)) {
                initializeWithSpecificConfig(initializationContext, configLocation, logFile);
                return;
            }
            // 2. 按照log的底层实现的规则进行初始化
            initializeWithConventions(initializationContext, logFile);
        }
    1. 如果配置有configLocation,则调用initializeWithSpecificConfig,根据指定的配置文件进行初始化,代码如下:

          private void initializeWithSpecificConfig(
              LoggingInitializationContext initializationContext, String configLocation,
              LogFile logFile) {
          // 1. 占位符处理
          configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
          // 2. 加载配置文件,子类实现
          loadConfiguration(initializationContext, configLocation, logFile);
          }
      1. 占位符处理
      2. 加载配置文件,子类实现
    2. 按照log的底层实现的规则进行初始化,代码如下:

              private void initializeWithConventions(
              LoggingInitializationContext initializationContext, LogFile logFile) {
          // 1. 获得配置文件的路径
          String config = getSelfInitializationConfig();
          if (config != null && logFile == null) {
              // 2. 如果配置文件存在并且LogFile等于null,则调用reinitialize重新修改一些属性
              // self initialization has occurred, reinitialize in case of property changes
              reinitialize(initializationContext);
              return;
          }
          // 3. 如果config等于null,则
          if (config == null) {
              config = getSpringInitializationConfig();
          }
          // 4. 如果config有值了,则加载配置,然后return
          if (config != null) {
              loadConfiguration(initializationContext, config, logFile);
              return;
          }
          // 5. 加载默认配置
          loadDefaults(initializationContext, logFile);
          }
      1. 获得配置文件的路径,代码如下:

            protected String getSelfInitializationConfig() {
                return findConfig(getStandardConfigLocations());
            }
        1. 调用getStandardConfigLocations获得配置文件的路径,子类实现
        2. 遍历给定的locations,依次通过ClassPathResource进行加载,如果存在,则加上classpath:前缀 返回,否则,返回null.代码如下:

              private String findConfig(String[] locations) {
                  for (String location : locations) {
                      ClassPathResource resource = new ClassPathResource(location,
                              this.classLoader);
                      if (resource.exists()) {
                          return "classpath:" + location;
                      }
                  }
                  return null;
              }
      2. 如果配置文件存在并且LogFile等于null,则调用reinitialize重新修改一些属性,该方法默认是空实现,代码如下:

            protected void reinitialize(LoggingInitializationContext initializationContext) {
                }
      3. 如果config等于null,则在log底层实现支持的配置文件加上-spring 后进行加载,代码如下:

            protected String getSpringInitializationConfig() {
                return findConfig(getSpringConfigLocations());
            }
        1. log底层实现支持的配置文件加上-spring,代码如下:

              protected String[] getSpringConfigLocations() {
                  // 1. 获得标准的配置文件路径,依次遍历之
                  String[] locations = getStandardConfigLocations();
                  for (int i = 0; i < locations.length; i++) {
                      // 2.1 修改原配置为xxx-spring.extension 进行加载
                      String extension = StringUtils.getFilenameExtension(locations[i]);
                      locations[i] = locations[i].substring(0,
                              locations[i].length() - extension.length() - 1) + "-spring."
                              + extension;
                  }
                  return locations;
              }
        2. 遍历给定的locations,依次通过ClassPathResource进行加载,如果存在,则加上classpath:前缀 返回,否则,返回null
      4. 如果config有值了,则加载配置,然后return.该方法默认是空实现
      5. 加载默认配置.该方法默认是空实现
  3. 此外该类还声明了2个方法,供子类使用:

    1. getPackagedConfigFile–>在当前类所在的路径下拼接指定的fileName生成路径.代码如下:

          protected final String getPackagedConfigFile(String fileName) {
              String defaultPath = ClassUtils.getPackageName(getClass());
              defaultPath = defaultPath.replace('.', '/');
              defaultPath = defaultPath + "/" + fileName;
              defaultPath = "classpath:" + defaultPath;
              return defaultPath;
          }
    2. applySystemProperties–>设置系统的环境变量.代码如下:

          protected final void applySystemProperties(Environment environment, LogFile logFile) {
              new LoggingSystemProperties(environment).apply(logFile);
          }

      这里说一下LoggingSystemProperties,该类的作用是设置系统属性的工具类,以供log 配置文件使用.

      1. 字段如下:

            // PID
            static final String PID_KEY = LoggingApplicationListener.PID_KEY;
            // LOG_EXCEPTION_CONVERSION_WORD
            static final String EXCEPTION_CONVERSION_WORD = LoggingApplicationListener.EXCEPTION_CONVERSION_WORD;
            // CONSOLE_LOG_PATTERN
            static final String CONSOLE_LOG_PATTERN = LoggingApplicationListener.CONSOLE_LOG_PATTERN;
            // FILE_LOG_PATTERN
            static final String FILE_LOG_PATTERN = LoggingApplicationListener.FILE_LOG_PATTERN;
            // LOG_LEVEL_PATTERN
            static final String LOG_LEVEL_PATTERN = LoggingApplicationListener.LOG_LEVEL_PATTERN;
            private final Environment environment;
            LoggingSystemProperties(Environment environment) {
                this.environment = environment;
            }
      2. apply 方法实现如下:

            public void apply(LogFile logFile) {
                // 1. 实例化RelaxedPropertyResolver,前缀为logging.
                RelaxedPropertyResolver propertyResolver = RelaxedPropertyResolver
                        .ignoringUnresolvableNestedPlaceholders(this.environment, "logging.");
                // 2. 如果不存在环境变量中不存在LOG_EXCEPTION_CONVERSION_WORD的配置并且environment中配置了logging.exception-conversion-word
                // 则进行设置
                setSystemProperty(propertyResolver, EXCEPTION_CONVERSION_WORD,
                        "exception-conversion-word");
                // 3. 如果不存在环境变量中不存在LPID,则实例化ApplicationPid获取pid值后进行设置
                setSystemProperty(PID_KEY, new ApplicationPid().toString());
                // 4. 如果不存在环境变量中不存在CONSOLE_LOG_PATTERN的配置并且environment中配置了logging.pattern.console
                // 则进行设置
                setSystemProperty(propertyResolver, CONSOLE_LOG_PATTERN, "pattern.console");
                // 5. 如果不存在环境变量中不存在CONSOLE_LOG_PATTERN的配置并且environment中配置了logging.pattern.console
                // 则进行设置 
                setSystemProperty(propertyResolver, FILE_LOG_PATTERN, "pattern.file");
                // 6. 如果不存在环境变量中不存在LOG_LEVEL_PATTERN的配置并且environment中配置了pattern.level
                // 则进行设置 
                setSystemProperty(propertyResolver, LOG_LEVEL_PATTERN, "pattern.level");
                // 7. 如果LogFile不等于null,则将LogFile配置的path,文件路径设置到环境变量中
                if (logFile != null) {
                    logFile.applyToSystemProperties();
                }
            }
  4. 在AbstractLoggingSystem中还声明了1个泛型静态内部类–>LogLevels,作用是维护1个关于log底层支持的日志级别和LogLevel的映射.泛型参数T–>底层的日志级别类型.

    1. 字段,构造器如下:

          // key -->LogLevel,value --> log底层支持的日志级别
          private final Map systemToNative;
      
          // key--> log底层支持的日志级别,value --> LogLevel
          private final Map nativeToSystem;
      
          public LogLevels() {
              this.systemToNative = new HashMap();
              this.nativeToSystem = new HashMap();
          }
    2. 其声明的方法都很简单,如下:

          public void map(LogLevel system, T nativeLevel) {
              if (!this.systemToNative.containsKey(system)) {
                  this.systemToNative.put(system, nativeLevel);
              }
              if (!this.nativeToSystem.containsKey(nativeLevel)) {
                  this.nativeToSystem.put(nativeLevel, system);
              }
          }
      
          public LogLevel convertNativeToSystem(T level) {
              return this.nativeToSystem.get(level);
          }
      
          public T convertSystemToNative(LogLevel level) {
              return this.systemToNative.get(level);
          }
      
          public Set getSupported() {
              return new LinkedHashSet(this.nativeToSystem.values());
          }

JavaLoggingSystem

JavaLoggingSystem 继承自AbstractLoggingSystem,使用的java.util.logging来实现的.

  1. 字段,构造器如下:

        private static final LogLevels LEVELS = new LogLevels();
    
        static {
            LEVELS.map(LogLevel.TRACE, Level.FINEST);
            LEVELS.map(LogLevel.DEBUG, Level.FINE);
            LEVELS.map(LogLevel.INFO, Level.INFO);
            LEVELS.map(LogLevel.WARN, Level.WARNING);
            LEVELS.map(LogLevel.ERROR, Level.SEVERE);
            LEVELS.map(LogLevel.FATAL, Level.SEVERE);
            LEVELS.map(LogLevel.OFF, Level.OFF);
        }
    
        public JavaLoggingSystem(ClassLoader classLoader) {
            super(classLoader);
        }

    至此,我们知道了JavaLoggingSystem中和LogLevel的映射关系如下:

    • TRACE–>FINEST
    • DEBUG–>FINE
    • INFO–>INFO
    • WARN–>WARNING
    • ERROR, FATAL–> SEVERE
    • OFF–>OFF
  2. 方法如下:

    1. getStandardConfigLocations,代码如下:

          protected String[] getStandardConfigLocations() {
              return new String[] { "logging.properties" };
          }
    2. loadDefaults–>如果LogFile不等于null,则读取org/springframework/boot/logging/java/logging-file.properties,否则,读取org/springframework/boot/logging/java/logging.properties.实现如下:

          protected void loadDefaults(LoggingInitializationContext initializationContext,
              LogFile logFile) {
          if (logFile != null) {
              loadConfiguration(getPackagedConfigFile("logging-file.properties"), logFile);
          }
          else {
              loadConfiguration(getPackagedConfigFile("logging.properties"), logFile);
          }
          }

      loadConfiguration–>加载配置文件,实现如下:

              protected void loadConfiguration(String location, LogFile logFile) {
          Assert.notNull(location, "Location must not be null");
          try {
              // 1. 读取配置文件的信息到String 中
              String configuration = FileCopyUtils.copyToString(
                      new InputStreamReader(ResourceUtils.getURL(location).openStream()));
              if (logFile != null) {
                  // 2. 如果logFile不等于null,则将${LOG_FILE} 替换为logFile的路径
                  configuration = configuration.replace("${LOG_FILE}",
                          StringUtils.cleanPath(logFile.toString()));
              }
              // 3. 重新配置
              LogManager.getLogManager().readConfiguration(
                      new ByteArrayInputStream(configuration.getBytes()));
          }
          catch (Exception ex) {
              throw new IllegalStateException(
                      "Could not initialize Java logging from " + location, ex);
          }
          }
      

      因此,其加载配置文件的顺序如下:

      1. 如果classpath:logging.properties 存在,并且LogFile不存在的话,则进行加载
      2. 如果classpath:logging-spring.properties存在,则进行加载
      3. 如果LogFile存在,则加载classpath:org/springframework/boot/logging/java/logging-file.properties
      4. 否则,加载classpath:org/springframework/boot/logging/java/logging.properties

      因此,在默认情况下,会加载classpath:org/springframework/boot/logging/java/logging.properties ,其内容如下所示:

          handlers =java.util.logging.ConsoleHandler
          .level = INFO
      
          java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter
          java.util.logging.ConsoleHandler.level = ALL
      
          org.hibernate.validator.internal.util.Version.level = WARNING
          org.apache.coyote.http11.Http11NioProtocol.level = WARNING
          org.crsh.plugin.level = WARNING
          org.apache.tomcat.util.net.NioSelectorPool.level = WARNING
          org.apache.catalina.startup.DigesterFactory.level = SEVERE
          org.apache.catalina.util.LifecycleBase.level = SEVERE
          org.eclipse.jetty.util.component.AbstractLifeCycle.level = SEVERE
      

      这里配置了Console日志的格式化由SimpleFormatter来实现,这里我们就来看看其实现.

      1. SimpleFormatter 继承了java.util.logging.Formatter,其字段如下:

            private static final String DEFAULT_FORMAT = "[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL] - %8$s %4$s [%7$s] --- %3$s: %5$s%6$s%n";
        
            // 日志格式
            private final String format = getOrUseDefault("LOG_FORMAT", DEFAULT_FORMAT);
        
            // pid 
            private final String pid = getOrUseDefault("PID", "????");
        
            private final Date date = new Date();

        其中getOrUseDefault方法如下:

            private static String getOrUseDefault(String key, String defaultValue) {
                String value = null;
                try {
                    // 1. 获取指定的环境变量值。环境变量是一个取决于系统的外部指定的值。
                    value = System.getenv(key);
                }
                catch (Exception ex) {
                    // ignore
                }
                // 2. 如果value等于null,则赋值为默认值
                if (value == null) {
                    value = defaultValue;
                }
                // 3. 系统属性的字符串值,如果没有带有此键的属性,则返回默认值。
                return System.getProperty(key, value);
            }
        1. 获取指定的环境变量值。环境变量是一个取决于系统的外部指定的值。
        2. 如果value等于null,则赋值为默认值
        3. 系统属性的字符串值,如果没有带有此键的属性,则返回默认值。

        因此,在默认情况下:

        1. 对于format来说,由于在环境变量,系统属性中都不存在LOG_FORMAT的配置,因此默认使用[%1tY−tm-%1tdtH:%1tM:tS.%1tL]−s %4s[s] — %3s:s%6$s%n.
        2. 对于PID来说,由于在系统属性中设置了PID,因此会返回该程序的pid.

        问题来了,PID是何时设置的呢?
        在SpringApplication的run方法中,会调用prepareEnvironment方法,在该方法中会发送ApplicationEnvironmentPreparedEvent事件,LoggingApplicationListener会监听该事件,最终调用initialize方法,代码如下:

            protected void initialize(ConfigurableEnvironment environment,
                    ClassLoader classLoader) {
                new LoggingSystemProperties(environment).apply();
                LogFile logFile = LogFile.get(environment);
                if (logFile != null) {
                    logFile.applyToSystemProperties();
                }
                initializeEarlyLoggingLevel(environment);
                initializeSystem(environment, this.loggingSystem, logFile);
                initializeFinalLoggingLevels(environment, this.loggingSystem);
                registerShutdownHookIfNecessary(environment, this.loggingSystem);
            }

        在该方法中调用了LoggingSystemProperties#apply,最终将PID设置到了系统属性中

    3. beforeInitialize –>将root logger 的日志级别设置为SEVERE,代码如下:

          public void beforeInitialize() {
          super.beforeInitialize();
          Logger.getLogger("").setLevel(Level.SEVERE);
          }   
    4. setLogLevel,代码如下:

          public void setLogLevel(String loggerName, LogLevel level) {
              Assert.notNull(level, "Level must not be null");
              // 1. 如果loggerName等于null或者root 等于loggerName,则将其赋值""
              if (loggerName == null || ROOT_LOGGER_NAME.equals(loggerName)) {
                  loggerName = "";
              }
              // 2.根据loggerName 获得对应的logger
              Logger logger = Logger.getLogger(loggerName);
              if (logger != null) {
                  // 3. 如果获取到,则将其传入的LogLevel 转换为 JavaLogging 对应的log
                  logger.setLevel(LEVELS.convertSystemToNative(level));
              }
          }
    5. getLoggerConfigurations,代码如下:

          public List getLoggerConfigurations() {
              List result = new ArrayList();
              // 1.获得JavaLogging 中的所有logger的名字,遍历之
              Enumeration names = LogManager.getLogManager().getLoggerNames();
              while (names.hasMoreElements()) {
                  // 2. 根据名字获得对应的LoggerConfiguration,添加到result中
                  result.add(getLoggerConfiguration(names.nextElement()));
              }
              // 3. 排序,将root logger 排在第1位,其他的按照字典顺序排序
              Collections.sort(result, CONFIGURATION_COMPARATOR);
              return Collections.unmodifiableList(result);
          }
      1. 获得JavaLogging 中的所有logger的名字,遍历之
      2. 根据名字获得对应的LoggerConfiguration,添加到result中,代码如下:

            public LoggerConfiguration getLoggerConfiguration(String loggerName) {
                // 1 .根据loggerName 获得对应的logger,如果获取不到,返回null
                Logger logger = Logger.getLogger(loggerName);
                if (logger == null) {
                    return null;
                }
                // 2. 获得对应的level,effectiveLevel
                LogLevel level = LEVELS.convertNativeToSystem(logger.getLevel());
                LogLevel effectiveLevel = LEVELS.convertNativeToSystem(getEffectiveLevel(logger));
                // 3.如果logger没有名字,则将其赋值为root
                String name = (StringUtils.hasLength(logger.getName()) ? logger.getName()
                        : ROOT_LOGGER_NAME);
                // 4. 实例化LoggerConfiguration
                return new LoggerConfiguration(name, level, effectiveLevel);
            }
        1. 根据loggerName 获得对应的logger,如果获取不到,返回null
        2. 获得对应的level,effectiveLevel.其中getEffectiveLevel方法如下:

              private Level getEffectiveLevel(Logger root) {
                  Logger logger = root;
                  while (logger.getLevel() == null) {
                      logger = logger.getParent();
                  }
                  return logger.getLevel();
              }

          如果Logger对应的Level 等于null,说明该logger的日志级别是继承其父类的,那么此时就需要不断的先上查找,只至获取到Level

        3. 获得对应的level,effectiveLevel
        4. 实例化LoggerConfiguration
      3. 排序,将root logger 排在第1位,其他的按照字典顺序排序

集成

由于spring boot 默认依赖的是logback,因此需要去除spring-boot-starter-logging的依赖,
如下:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-logging</artifactId>
                    </exclusion>
                </exclusions>
    </dependency>

同时,还需要加入commons-logging依赖,不然就会在SpringApplication 初始化时抛出异常,原因是SpringApplication有如下字段:

    private static final Log logger = LogFactory.getLog(SpringApplication.class);

因此,修改pom文件,加入如下配置:

    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
         <version>1.2</version>
    </dependency>   

由于java.util.logging 是jdk自带的,因此不需要加入其它的jar包了

LoggingSystem生命周期分析

注意,该启动流程分析是基于JavaLoggingSystem来分析的,其他的都一样,只不过在具体的实现上依赖于底层依赖,但是大致流程是不变的

  1. 在SpringApplication#run方法中,会调用SpringApplicationRunListeners#starting方法,如下:

        SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();

    在该方法中,会发送ApplicationEnvironmentPreparedEvent事件.

    LoggingApplicationListener监听了该事件,最终调用了onApplicationStartingEvent进行处理.代码如下:

        private void onApplicationStartingEvent(ApplicationStartingEvent event) {
            this.loggingSystem = LoggingSystem
                    .get(event.getSpringApplication().getClassLoader());
            this.loggingSystem.beforeInitialize();
        }
    

    由于此时我们使用的是java.util.logging,因此此时返回的是JavaLoggingSystem,并执行JavaLoggingSystem#beforeInitialize

  2. 接着执行,接下来在SpringApplication#run,会调用prepareEnvironment,而在该方法中会调用SpringApplicationRunListeners#environmentPrepared方法,发送ApplicationEnvironmentPreparedEvent代码如下:

        listeners.environmentPrepared(environment);

    同样,最终会调用LoggingApplicationListener#onApplicationEnvironmentPreparedEvent方法,代码如下:

        private void onApplicationEnvironmentPreparedEvent(
                ApplicationEnvironmentPreparedEvent event) {
            if (this.loggingSystem == null) {
                this.loggingSystem = LoggingSystem
                        .get(event.getSpringApplication().getClassLoader());
            }
            initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
        }
    1. 如果loggingSystem等于null,则通过调用LoggingSystem#get获得,由于已经在第1步获得了,因此此处不执行
    2. 初始化,代码如下:

          protected void initialize(ConfigurableEnvironment environment,
                  ClassLoader classLoader) {
              // 1. 向系统属性中写入PID,CONSOLE_LOG_PATTERN等配置
              new LoggingSystemProperties(environment).apply();
              // 2. 获取LogFile,如果LogFile不等于null,则向系统属性写入LogFile配置的文件路径
              // 由于默认情况下,没有配置logging.file和logging.path中的任意1个,因此返回null
              LogFile logFile = LogFile.get(environment);
              if (logFile != null) {
                  logFile.applyToSystemProperties();
              }
              // 3. 如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null,则尝试设置springBootLogging
              initializeEarlyLoggingLevel(environment);
              // 4. 初始化loggingSystem
              initializeSystem(environment, this.loggingSystem, logFile);
              // 5. 设置日志级别
              initializeFinalLoggingLevels(environment, this.loggingSystem);
              // 6. 注册ShutdownHook
              registerShutdownHookIfNecessary(environment, this.loggingSystem);
          }
      1. 向系统属性中写入PID,CONSOLE_LOG_PATTERN等配置
      2. 获取LogFile,如果LogFile不等于null,则向系统属性写入LogFile配置的文件路径,由于默认情况下,没有配置logging.file和logging.path中的任意1个,因此返回nul
      3. 如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null,则尝试设置springBootLogging.代码如下:

            private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) {
                // 1. 如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null 
                if (this.parseArgs && this.springBootLogging == null) {
                    // 1.1 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG
                    if (isSet(environment, "debug")) {
                        this.springBootLogging = LogLevel.DEBUG;
                    }
                    // 1.2 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG
                    if (isSet(environment, "trace")) {
                        this.springBootLogging = LogLevel.TRACE;
                    }
                }
            }
        1. 如果parseArgs(默认等于true)等于true并且spring Boot的日志级别等于null

          1. 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG
          2. 如果ConfigurableEnvironment中配置了debug(debug对应的值存在并且不是false),则将spring Boot的日志级别 设置为DEBUG
      4. 初始化loggingSystem.代码如下:

            private void initializeSystem(ConfigurableEnvironment environment,
                    LoggingSystem system, LogFile logFile) {
                // 1. 实例化LoggingInitializationContext
                LoggingInitializationContext initializationContext = new LoggingInitializationContext(
                        environment);
                // 2. 从ConfigurableEnvironment中获取logging.config 所对应的配置
                String logConfig = environment.getProperty(CONFIG_PROPERTY);
                if (ignoreLogConfig(logConfig)) {
                    // 2.1 如果logging.config没有配置或者配置的值是-D开头的,则最终调用AbstractLoggingSystem#initializeWithConventions 进行初始化
                    system.initialize(initializationContext, null, logFile);
                }
                else {
                    try {
                        // 2.2 否则,通过ResourceUtils进行加载判断其文件是否存在,如果不存在,则抛出IllegalStateException
                        // 否则,最终调用AbstractLoggingSystem#initializeWithSpecificConfig
                        ResourceUtils.getURL(logConfig).openStream().close();
                        system.initialize(initializationContext, logConfig, logFile);
                    }
                    catch (Exception ex) {
                        // NOTE: We can't use the logger here to report the problem
                        System.err.println("Logging system failed to initialize "
                                + "using configuration from '" + logConfig + "'");
                        ex.printStackTrace(System.err);
                        throw new IllegalStateException(ex);
                    }
                }
            }
        1. 实例化LoggingInitializationContext
        2. 从ConfigurableEnvironment中获取logging.config 所对应的配置

          1. 如果logging.config没有配置或者配置的值是-D开头的,则最终调用AbstractLoggingSystem#initializeWithConventions 进行初始化
          2. 否则,通过ResourceUtils进行加载判断其文件是否存在,如果不存在,则抛出IllegalStateException,否则,最终调用AbstractLoggingSystem#initializeWithSpecificConfig

        默认情况下,使用2.1,由于此时我们使用的是JavaLoggingSystem,因此会首先加载classpath:logging.properties ,由于不存在,并且此时LogFile等于null,因此最终执行JavaLoggingSystem #loadDefaults,代码如下:

                protected void loadDefaults(LoggingInitializationContext initializationContext,
            LogFile logFile) {
            // 1. 如果LogFile不等于null,则读取org/springframework/boot/logging/java/logging-file.properties,否则,读取org/springframework/boot/logging/java
            // logging.properties
            if (logFile != null) {
            loadConfiguration(getPackagedConfigFile("logging-file.properties"), logFile);
            }
            else {
            loadConfiguration(getPackagedConfigFile("logging.properties"), logFile);
            }
            }

        同样,由于logFile等于null,最终会读取classpath:org/springframework/boot/logging/java/logging.properties,这点我们前面有叙述.

      5. 设置日志级别,代码如下:

            private void initializeFinalLoggingLevels(ConfigurableEnvironment environment,
                    LoggingSystem system) {
                // 1. 如果springBootLogging配置了,则获取到其对应的logger,设置其日志级别和springBootLogging的一样
                if (this.springBootLogging != null) {
                    initializeLogLevel(system, this.springBootLogging);
                }
                // 2.读取environment中logging.level.*=*的配置进行日志的配置,一般都是到这里
                setLogLevels(system, environment);
            }
        1. 如果springBootLogging配置了,则获取到其对应的logger,设置其日志级别和springBootLogging的一样
        2. 读取environment中logging.level.*=*的配置进行日志的配置,一般都是到这里,代码如下:

              protected void setLogLevels(LoggingSystem system, Environment environment) {
                  // 1. 获得environment中logging.level.*=* 的配置
                  Map levels = new RelaxedPropertyResolver(environment)
                          .getSubProperties("logging.level.");
                  // 标志位,标记是否是对root logger的配置
                  boolean rootProcessed = false;
                  // 2. 遍历之
                  for (Entry entry : levels.entrySet()) {
                      String name = entry.getKey();
                      // 2.1 如果其logger 名和root 一样,如果rootProcessed等于false,则continue.否则,将name设置为null,rootProcessed
                      // 设置为true.--> 意味着,如果logging.level.root= xxx配置了多个,则只有第1个生效
                      if (name.equalsIgnoreCase(LoggingSystem.ROOT_LOGGER_NAME)) {
                          if (rootProcessed) {
                              continue;
                          }
                          name = null;
                          rootProcessed = true;
                      }
                      // 2.2 设置对应的logger的日志级别
                      setLogLevel(system, environment, name, entry.getValue().toString());
                  }
              }
      6. 注册ShutdownHook,代码如下:

            private void registerShutdownHookIfNecessary(Environment environment,
                    LoggingSystem loggingSystem) {
                // 1. 从Environment中读取logging.register-shutdown-hook的配置(如果没有配置,则返回false)
                boolean registerShutdownHook = new RelaxedPropertyResolver(environment)
                        .getProperty(REGISTER_SHUTDOWN_HOOK_PROPERTY, Boolean.class, false);
                if (registerShutdownHook) {
                    // 2. 如果registerShutdownHook等于true,则获得LoggingSystem对应的ShutdownHandler
                    // 如果不等于null则进行注册
                    Runnable shutdownHandler = loggingSystem.getShutdownHandler();
                    if (shutdownHandler != null
                            && shutdownHookRegistered.compareAndSet(false, true)) {
                        registerShutdownHook(new Thread(shutdownHandler));
                    }
                }
            }
        1. 从Environment中读取logging.register-shutdown-hook的配置(如果没有配置,则返回false)
        2. 如果registerShutdownHook等于true,则获得LoggingSystem对应的ShutdownHandler,如果不等于null则进行注册.代码如下:

              void registerShutdownHook(Thread shutdownHook) { Runtime.getRuntime().addShutdownHook(shutdownHook); }

        由于默认情况下,我们没有配置logging.register-shutdown-hook=true,因此是不会注册的. 如果配置了,则JavaLoggingSystem#getShutdownHandler 会返回ShutdownHandler,当jvm退出前,会最终调用其run方法,代码如下:

            public void run() {
                // 重新设置日志配置,所有被命名的logger被删除,所有的Handlers 关闭
                // 除了root logger外所有的日志级别设置为null,root logger 的日志级别设置为info
                LogManager.getLogManager().reset();
            }
  3. 接下来,SpringApplication会调用其prepareContext方法,在该方法中,最终会调用SpringApplicationRunListeners#contextLoaded方法,代码如下:

        listeners.contextLoaded(context);

    在该方法中,最终会发送ApplicationPreparedEvent事件,LoggingApplicationListener监听到该事件后,最终会调用onApplicationPreparedEvent方法进行注册,代码如下:

        private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
            ConfigurableListableBeanFactory beanFactory = event.getApplicationContext()
                    .getBeanFactory();
            if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
                beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
            }
        }

    如果beanFactory中不包含id为springBootLoggingSystem的bean,则将loggingSystem进行注册,id为springBootLoggingSystem

  4. 当spring应用结束前,会执行AbstractApplicationContext#close方法,在该方法中调用了doClose,而在该方法总,调用了publishEvent方法发布了ContextClosedEvent事件,代码如下:

        publishEvent(new ContextClosedEvent(this));

    LoggingApplicationListener监听到该事件后,最终会调用onContextClosedEvent方法,代码如下:

        private void onContextClosedEvent() {
            if (this.loggingSystem != null) {
                this.loggingSystem.cleanUp();
            }
        }

    由于JavaLoggingSystem没有覆写cleanUp方法,因此,该方法不会带来任何的副作用(因为是空实现). 代码如下:

        public void cleanUp() {
        }

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析27-JavaLoggingSystem及LoggingSystem生命周期详解

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏