• Spring Boot Logback启动流程


    Spring Boot 默认使用的是 Logback 的日志框架、Logback 的组件主要通过 Spring Boot ApplicationListener 启动的

    // LoggingApplicationListener
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
       if (event instanceof ApplicationStartingEvent) {
          onApplicationStartingEvent((ApplicationStartingEvent) event);
       }
       else if (event instanceof ApplicationEnvironmentPreparedEvent) {
          onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
       }
       else if (event instanceof ApplicationPreparedEvent) {
          onApplicationPreparedEvent((ApplicationPreparedEvent) event);
       }
       else if (event instanceof ContextClosedEvent
             && ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
          onContextClosedEvent();
       }
       else if (event instanceof ApplicationFailedEvent) {
          onApplicationFailedEvent();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    onApplicationStartingEvent

    创建 LoggingSystem

    private void onApplicationStartingEvent(ApplicationStartingEvent event) {
       this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
       this.loggingSystem.beforeInitialize();
    }
    
    • 1
    • 2
    • 3
    • 4

    LoggingSystem 是 Spring Boot 抽象的日志系统接口

    public static LoggingSystem get(ClassLoader classLoader) {
       String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
       if (StringUtils.hasLength(loggingSystem)) {
          if (NONE.equals(loggingSystem)) {
             return new NoOpLoggingSystem();
          }
          return get(classLoader, loggingSystem);
       }
       return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
             .map((entry) -> get(classLoader, entry.getValue())).findFirst()
             .orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
    }
    
    private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
      try {
        Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
        Constructor<?> constructor = systemClass.getDeclaredConstructor(ClassLoader.class);
        constructor.setAccessible(true);
        return (LoggingSystem) constructor.newInstance(classLoader);
      }
      catch (Exception ex) {
        throw new IllegalStateException(ex);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    SYSTEMS 的值如下、默认情况下创建的就是 LogbackLoggingSystem

    private static final Map<String, String> SYSTEMS;
    
    static {
       Map<String, String> systems = new LinkedHashMap<>();
       systems.put("ch.qos.logback.classic.LoggerContext",
             "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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接下来进入到 LogbackLoggingSystem#beforeInitialize 方法

    @Override
    public void beforeInitialize() {
       LoggerContext loggerContext = getLoggerContext();
       if (isAlreadyInitialized(loggerContext)) {
          return;
       }
       super.beforeInitialize();
       loggerContext.getTurboFilterList().add(FILTER);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    通过 StaticLoggerBinder.getSingleton() 创建 LoggerContext

    private LoggerContext getLoggerContext() {
       ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
       Assert.isInstanceOf(LoggerContext.class, factory,
             String.format(
                   "LoggerFactory is not a Logback LoggerContext but Logback is on "
                         + "the classpath. Either remove Logback or the competing "
                         + "implementation (%s loaded from %s). If you are using "
                         + "WebLogic you will need to add 'org.slf4j' to "
                         + "prefer-application-packages in WEB-INF/weblogic.xml",
                   factory.getClass(), getLocation(factory)));
       return (LoggerContext) factory;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UzJLNIVn-1652367835486)(https://tva1.sinaimg.cn/large/e6c9d24egy1h0k3conp2qj214x0u0jvx.jpg)]

    进入到静态代码块的 init 方法

    void init() {
        try {
            try {
                new ContextInitializer(defaultLoggerContext).autoConfig();
            } catch (JoranException je) {
                Util.report("Failed to auto configure default logger context", je);
            }
            // logback-292
            if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
                StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
            }
            contextSelectorBinder.init(defaultLoggerContext, KEY);
            initialized = true;
        } catch (Exception t) { // see LOGBACK-1159
            Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    最终会执行下面的代码、尝试获取默认配置文件、如果不存在则通过 SPI 获取 Configurator 实现类、如果还是没有、则使用默认的配置 BasicConfigurator

    public void autoConfig() throws JoranException {
        StatusListenerConfigHelper.installIfAsked(loggerContext);
        URL url = findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            configureByResource(url);
        } else {
            Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
            if (c != null) {
                try {
                    c.setContext(loggerContext);
                    c.configure(loggerContext);
                } catch (Exception e) {
                    throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
                                    .getCanonicalName() : "null"), e);
                }
            } else {
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(loggerContext);
                basicConfigurator.configure(loggerContext);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    onApplicationEnvironmentPreparedEvent

    初始化阶段

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
       if (this.loggingSystem == null) {
          this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
       }
       initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
       new LoggingSystemProperties(environment).apply();
       this.logFile = LogFile.get(environment);
       if (this.logFile != null) {
          this.logFile.applyToSystemProperties();
       }
       this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
       initializeEarlyLoggingLevel(environment);
       initializeSystem(environment, this.loggingSystem, this.logFile);
       initializeFinalLoggingLevels(environment, this.loggingSystem);
       registerShutdownHookIfNecessary(environment, this.loggingSystem);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    主要就是从 environment 对象中设置相关属性值到 System 中、然后判断是否设置了 log file

    最主要的 initializeSystem

    private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
       LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
       String logConfig = environment.getProperty(CONFIG_PROPERTY);
       if (ignoreLogConfig(logConfig)) {
          system.initialize(initializationContext, null, logFile);
       }
       else {
          try {
             system.initialize(initializationContext, logConfig, logFile);
          }
          catch (Exception ex) {
             Throwable exceptionToReport = ex;
             while (exceptionToReport != null && !(exceptionToReport instanceof FileNotFoundException)) {
                exceptionToReport = exceptionToReport.getCause();
             }
             exceptionToReport = (exceptionToReport != null) ? exceptionToReport : 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 + "'");
             exceptionToReport.printStackTrace(System.err);
             throw new IllegalStateException(ex);
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    正常情况下、我们都没有配置 logConfig 进入到下面的方法

    	@Override
    	public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
    		LoggerContext loggerContext = getLoggerContext();
    		if (isAlreadyInitialized(loggerContext)) {
    			return;
    		}
    		super.initialize(initializationContext, configLocation, logFile);
    		loggerContext.getTurboFilterList().remove(FILTER);
    		markAsInitialized(loggerContext);
    		if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
    			getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
    					+ "' system property. Please use 'logging.config' instead.");
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    进入到父类的 initialize 方法中

    @Override
    public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
       if (StringUtils.hasLength(configLocation)) {
          initializeWithSpecificConfig(initializationContext, configLocation, logFile);
          return;
       }
       initializeWithConventions(initializationContext, logFile);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
       String config = getSelfInitializationConfig();
       if (config != null && logFile == null) {
          // self initialization has occurred, reinitialize in case of property changes
          reinitialize(initializationContext);
          return;
       }
       if (config == null) {
          config = getSpringInitializationConfig();
       }
       if (config != null) {
          loadConfiguration(initializationContext, config, logFile);
          return;
       }
       loadDefaults(initializationContext, logFile);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    正常建议使用的是 xxx-spring.xml 的配置文件、那么我们进入到 loadConfiguration 中

    @Override
    protected void loadConfiguration(LoggingInitializationContext initializationContext, String location,
          LogFile logFile) {
       super.loadConfiguration(initializationContext, location, logFile);
       LoggerContext loggerContext = getLoggerContext();
       stopAndReset(loggerContext);
       try {
          configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location));
       }
       catch (Exception ex) {
          throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
       }
       List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList();
       StringBuilder errors = new StringBuilder();
       for (Status status : statuses) {
          if (status.getLevel() == Status.ERROR) {
             errors.append((errors.length() > 0) ? String.format("%n") : "");
             errors.append(status.toString());
          }
       }
       if (errors.length() > 0) {
          throw new IllegalStateException(String.format("Logback configuration error detected: %n%s", errors));
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
          URL url) throws JoranException {
       if (url.toString().endsWith("xml")) {
          JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
          configurator.setContext(loggerContext);
          configurator.doConfigure(url);
       }
       else {
          new ContextInitializer(loggerContext).configureByResource(url);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    我们发现了一个 SpringBootJoranConfigurator 这个配置一看就是 Spring Boot 相关的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-st2p63uA-1652367835487)(https://tva1.sinaimg.cn/large/e6c9d24egy1h0k3nimm0uj21el0u043j.jpg)]

    增加了一些 Spring Boot 的规则、非常明显、用于从 environment 中获取属性填充到 logback 配置文件中

    微信公众号:CoderLi

    一起看下 logback-spring.xml 文件就能感受到了

    
    <configuration scan="true">
    
        <springProperty scope="context" name="logName" source="spring.application.name" defaultValue="localhost.log"/>
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>logs/${logName}.logfile>
            <append>trueappend>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>logs/${logName}-%d{yyyy-MM-dd}.%i.log.zipfileNamePattern>
                <maxFileSize>100MBmaxFileSize>
                <maxHistory>7maxHistory>
                <totalSizeCap>3GBtotalSizeCap>
            rollingPolicy>
            <encoder>
                <pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] --%mdc{client} %msg%npattern>
            encoder>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>DEBUGlevel>
            filter>
        appender>
        
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>
                    [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n
                pattern>
                <charset>UTF-8charset> 
            encoder>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>DEBUGlevel>
            filter>
        appender>
    
        <springProfile name="dev,test">
            <root level="DEBUG">
                <appender-ref ref="FILE"/>
                <appender-ref ref="STDOUT"/>
            root>
        springProfile>
    
        <springProfile name="prod">
            <root level="INFO">
                <appender-ref ref="FILE"/>
                <appender-ref ref="STDOUT"/>
            root>
        springProfile>
    
        <logger name="org.springframework" level="INFO"/>
        <logger name="com.netflix" level="WARN"/>
        <logger name="org" level="INFO"/>
        <logger name="springfox.documentation" level="INFO"/>
    
    configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
  • 相关阅读:
    Pytorch导出onnx模型,C++转化为TensorRT并实现推理过程
    [Err] 1093 - You can‘t specify target table ‘XXX‘ for update in FROM clause
    FPGA设计时序约束一、主时钟与生成时钟
    HTML期末大作业(HTML+CSS+JavaScript响应式游戏资讯网站bootstrap网页)
    【Python入门】元组与字符串
    Linux下导出dump文件(Oracle和PG数据)
    视频美颜SDK,提升企业视频通话质量与形象
    (2022|ECCV,文本图像视频,3D 邻域注意,3D 稀疏注意)NÜWA:神经视觉世界创建的视觉合成预训练
    RabbitMQ学习记录
    LeetCode-208. Implement Trie (Prefix Tree) [C++][Java]
  • 原文地址:https://blog.csdn.net/lijinxiong520/article/details/124742282