其实默认情况下,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