Spring Boot log日志源码解析
SpringBoot源码中对日志是如何处理的?如何开启的?这篇文章带大家从Spring Boot源码层面来了解日志的相关操作。
注册LoggingApplicationListener监听
首先,在springboot的/META-INF/spring.factories配置文件中可以看到注册了LoggingApplicationListener的监听器
# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.context.logging.LoggingApplicationListener,\
SpringApplication中调用
而LoggingApplicationListener实现了接口GenericApplicationListener,最终实现了接口ApplicationListener。
在SpringApplication的构造方法中会获得/META-INF/spring.factories中注册的监听器。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); // 获得注册的监听器 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
在SpringApplication执行run方法的不同阶段,都会调用SpringApplicationRunListeners的对应事件方法。
下面看一下SpringApplicationRunListeners的代码:
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; // ... public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } public void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } public void contextPrepared(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextPrepared(context); } } public void contextLoaded(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.contextLoaded(context); } } public void started(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.started(context); } } public void running(ConfigurableApplicationContext context) { for (SpringApplicationRunListener listener : this.listeners) { listener.running(context); } } // ... }
在不同阶段SpringApplicationRunListeners中提供的方法会遍历listeners中的事件,也就是前面SpringApplication的构造方法获得的事件。
LoggingApplicationListener源码
那么现在看一下LoggingApplicationListener源码中对应的方法都做了些什么。比如onApplicationEvent方法代码如下:
@Override public void onApplicationEvent(ApplicationEvent event) { //在springboot启动的时候 if (event instanceof ApplicationStartedEvent) { onApplicationStartedEvent((ApplicationStartedEvent) event); } //springboot的Environment环境准备完成的时候 else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } //在springboot容器的环境设置完成以后 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(); } }
onApplicationStartedEvent方法,在springboot开始启动时调用,主要工作为获取LoggingSystem。然后调用beforeInitialize进行初始化LoggingSystem前的设置。
private void onApplicationStartedEvent(ApplicationStartedEvent event) { this.loggingSystem = LoggingSystem .get(event.getSpringApplication().getClassLoader()); this.loggingSystem.beforeInitialize(); }
LoggingSystem源码
LoggingSystem的get方法源代码如下:
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")); }
返回值主要通过SYSTEMS获取。而SYSTEMS通过static代码块初始化。
static { Map<String, String> 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); }
在下LoggingSystem类中首先设置LoggingSystem,当运行参数配置-Dorg.springframework.boot.logging.LoggingSystem的时候,会根据配置的参数加载默认支持的LoggingSystem有3个,当没有配置时,或默认获取第一个,即LogbackLoggingSystem。
onApplicationEnvironmentPreparedEvent方法
onApplicationEnvironmentPreparedEvent方法,在springboot完成环境初始化以后进行调用。
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) { //设置相关的环境参数SystemProperty new LoggingSystemProperties(environment).apply(); LogFile logFile = LogFile.get(environment); if (logFile != null) { logFile.applyToSystemProperties(); } //environment参数debug,trace设置日志级别 initializeEarlyLoggingLevel(environment); //environment参数logging.config,初始化loggingSystem initializeSystem(environment, this.loggingSystem, logFile); //设置springboot默认的一些日志级别 initializeFinalLoggingLevels(environment, this.loggingSystem); //注册ShutdownHook registerShutdownHookIfNecessary(environment, this.loggingSystem); }
new LoggingSystemProperties(environment).apply()的主要作用是设置logging.exception-conversion-word、pattern.console、pattern.file、pattern.level到System环境中。
public void apply(LogFile logFile) { PropertyResolver resolver = getPropertyResolver(); setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "exception-conversion-word"); setSystemProperty(PID_KEY, new ApplicationPid().toString()); setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console"); setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file"); setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history"); setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size"); setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level"); setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "pattern.dateformat"); if (logFile != null) { logFile.applyToSystemProperties(); } }
initializeEarlyLoggingLevel(environment)的主要作用是根据environment环境中的debug或者trace属性设置日志级别springBootLogging(后面代码会使用到)。
private void initializeEarlyLoggingLevel(ConfigurableEnvironment environment) { if (this.parseArgs && this.springBootLogging == null) { if (isSet(environment, "debug")) { this.springBootLogging = LogLevel.DEBUG; } if (isSet(environment, "trace")) { this.springBootLogging = LogLevel.TRACE; } } }
nitializeFinalLoggingLevels(environment,this.loggingSystem)做了两件事:设置springboot内置的log日志级别debug或者trace和通过logging.level.*设置第三方的包的日志。
默认日志组件对应包分组:
static { MultiValueMap<String, String> loggers = new LinkedMultiValueMap<>(); loggers.add("web", "org.springframework.core.codec"); loggers.add("web", "org.springframework.http"); loggers.add("web", "org.springframework.web"); loggers.add("web", "org.springframework.boot.actuate.endpoint.web"); loggers.add("web", "org.springframework.boot.web.servlet.ServletContextInitializerBeans"); loggers.add("sql", "org.springframework.jdbc.core"); loggers.add("sql", "org.hibernate.SQL"); DEFAULT_GROUP_LOGGERS = Collections.unmodifiableMap(loggers); }
对应组件日志级别:
static { MultiValueMap<LogLevel, String> loggers = new LinkedMultiValueMap<>(); loggers.add(LogLevel.DEBUG, "sql"); loggers.add(LogLevel.DEBUG, "web"); loggers.add(LogLevel.DEBUG, "org.springframework.boot"); loggers.add(LogLevel.TRACE, "org.springframework"); loggers.add(LogLevel.TRACE, "org.apache.tomcat"); loggers.add(LogLevel.TRACE, "org.apache.catalina"); loggers.add(LogLevel.TRACE, "org.eclipse.jetty"); loggers.add(LogLevel.TRACE, "org.hibernate.tool.hbm2ddl"); LOG_LEVEL_LOGGERS = Collections.unmodifiableMap(loggers); }
至此我们从整体上聊了日志系统的源码。
原文链接:《Spring Boot log日志源码解析》
关于日志的使用可参看文章:《Spring Boot日志框架系统及配置详解》
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接
本文链接:http://choupangxia.com/2019/10/27/spring-boot-log-source-code/