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日志框架系统及配置详解

Spring Boot log日志源码解析插图


Spring Boot log日志源码解析插图1

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:http://choupangxia.com/2019/10/27/spring-boot-log-source-code/