Spring 正在重置我的日志记录配置 - 我该如何解决这个问题?
Spring is resetting my logging configuration - how do I work around this?
我有一个 Spring 批处理作业,负责处理传入的客户文件。其中一项要求是日志记录是根据作业运行(按客户)分离日志文件。
在我的应用程序的主程序中,我处理命令行参数,然后从那里动态创建我的 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.");
}
从我的 main(或从它调用)执行的任何日志语句都按预期记录到文件中。我可以在调试模式下向下钻取,看到我在根记录器上有两个附加程序——来自两个配置的 "Console" 和 "File" 附加程序。但是,一旦我 运行 SpringApplication.run
命令,FileAppender
就消失了。
我逐步执行了 SpringApplicaton.run(...)
方法,发现 Spring 正在重置我的日志记录配置并从 logback.xml.
重新加载它
来自Spring申请:
try {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, args);
for (SpringApplicationRunListener runListener : runListeners) {
runListener.environmentPrepared(environment);
}
...
来自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
:
@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
)中:
@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();
}
}
}
上面打了最后一个else,reinitialize()
就调用了:
@Override
protected void reinitialize() {
getLoggerContext().reset();
loadConfiguration(getSelfInitializationConfig(), null);
}
在上下文中调用重置是重置所有内容的原因。问题是,深入研究 loadConfiguration
方法也会调用日志记录上下文的重置方法。
关于如何绕过 Spring 重置我的日志记录配置的任何想法?
仅供参考,我使用的是 Spring.
的 4.1.4.RELEASE 版本
这听起来像是将您对日志记录配置的自定义推迟到 LoggingApplicationListener
之后 运行 应该可以工作。
LoggingApplicationListener
执行其初始化以响应 ApplicationEnvironmentPreparedEvent
并具有 Ordered.HIGHEST_PRECEDENCE + 11
的顺序。为了防止您的自定义配置被覆盖,您可以将您的自定义逻辑封装在一个 SmartApplicationListener
中,它响应相同的事件但具有较低的顺序,因此它在 LoggingApplicationListener
之后 运行s:
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;
}
}
您可以创建侦听器并将其注册到应用程序的主要方法中:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.listeners(new CustomLoggingConfigurationApplicationListener())
.run(args);
}
}
我有一个 Spring 批处理作业,负责处理传入的客户文件。其中一项要求是日志记录是根据作业运行(按客户)分离日志文件。
在我的应用程序的主程序中,我处理命令行参数,然后从那里动态创建我的 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.");
}
从我的 main(或从它调用)执行的任何日志语句都按预期记录到文件中。我可以在调试模式下向下钻取,看到我在根记录器上有两个附加程序——来自两个配置的 "Console" 和 "File" 附加程序。但是,一旦我 运行 SpringApplication.run
命令,FileAppender
就消失了。
我逐步执行了 SpringApplicaton.run(...)
方法,发现 Spring 正在重置我的日志记录配置并从 logback.xml.
来自Spring申请:
try {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, args);
for (SpringApplicationRunListener runListener : runListeners) {
runListener.environmentPrepared(environment);
}
...
来自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
:
@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
)中:
@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();
}
}
}
上面打了最后一个else,reinitialize()
就调用了:
@Override
protected void reinitialize() {
getLoggerContext().reset();
loadConfiguration(getSelfInitializationConfig(), null);
}
在上下文中调用重置是重置所有内容的原因。问题是,深入研究 loadConfiguration
方法也会调用日志记录上下文的重置方法。
关于如何绕过 Spring 重置我的日志记录配置的任何想法?
仅供参考,我使用的是 Spring.
的 4.1.4.RELEASE 版本这听起来像是将您对日志记录配置的自定义推迟到 LoggingApplicationListener
之后 运行 应该可以工作。
LoggingApplicationListener
执行其初始化以响应 ApplicationEnvironmentPreparedEvent
并具有 Ordered.HIGHEST_PRECEDENCE + 11
的顺序。为了防止您的自定义配置被覆盖,您可以将您的自定义逻辑封装在一个 SmartApplicationListener
中,它响应相同的事件但具有较低的顺序,因此它在 LoggingApplicationListener
之后 运行s:
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;
}
}
您可以创建侦听器并将其注册到应用程序的主要方法中:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.listeners(new CustomLoggingConfigurationApplicationListener())
.run(args);
}
}