2015-02-09 3 views
2

У меня есть Весеннее пакетное задание, которое отвечает за обработку входящих файлов клиента. Одним из требований является то, что ведение журнала выполняется для разделения файлов журналов на выполнение задания (по клиенту).Весна сбрасывает мою конфигурацию регистрации - как мне обойти это?

В основном приложении я обрабатываю аргументы командной строки, а затем динамически создаю свой 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> 

Мой код добавления Appender:

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 исчезает.

Я прошел через метод SpringApplicaton.run(...), и я обнаружил, что Spring сбрасывает мою конфигурацию ведения журнала и перезагружает ее из файла logback.xml.

От SpringApplication:

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); 
    } 
} 

В LogbackLoggingSystemAbstractLoggingSystem):

@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() называется:

@Override 
protected void reinitialize() { 
    getLoggerContext().reset(); 
    loadConfiguration(getSelfInitializationConfig(), null); 
} 

Вызов для сброса от контекста является то, что сбрасывает все. Дело в том, что копание в методе loadConfiguration также вызывает метод сброса в контексте ведения журнала.

Любые идеи о том, как обойти пружину, сбросив мою конфигурацию регистрации?

FYI, я использую версию Spring версии 4.1.4.RELEASE.

+0

Этот вопрос не связан с весной или весной, все это связано с Spring Boot. –

ответ

4

Похоже, отложить настройку конфигурации ведения журнала до тех пор, пока работа LoggingApplicationListener не будет работать.

LoggingApplicationListener выполняет свою инициализацию в ответ на ApplicationEnvironmentPreparedEvent и имеет порядок Ordered.HIGHEST_PRECEDENCE + 11.Чтобы предотвратить пользовательскую конфигурацию от быть переопределены, вы можете инкапсулировать настройки логики в SmartApplicationListener, которая реагирует на то же событие, но с более низким порядком, поэтому он работает после LoggingApplicationListener:

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); 
    } 
} 
+0

100% пятно на ответе. Спасибо, Энди. – IceBox13

+0

У меня такая же проблема, но это решение не работает для меня. Я уже добрался до этого самого «потенциального решения», но почему-то у моего слушателя никогда не было возможности прослушать ApplicationEnvironmentPreparedEvent и других. Например, если я добавлю точку останова для поддержкиEventType, то все, на которое он вызывается, это: ContextRefreshedEvent, EmbeddedServletContainerInitializedEvent, ApplicationReadyEvent и ContextClosedEvent. ApplicationEnvironmentPreparedEvent, который должен быть раньше всех этих других событий, никогда не запускает моего слушателя. Кто-нибудь знает, почему? – Fabio

+0

Просто выяснили, почему в моем случае это не сработало. Я зарегистрировал своего слушателя, используя @Import в классе Application, вместо прямого вызова метода listeners в SpringApplicationBuilder. Из-за этого мой слушатель не был зарегистрирован вовремя для многих событий, включая ApplicationEnvironmentPreparedEvent. Интересно, будет ли это поведение ожидаться (и документировано), или если это ошибка. – Fabio