我有一个Spring Batch作业,负责处理传入的客户文件。要求之一是完成日志记录以按作业运行(由客户)分隔日志文件。
在应用程序的主体中,我处理命令行参数,然后从那里动态创建FileAppender。
我的logback.xml:
<configuration> <appender name="Console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="Console" /> </root> </configuration>
我的代码添加了追加器:
private static void setupFileAppender() { String logDir = fetchLogDir(); LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); String datePortion = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); FileAppender<ILoggingEvent> fileAppender = new FileAppender<>(); fileAppender.setContext(loggerContext); fileAppender.setName("File"); fileAppender.setFile(logDir + baseFileName + "-" + datePortion + ".log"); fileAppender.setAppend(true); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); encoder.setContext(loggerContext); encoder.setPattern("%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n"); encoder.start(); fileAppender.setEncoder(encoder); fileAppender.start(); Logger rootLogger = loggerContext.getLogger("root"); rootLogger.addAppender(fileAppender); log.info("Logging configured."); }
从我的主目录(或从其调用)执行的所有日志语句均按预期记录到文件中。我可以在调试模式下进行深入研究,然后看到根记录器上有两个追加程序- 两种配置中的“控制台”和“文件”追加程序。但是,一旦运行SpringApplication.run命令,该FileAppender消失就消失了。
SpringApplication.run
FileAppender
我逐步介绍了该SpringApplicaton.run(...)方法,发现Spring正在重置我的日志记录配置,并从logback.xml重新加载它。
SpringApplicaton.run(...)
从SpringApplication:
try { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, args); for (SpringApplicationRunListener runListener : runListeners) { runListener.environmentPrepared(environment); } ...
来自EventPublishingRunListener:
EventPublishingRunListener
@Override public void environmentPrepared(ConfigurableEnvironment environment) { publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); } private void publishEvent(SpringApplicationEvent event) { this.multicaster.multicastEvent(event); }
稍后打了几个电话,然后LoggingApplicationListener:
LoggingApplicationListener
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartedEvent) { onApplicationStartedEvent((ApplicationStartedEvent) event); } else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } } private void onApplicationPreparedEvent(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) { if (System.getProperty(PID_KEY) == null) { System.setProperty(PID_KEY, new ApplicationPid().toString()); } initializeEarlyLoggingLevel(environment); initializeSystem(environment, this.loggingSystem); initializeFinalLoggingLevels(environment, this.loggingSystem); } private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system) { LogFile logFile = LogFile.get(environment); String logConfig = environment.getProperty(CONFIG_PROPERTY); if (StringUtils.hasLength(logConfig)) { try { ResourceUtils.getURL(logConfig).openStream().close(); system.initialize(logConfig, logFile); } catch (Exception ex) { this.logger.warn("Logging environment value '" + logConfig + "' cannot be opened and will be ignored " + "(using default location instead)"); system.initialize(null, logFile); } } else { system.initialize(null, logFile); } }
在LogbackLoggingSystem(和AbstractLoggingSystem)中:
LogbackLoggingSystem
AbstractLoggingSystem
@Override public void initialize(String configLocation, LogFile logFile) { getLogger(null).getLoggerContext().getTurboFilterList().remove(FILTER); super.initialize(configLocation, logFile); } @Override public void initialize(String configLocation, LogFile logFile) { if (StringUtils.hasLength(configLocation)) { // Load a specific configuration configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation); loadConfiguration(configLocation, logFile); } else { String selfInitializationConfig = getSelfInitializationConfig(); if (selfInitializationConfig == null) { // No self initialization has occurred, use defaults loadDefaults(logFile); } else if (logFile != null) { // Self initialization has occurred but the file has changed, reload loadConfiguration(selfInitializationConfig, logFile); } else { reinitialize(); } } }
最后一个击在上面,reinitialize()称为:
reinitialize()
@Override protected void reinitialize() { getLoggerContext().reset(); loadConfiguration(getSelfInitializationConfig(), null); }
调用上下文重置将重置所有内容。事实是,深入该loadConfiguration方法也将reset方法称为日志记录上下文。
loadConfiguration
关于如何解决Spring重新设置日志记录配置的任何想法?
仅供参考,我使用的是4.1.4.RELEASE版本的Spring。
这听起来像将您对日志记录配置的自定义推迟到LoggingApplicationListener运行之后才起作用。
LoggingApplicationListener响应ApplicationEnvironmentPreparedEvent并执行的顺序执行初始化Ordered.HIGHEST_PRECEDENCE + 11。为了防止您的自定义配置被覆盖,您可以将您的自定义逻辑封装在SmartApplicationListener响应相同事件但顺序较低的中,使其在LoggingApplicationListener以下时间运行:
ApplicationEnvironmentPreparedEvent
Ordered.HIGHEST_PRECEDENCE + 11
SmartApplicationListener
public class CustomLoggingConfigurationApplicationListener implements SmartApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { // Customise the logging configuration } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 12; } @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType); } @Override public boolean supportsSourceType(Class<?> sourceType) { return true; } }
您可以创建侦听器并将其注册到应用程序的main方法中:
@SpringBootApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class) .listeners(new CustomLoggingConfigurationApplicationListener()) .run(args); } }