spring boot 源码解析28-Log4J2LoggingSystem

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

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

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

前言

spring boot 中关于Log的实现我们已经分析了JavaLoggingSystem,本文就来看看Log4J2LoggingSystem,在分析之前,我们需要先分析一下Slf4JLoggingSystem–> Log4J2LoggingSystem,LogbackLoggingSystem 的父类.

解析

Slf4JLoggingSystem

  1. 字段,构造器如下:

        private static final String BRIDGE_HANDLER = "org.slf4j.bridge.SLF4JBridgeHandler";
    
        public Slf4JLoggingSystem(ClassLoader classLoader) {
            super(classLoader);
        }
  2. 覆写了如下方法:

    1. beforeInitialize,代码如下:

          public void beforeInitialize() {
              super.beforeInitialize();
              // 1. 配置SLF4JBridgeHandler
              configureJdkLoggingBridgeHandler();
          }

      configureJdkLoggingBridgeHandler–>配置SLF4JBridgeHandler 代码如下:

          private void configureJdkLoggingBridgeHandler() {
              try {
                  // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则
                  if (isBridgeHandlerAvailable()) {
                      // 1.1 删除slf4j 中root logger 配置的所有handler
                      removeJdkLoggingBridgeHandler();
                      // 1.2 为root logger添加SLF4JBridgeHandler
                      SLF4JBridgeHandler.install();
                  }
              }
              catch (Throwable ex) {
                  // Ignore. No java.util.logging bridge is installed.
              }
          }
      1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则

        1. 删除slf4j 中root logger 配置的所有handler,代码如下:

              private void removeJdkLoggingBridgeHandler() {
                  try {
                      if (isBridgeHandlerAvailable()) {
                          try {
                              SLF4JBridgeHandler.removeHandlersForRootLogger();
                          }
                          catch (NoSuchMethodError ex) {
                              // Method missing in older versions of SLF4J like in JBoss AS 7.1
                              SLF4JBridgeHandler.uninstall();
                          }
                      }
                  }
                  catch (Throwable ex) {
                      // Ignore and continue
                  }
              }
        2. 为root logger添加SLF4JBridgeHandler,代码如下:

              public static void install() {
                  LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());
              }
    2. cleanUp–>删除slf4j 中root logger 配置的所有handler,代码如下:

          public void cleanUp() {
              removeJdkLoggingBridgeHandler();
          }
    3. loadConfiguration–>设置系统属性,代码如下:

          protected void loadConfiguration(LoggingInitializationContext initializationContext,
                  String location, LogFile logFile) {
              Assert.notNull(location, "Location must not be null");
              if (initializationContext != null) {
                  applySystemProperties(initializationContext.getEnvironment(), logFile);
              }
          }

      调用父类(AbstractLoggingSystem)中的 applySystemProperties方法,代码如下:

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

