SpringBoot如何与logback集成

其实默认情况下,Spring Boot会自动用Logback作为应用日志框架,并用INFO级别输出到控制台。 可以由 spring-boot-starter-logging里面的依赖看出 

那么问题来了, 在springboot中,日志是如何初始化的,怎么加载的日志配置文件,这个日志配置文件是否可以外置化?

带着一系列的问题,查阅了springboot的官方文档及相关资料,总结如下:

1. Logback在SpringBoot应用是如何初始化的

 Spring Boot启动的时候,由org.springframework.boot.context.logging.LoggingApplicationListener根据情况初始化并使用。

 1.1 如何加载logback相关配置文件

在spring 初始化启动的过程中,会根据生命周期的不同阶段,发出对应的动作。这就是Spring ApplicationListener,设计基于观察者模式,而其中LoggingApplicationListener类便是负责logging日志框架的初始化操作。
LoggingApplicationListener被配置在spring-boot-x.x.x.jar的spring.factories文件中,spring启动的时候会去读取这个文件。
   在Spring应用启动时会先获取到对应的日志实例。

  

// org.springframework.boot.logging.LoggingApplicationListener
private void onApplicationStartedEvent(ApplicationStartedEvent event) {
    this.loggingSystem = LoggingSystem
            .get(event.getSpringApplication().getClassLoader());
    this.loggingSystem.beforeInitialize();
}

  检测和获取日志系统,检测的顺序定义在SYSTEMS中,最终通过反射创建LogbackLoggingSystem实例。

  

// org.springframework.boot.logging.LoggingSystem
public static LoggingSystem get(ClassLoader classLoader) {
    String loggingSystem = System.getProperty(SYSTEM_PROPERTY);// null
    if (StringUtils.hasLength(loggingSystem)) {
        if (NONE.equals(loggingSystem)) {
            return new NoOpLoggingSystem();
        }
        return get(classLoader, loggingSystem);
    }
    for (Map.Entry<String, String> entry : SYSTEMS.entrySet()) {
        if (ClassUtils.isPresent(entry.getKey(), classLoader)) {
            return get(classLoader, entry.getValue());
        }
    }
    throw new IllegalStateException("No suitable logging system located");
}


/**
 * A System property that can be used to indicate the {@link LoggingSystem} to use.
 */
public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();

private static final Map<String, String> SYSTEMS;

static {
    Map<String, String> systems = new LinkedHashMap<String, String>();
    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);
}

   然后在ApplicationEnvironmentPreparedEvent之后进行logback的初始化

  

private void onApplicationEnvironmentPreparedEvent(
        ApplicationEnvironmentPreparedEvent event) {
    if (this.loggingSystem == null) {
        this.loggingSystem = LoggingSystem
                .get(event.getSpringApplication().getClassLoader());
    }
    initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}

    /**
 * Initialize the logging system according to preferences expressed through the
 * {@link Environment} and the classpath.
 * @param environment the environment
 * @param classLoader the classloader
 */
protected void initialize(ConfigurableEnvironment environment,
        ClassLoader classLoader) {
    new LoggingSystemProperties(environment).apply();
    LogFile logFile = LogFile.get(environment);
    if (logFile != null) {
        logFile.applyToSystemProperties();
    }
    initializeEarlyLoggingLevel(environment);
    initializeSystem(environment, this.loggingSystem, logFile);
    initializeFinalLoggingLevels(environment, this.loggingSystem);
    registerShutdownHookIfNecessary(environment, this.loggingSystem);
}

  重点看配置文件检测的过程。

private void initializeWithConventions(
        LoggingInitializationContext initializationContext, LogFile logFile) {
        // 获取logback自己支持的配置文件
    String config = getSelfInitializationConfig();
    if (config != null && logFile == null) {
        // self initialization has occurred, reinitialize in case of property changes
        reinitialize(initializationContext);
        return;
    }
    // 如果没有则检测spring相关的logback配置
    if (config == null) {
        config = getSpringInitializationConfig();
    }
    if (config != null) {
        loadConfiguration(initializationContext, config, logFile);
        return;
    }
    loadDefaults(initializationContext, logFile);
}

logback自身配置文件的生效顺序。

// org.springframework.boot.logging.logback.LogbackLoggingSystem
@Override
protected String[] getStandardConfigLocations() {
    return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy",
            "logback.xml" };
}

通过这里可以看到spring boot中支持的logback配置文件格式,就是在logback自配置文件(logback-test.xml, logback.xml等)基础上文件名后面加了“-spring”,如logback-test-spring, logback-spring等。

/**
 * Return the spring config locations for this system. By default this method returns
 * a set of locations based on {@link #getStandardConfigLocations()}.
 * @return the spring config locations
 * @see #getSpringInitializationConfig()
 */
protected String[] getSpringConfigLocations() {
    String[] locations = getStandardConfigLocations();
    for (int i = 0; i < locations.length; i++) {
        String extension = StringUtils.getFilenameExtension(locations[i]);
        locations[i] = locations[i].substring(0,
                locations[i].length() - extension.length() - 1) + "-spring."
                + extension;
    }
    return locations;
}

此外,Spring Boot使用JUnit时,如果没有配置SpringBootTest注解,日志系统根本不会得到初始化,会使用org.slf4j.impl.StaticLoggerBinder获取,如果在test/resource下面存在logback-test.xml则会生效,否则就使用系统默认的配置。如果配置了置SpringBootTest注解,则SpringBoot会正常的初始化,日志系统会正常加载。

 

2.自定义日志配置

由于日志服务一般都在ApplicationContext创建前就初始化了,它并不是必须通过Spring的配置文件控制。因此通过系统属性和传统的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。

 

Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml),命名为logback-spring.xml的日志配置文件,spring boot可以为它添加一些spring boot特有的配置项(下面会提到)。

上面是默认的命名规则,并且放在src/main/resources下面即可。

如果你即想完全掌控日志配置,但又不想用logback.xml作为Logback配置的名字,可以通过logging.config属性指定自定义的名字:

logging.config=classpath:logging-config.xml

虽然一般并不需要改变配置文件的名字,但是如果你想针对不同运行时Profile使用不同的日

志配置,这个功能会很有用。

 

参考:

  https://zhuanlan.zhihu.com/p/27874182

 

posted @ 2020-07-08 14:43  hxwang  阅读(823)  评论(0编辑  收藏  举报