001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2022, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.classic.model.processor; 015 016import ch.qos.logback.classic.joran.ReconfigureOnChangeTask; 017import ch.qos.logback.classic.model.ConfigurationModel; 018import ch.qos.logback.core.Context; 019import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 020import ch.qos.logback.core.model.Model; 021import ch.qos.logback.core.model.processor.ModelHandlerBase; 022import ch.qos.logback.core.model.processor.ModelHandlerException; 023import ch.qos.logback.core.model.processor.ModelInterpretationContext; 024import ch.qos.logback.core.spi.ConfigurationEvent; 025import ch.qos.logback.core.util.Duration; 026import ch.qos.logback.core.util.OptionHelper; 027 028import java.net.URL; 029import java.util.concurrent.ScheduledExecutorService; 030import java.util.concurrent.ScheduledFuture; 031import java.util.concurrent.TimeUnit; 032 033/** 034 * This is a subclass of {@link ConfigurationModelHandler} offering configuration reloading support. 035 * 036 */ 037public class ConfigurationModelHandlerFull extends ConfigurationModelHandler { 038 039 public static String FAILED_WATCH_PREDICATE_MESSAGE_1 = "Missing watchable .xml or .properties files."; 040 public static String FAILED_WATCH_PREDICATE_MESSAGE_2 = "Watching .xml files requires that the main configuration file is reachable as a URL"; 041 042 public ConfigurationModelHandlerFull(Context context) { 043 super(context); 044 } 045 046 static public ModelHandlerBase makeInstance2(Context context, ModelInterpretationContext mic) { 047 return new ConfigurationModelHandlerFull(context); 048 } 049 050 @Override 051 protected void processScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) { 052 // override parent to do nothing 053 } 054 055 @Override 056 public void postHandle(ModelInterpretationContext mic, Model model) throws ModelHandlerException { 057 ConfigurationModel configurationModel = (ConfigurationModel) model; 058 // post handling of scan attribute works even we need to watch for included files because the main url is 059 // set in GenericXMLConfigurator very early in the configuration process 060 postProcessScanAttrib(mic, configurationModel); 061 } 062 063 protected void postProcessScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) { 064 String scanStr = mic.subst(configurationModel.getScanStr()); 065 String scanPeriodStr = mic.subst(configurationModel.getScanPeriodStr()); 066 detachedPostProcess(scanStr, scanPeriodStr); 067 } 068 069 /** 070 * This method is called from this class but also from logback-tyler. 071 * 072 * This method assumes that the variables scanStr and scanPeriodStr have undergone variable substitution 073 * as applicable to their current environment 074 * 075 * @param scanStr 076 * @param scanPeriodStr 077 * @since 1.5.0 078 */ 079 public void detachedPostProcess(String scanStr, String scanPeriodStr) { 080 if (!OptionHelper.isNullOrEmptyOrAllSpaces(scanStr) && !"false".equalsIgnoreCase(scanStr)) { 081 ScheduledExecutorService scheduledExecutorService = context.getScheduledExecutorService(); 082 boolean watchPredicateFulfilled = ConfigurationWatchListUtil.watchPredicateFulfilled(context); 083 if (!watchPredicateFulfilled) { 084 addWarn(FAILED_WATCH_PREDICATE_MESSAGE_1); 085 addWarn(FAILED_WATCH_PREDICATE_MESSAGE_2); 086 return; 087 } 088 ReconfigureOnChangeTask rocTask = new ReconfigureOnChangeTask(); 089 rocTask.setContext(context); 090 091 addInfo("Registering a new ReconfigureOnChangeTask " + rocTask); 092 093 context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectorRegisteredEvent(rocTask)); 094 095 Duration duration = getDurationOfScanPeriodAttribute(scanPeriodStr, SCAN_PERIOD_DEFAULT); 096 097 addInfo("Will scan for changes in [" + ConfigurationWatchListUtil.getConfigurationWatchList(context) + "] "); 098 // Given that included files are encountered at a later phase, the complete list 099 // of files to scan can only be determined when the configuration is loaded in full. 100 // However, scan can be active if mainURL is set. Otherwise, when changes are 101 // detected the top level config file cannot be accessed. 102 addInfo("Setting ReconfigureOnChangeTask scanning period to " + duration); 103 104 ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(rocTask, duration.getMilliseconds(), duration.getMilliseconds(), 105 TimeUnit.MILLISECONDS); 106 rocTask.setScheduredFuture(scheduledFuture); 107 context.addScheduledFuture(scheduledFuture); 108 } 109 110 } 111 112 private Duration getDurationOfScanPeriodAttribute(String scanPeriodAttrib, Duration defaultDuration) { 113 Duration duration = null; 114 115 if (!OptionHelper.isNullOrEmptyOrAllSpaces(scanPeriodAttrib)) { 116 try { 117 duration = Duration.valueOf(scanPeriodAttrib); 118 } catch (IllegalStateException | IllegalArgumentException e) { 119 addWarn("Failed to parse 'scanPeriod' attribute [" + scanPeriodAttrib + "]", e); 120 // default duration will be set below 121 } 122 } 123 124 if (duration == null) { 125 addInfo("No 'scanPeriod' specified. Defaulting to " + defaultDuration.toString()); 126 duration = defaultDuration; 127 } 128 return duration; 129 } 130}