Log4J2LoggingSystem

  1. 字段如下:

        private static final String FILE_PROTOCOL = "file";
    
        private static final LogLevels LEVELS = new LogLevels();
    
        static {
            LEVELS.map(LogLevel.TRACE, Level.TRACE);
            LEVELS.map(LogLevel.DEBUG, Level.DEBUG);
            LEVELS.map(LogLevel.INFO, Level.INFO);
            LEVELS.map(LogLevel.WARN, Level.WARN);
            LEVELS.map(LogLevel.ERROR, Level.ERROR);
            LEVELS.map(LogLevel.FATAL, Level.FATAL);
            LEVELS.map(LogLevel.OFF, Level.OFF);
        }
    
        // 在beforeInitialize中添加该filter,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了
        private static final Filter FILTER = new AbstractFilter() {
    
            @Override
            public Result filter(LogEvent event) {
                return Result.DENY;
            }
    
            @Override
            public Result filter(Logger logger, Level level, Marker marker, Message msg,
                    Throwable t) {
                return Result.DENY;
            }
    
            @Override
            public Result filter(Logger logger, Level level, Marker marker, Object msg,
                    Throwable t) {
                return Result.DENY;
            }
    
            @Override
            public Result filter(Logger logger, Level level, Marker marker, String msg,
                    Object... params) {
                return Result.DENY;
            }
    
        };
    
        public Log4J2LoggingSystem(ClassLoader classLoader) {
            super(classLoader);
        }
    
  2. 方法如下:

    1. getStandardConfigLocations –> 获取配置文件,代码如下:

          protected String[] getStandardConfigLocations() {
              return getCurrentlySupportedConfigLocations();
          }

      调用:

          private String[] getCurrentlySupportedConfigLocations() {
              List supportedConfigLocations = new ArrayList();
              // 1. 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持
              if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) {
                  Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml");
              }
              // 2. 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn
              if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
                  Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn");
              }
              // 3. 默认加入log4j2.xml
              supportedConfigLocations.add("log4j2.xml");
              return supportedConfigLocations
                      .toArray(new String[supportedConfigLocations.size()]);
          }
      1. 如果当前类路径下存在com.fasterxml.jackson.dataformat.yaml.YAMLParser,则加入log4j2.yaml,log4j2.yml,默认情况下不支持
      2. 如果当前类路径下存在com.fasterxml.jackson.databind.ObjectMapper,则加入log4j2.json,log4j2.jsn
      3. 默认加入log4j2.xml

      因此,默认情况下返回的是log4j2.json, log4j2.jsn, log4j2.xml

    2. beforeInitialize,代码如下:

          public void beforeInitialize() {
              LoggerContext loggerContext = getLoggerContext();
              if (isAlreadyInitialized(loggerContext)) {
                  return;
              }
              super.beforeInitialize();
              loggerContext.getConfiguration().addFilter(FILTER);
          }
      1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return
      2. 调用父类的beforeInitialize,配置SLF4JBridgeHandler
      3. 添加FILTER,目的是log4j2 没有初始化完毕时是不能使用的,此时所有的处理都是DENY,就不会打印日志了
    3. initialize,代码如下:

          public void initialize(LoggingInitializationContext initializationContext,
                  String configLocation, LogFile logFile) {
              // 1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,
              // 则意味着已经初始化过了,此时直接return
              LoggerContext loggerContext = getLoggerContext();
              if (isAlreadyInitialized(loggerContext)) {
                  return;
              }
              // 2. 删除FILTER,此时意味着已经初始化成功了
              loggerContext.getConfiguration().removeFilter(FILTER);
              // 3. 加载配置文件
              super.initialize(initializationContext, configLocation, logFile);
              // 4. 向LoggerContext中的ExternalContext 存放-->org.springframework.boot.logging.LoggingSystem,标记成功初始化
              markAsInitialized(loggerContext);
          }
      1. 获得LoggerContext,如果LoggerContext中的ExternalContext 存放的是org.springframework.boot.logging.LoggingSystem,则意味着已经初始化过了,此时直接return
      2. 删除FILTER,此时意味着已经初始化成功了
      3. 加载配置文件
      4. 向LoggerContext中的ExternalContext 存放–>org.springframework.boot.logging.LoggingSystem,标记成功初始化.代码如下:

            private void markAsInitialized(LoggerContext loggerContext) {
                loggerContext.setExternalContext(LoggingSystem.class.getName());
            }
    4. loadDefaults–>加载默认的配置文件:

          protected void loadDefaults(LoggingInitializationContext initializationContext,
                  LogFile logFile) {
              // 1. 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的
              if (logFile != null) {
                  loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile);
              }
              // 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml
              else {
                  loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile);
              }
          }
      1. 如果logFile 不等于null,则加载org/springframework/boot/logging/log4j2/log4j2-file.xml的配置,默认这步是不会执行的
      2. 否则,加载org/springframework/boot/logging/log4j2/log4j2.xml

      loadConfiguration实现如下:

          protected void loadConfiguration(String location, LogFile logFile) {
              Assert.notNull(location, "Location must not be null");
              try {
                  // 1. 获得LoggerContext
                  LoggerContext ctx = getLoggerContext();
                  // 2.将location转换为URL
                  URL url = ResourceUtils.getURL(location);
                  // 3.根据url 获得对应的ConfigurationSource
                  ConfigurationSource source = getConfigurationSource(url);
                  // 4. 启动
                  ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
              }
              catch (Exception ex) {
                  throw new IllegalStateException(
                          "Could not initialize Log4J2 logging from " + location, ex);
              }
          }
      1. 获得LoggerContext
      2. 将location转换为URL
      3. 根据url 获得对应的ConfigurationSource.代码如下:

            private ConfigurationSource getConfigurationSource(URL url) throws IOException {
                InputStream stream = url.openStream();
                if (FILE_PROTOCOL.equals(url.getProtocol())) {// 如果是file协议
                    return new ConfigurationSource(stream, ResourceUtils.getFile(url));
                }
                // 2. 其他,由于当前是classpath 协议,因此会执行到这里
                return new ConfigurationSource(stream, url);
            }
      4. 启动
    5. reinitialize –>调用时机:在log4j初始化时,在类路径下加载的以下任一1个配置文件:log4j2.json, log4j2.jsn, log4j2.xml,通过LogFile等于null,则调用该方法.代码如下:

          protected void reinitialize(LoggingInitializationContext initializationContext) {
              getLoggerContext().reconfigure();
          }

      log4j 不会直接删除所有的Loggers在重新配置的阶段,而是会重新创建LoggerConfig,然后进行替换.旧的LoggerConfig,Appenders,Filters 会被释放

    6. setLogLevel.代码如下:

          public void setLogLevel(String loggerName, LogLevel logLevel) {
              // 1. 转换LogLevel 为log4j2的LogLevel
              Level level = LEVELS.convertSystemToNative(logLevel);
              // 2.根据loggerName 获得对应的LoggerConfig
              LoggerConfig loggerConfig = getLoggerConfig(loggerName);
              // 3. 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改
              if (loggerConfig == null) {
                  loggerConfig = new LoggerConfig(loggerName, level, true);
                  getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig);
              }
              else {
                  loggerConfig.setLevel(level);
              }
              // 4. 更新
              getLoggerContext().updateLoggers();
          }
      1. 转换LogLevel 为log4j2的LogLevel
      2. 根据loggerName 获得对应的LoggerConfig
      3. 如果不存在对应的LoggerConfig,则进行添加,否则直接进行修改
      4. 更新
    7. getLoggerConfigurations –> 获得所有的配置文件.代码如下:

          public List getLoggerConfigurations() {
              List result = new ArrayList();
              Configuration configuration = getLoggerContext().getConfiguration();
              for (LoggerConfig loggerConfig : configuration.getLoggers().values()) {
                  result.add(convertLoggerConfiguration(loggerConfig));
              }
              Collections.sort(result, CONFIGURATION_COMPARATOR);
              return result;
          }
      1. 获得Configuration中配置的Logger,遍历之
      2. 将LoggerConfig 转换为LoggerConfiguration.代码如下:

            private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) {
                if (loggerConfig == null) {
                    return null;
                }
                LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel());
                String name = loggerConfig.getName();
                if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) {
                    name = ROOT_LOGGER_NAME;
                }
                return new LoggerConfiguration(name, level, level);
            }
        1. 如果LoggerConfig等于null,则返回null
        2. 将log4j配置的日志级别转换为LogLevel
        3. 如果logger的名字等于root,或者不存在,则将其赋值为root
        4. 实例化LoggerConfiguration
      3. 排序–>将root logger 排在第1位,其他的按照字典顺序排序
    8. getShutdownHandler–>在ShutdownHandler中直接调用LoggerContext的stop方法.代码如下:

          public Runnable getShutdownHandler() {
              return new ShutdownHandler();
          }

      ShutdownHandler 代码如下:

          private final class ShutdownHandler implements Runnable {
      
              @Override
              public void run() {
                  getLoggerContext().stop();
              }
          }
    9. cleanUp–>调用时机,当spring boot 发出ContextClosedEvent事件时调用,代码如下:

          public void cleanUp() {
              super.cleanUp();
              LoggerContext loggerContext = getLoggerContext();
              markAsUninitialized(loggerContext);
              loggerContext.getConfiguration().removeFilter(FILTER);
          }
      1. 调用父类的cleanUp方法,删除rootLogger配置的handler
      2. 获得LoggerContext
      3. 设置ExternalContext 为null,代码如下:

            private void markAsUninitialized(LoggerContext loggerContext) {
                loggerContext.setExternalContext(null);
            }
      4. 删除FILTER

