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

前言

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

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

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

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

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

解析

本文先解析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<String, String> SYSTEMS;
    
    static {
        Map<String, String> systems = new LinkedHashMap<String, String>();
        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<LogLevel> 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<LoggerConfiguration> 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<String, String> 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<LoggerConfiguration> getLoggerConfigurations() {
          return Collections.emptyList();
      }
      
      @Override
      public LoggerConfiguration getLoggerConfiguration(String loggerName) {
          return null;
      }
      }

AbstractLoggingSystem

AbstractLoggingSystem–>实现了initialize方法

  1. 字段如下:

    protected static final Comparator<LoggerConfiguration> 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<LogLevel, T> systemToNative;
      
      // key--> log底层支持的日志级别,value --> LogLevel
      private final Map<T, LogLevel> nativeToSystem;
      
      public LogLevels() {
          this.systemToNative = new HashMap<LogLevel, T>();
          this.nativeToSystem = new HashMap<T, LogLevel>();
      }
    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<LogLevel> getSupported() {
          return new LinkedHashSet<LogLevel>(this.nativeToSystem.values());
      }

JavaLoggingSystem

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

  1. 字段,构造器如下:

    private static final LogLevels<Level> LEVELS = new LogLevels<Level>();
    
    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的配置,因此默认使用[%1 tY tm-%1 td tH:%1 tM: tS.%1 tL] s %4 s[ s] — %3 s: 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<LoggerConfiguration> getLoggerConfigurations() {
          List<LoggerConfiguration> result = new ArrayList<LoggerConfiguration>();
          // 1.获得JavaLogging 中的所有logger的名字,遍历之
          Enumeration<String> 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<String, Object> levels = new RelaxedPropertyResolver(environment)
                      .getSubProperties("logging.level.");
              // 标志位,标记是否是对root logger的配置
              boolean rootProcessed = false;
              // 2. 遍历之
              for (Entry<String, Object> 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() {
    }
    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/79103989
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