Spring Boot Logback启动流程

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

网络异常,图片无法展示
|


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();
   }
}
复制代码


onApplicationStartingEvent


创建 LoggingSystem

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


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);
  }
}
复制代码

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);
}
复制代码


接下来进入到 LogbackLoggingSystem#beforeInitialize 方法

@Override
public void beforeInitialize() {
   LoggerContext loggerContext = getLoggerContext();
   if (isAlreadyInitialized(loggerContext)) {
      return;
   }
   super.beforeInitialize();
   loggerContext.getTurboFilterList().add(FILTER);
}
复制代码


通过 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;
}
复制代码

网络异常,图片无法展示
|


进入到静态代码块的 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);
    }
}
复制代码


最终会执行下面的代码、尝试获取默认配置文件、如果不存在则通过 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);
        }
    }
}
复制代码


onApplicationEnvironmentPreparedEvent



初始化阶段

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
   if (this.loggingSystem == null) {
      this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
   }
   initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
复制代码
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);
}
复制代码


主要就是从 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);
      }
   }
}
复制代码


正常情况下、我们都没有配置 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.");
    }
  }
复制代码


进入到父类的 initialize 方法中

@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
   if (StringUtils.hasLength(configLocation)) {
      initializeWithSpecificConfig(initializationContext, configLocation, logFile);
      return;
   }
   initializeWithConventions(initializationContext, logFile);
}
复制代码
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);
}
复制代码


正常建议使用的是 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));
   }
}
复制代码
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);
   }
复制代码


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

网络异常,图片无法展示
|


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

网络异常,图片无法展示
|


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

<?xml version="1.0" encoding="UTF-8"?>
<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}.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/${logName}-%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>7</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%logger:%line] --%mdc{client} %msg%n</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
        </filter>
    </appender>
    <!-- Console output -->
    <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-8</charset> <!-- 此处设置字符集 -->
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>DEBUG</level>
        </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>



目录
相关文章
|
27天前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
113 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
19天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
36 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
138 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
5月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的销售项目流程化管理系统附带文章源码部署视频讲解等
65 3
|
19天前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
21 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
25天前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
114 2
|
1月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
44 2
|
20天前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
3月前
|
XML Java 应用服务中间件
SpringBoot启动流程解析
SpringBoot启动流程解析
41 0
|
4月前
|
Java 持续交付 Maven
Spring Boot程序的打包与运行:构建高效部署流程
构建高效的Spring Boot部署流程对于保障应用的快速、稳定上线至关重要。通过采用上述策略,您可以确保部署过程的自动化、可靠性和高效性,从而将专注点放在开发上面。无论是通过Maven的生命周期命令进行打包,还是通过容器技术对部署过程进行优化,选择正确的工具与实践是成功实现这一目标的关键。
170 2