Log4J2LoggingSystem 集成

  1. 由于spring boot 默认依赖的logback,因此我们需要去除,修改pom文件如下:

        
            org.springframework.boot
            spring-boot-starter
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        
  2. 加入 Log4J2LoggingSystem的依赖.pom文件如下:

        
            org.springframework.boot
            spring-boot-starter-log4j2
        

Log4J2LoggingSystem生命周期

  1. 由于SpringBootConfigurationFactory继承了ConfigurationFactory,因此当使用Log4J2LoggingSystem 的时候,由于log还没有初始化,此时先使用DefaultConfiguration,后续会使用指定的配置.代码如下:

        @Plugin(name = "SpringBootConfigurationFactory", category = ConfigurationFactory.CATEGORY)
        @Order(0)
        public class SpringBootConfigurationFactory extends ConfigurationFactory {
    
        private static final String[] TYPES = { ".springboot" };
    
        @Override
        protected String[] getSupportedTypes() {
            return TYPES;
        }
    
        @Override
        public Configuration getConfiguration(LoggerContext loggerContext,
                ConfigurationSource source) {
            if (source != null && source != ConfigurationSource.NULL_SOURCE) {
                if (LoggingSystem.get(loggerContext.getClass().getClassLoader()) != null) {
                    return new DefaultConfiguration();
                }
            }
            return null;
        }
        }

    当在类路径下存在.springboot结尾的文件,则会调用该类的getConfiguration方法.由于在spring-boot/src/main/resources/ 下,存在log4j2.springboot,因此该类会被执行.其文件内容如下:

        See SpringBootConfigurationFactory

    此时由于传入的ConfigurationSource不等于null,此时传入的是log4j2.springboot,因此会返回DefaultConfiguration

  2. ApplicationStartingEvent事件处理–>执行Log4J2LoggingSystem#beforeInitialize方法.在该方法中会调用Slf4JLoggingSystem#configureJdkLoggingBridgeHandler,会判断org.slf4j.bridge.SLF4JBridgeHandler是否存在,此时由于加入了spring-boot-starter-log4j2,因此加入了jul-to-slf4 jar 包, 该类就是在该包中,因此会执行后续操作,代码如下:

        private void configureJdkLoggingBridgeHandler() {
            try {
                // 1. 如果在当前类路径下存在org.slf4j.bridge.SLF4JBridgeHandler,则
                if (isBridgeHandlerAvailable()) {
                    // 1.1 删除slf4j 中root logger 配置的所有handler
                    removeJdkLoggingBridgeHandler();
                    // 1.2 为root logger添加SLF4JBridgeHandler
                    SLF4JBridgeHandler.install();
                }
            }
            catch (Throwable ex) {
                // Ignore. No java.util.logging bridge is installed.
            }
        }
  3. ApplicationEnvironmentPreparedEvent–> 最终会调用Log4J2LoggingSystem#initialize.
  4. ApplicationPreparedEvent事件–> 向beanFactory进行注册.
  5. ContextClosedEvent–>执行Log4J2LoggingSystem#cleanUp方法.
  6. jvm退出前,执行Log4J2LoggingSystem注册的ShutdownHandler,将LoggerContext停止掉.代码如下:

        public void run() {
            getLoggerContext().stop();
        }

来源:[]()

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » spring boot 源码解析28-Log4J2LoggingSystem

评论 抢沙发

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

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

支付宝扫一扫打赏

微信扫一扫打赏