Skip to content

Commit

Permalink
reloadable properties configuration
Browse files Browse the repository at this point in the history
Signed-off-by: Ceki Gulcu <[email protected]>
  • Loading branch information
ceki committed Sep 6, 2024
1 parent 9f4a2bc commit 4f9fb1f
Show file tree
Hide file tree
Showing 23 changed files with 344 additions and 193 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import ch.qos.logback.classic.model.processor.ConfigurationModelHandlerFull;
import ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.joran.GenericXMLConfigurator;
import ch.qos.logback.core.joran.JoranConfiguratorBase;
import ch.qos.logback.core.joran.action.AppenderRefAction;
import ch.qos.logback.core.joran.action.IncludeAction;
Expand All @@ -27,7 +26,6 @@
import ch.qos.logback.core.joran.spi.RuleStore;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.model.processor.DefaultProcessor;
import ch.qos.logback.core.model.processor.ModelInterpretationContext;

/**
* JoranConfigurator class adds rules specific to logback-classic.
Expand Down Expand Up @@ -58,7 +56,8 @@ public void addElementSelectorAndActionAssociations(RuleStore rs) {
rs.addRule(new ElementSelector("configuration/root/appender-ref"), () -> new AppenderRefAction());

rs.addRule(new ElementSelector("configuration/include"), () -> new IncludeAction());
rs.addRule(new ElementSelector("configuration/propertyConfigurator"), () -> new PropertyConfiguratorAction());
rs.addRule(new ElementSelector("configuration/propertiesConfigurator"), () -> new PropertiesConfiguratorAction());

rs.addRule(new ElementSelector("configuration/consolePlugin"), () -> new ConsolePluginAction());

rs.addRule(new ElementSelector("configuration/receiver"), () -> new ReceiverAction());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void link(DefaultProcessor defaultProcessor) {
defaultProcessor.addHandler(ContextNameModel.class, ContextNameModelHandler::makeInstance);
defaultProcessor.addHandler(LoggerContextListenerModel.class, LoggerContextListenerModelHandler::makeInstance);

defaultProcessor.addHandler(PropertyConfiguratorModel.class, PropertyConfiguratorModelHandler::makeInstance);
defaultProcessor.addHandler(PropertiesConfiguratorModel.class, PropertiesConfiguratorModelHandler::makeInstance);
defaultProcessor.addHandler(InsertFromJNDIModel.class, InsertFromJNDIModelHandler::makeInstance);

defaultProcessor.addHandler(AppenderModel.class, AppenderModelHandler::makeInstance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import ch.qos.logback.core.model.util.VariableSubstitutionsHelper;
import ch.qos.logback.core.spi.ContextAwareBase;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -76,14 +77,18 @@ public void doConfigure(URL url) throws JoranException {
}
}

public void doConfigure(String filename) throws JoranException {
try(FileInputStream fileInputStream = new FileInputStream(filename)) {
public void doConfigure(File file) throws JoranException {
try(FileInputStream fileInputStream = new FileInputStream(file)) {
doConfigure(fileInputStream);
} catch (IOException e) {
throw new JoranException("Failed to load file "+filename, e);
throw new JoranException("Failed to load file "+file, e);
}
}

public void doConfigure(String filename) throws JoranException {
doConfigure(new File(filename));
}

public void doConfigure(InputStream inputStream) throws JoranException {
Properties props = new Properties();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package ch.qos.logback.classic.joran;

import java.io.File;
import java.io.FileInputStream;
import java.net.URL;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
Expand All @@ -29,6 +30,7 @@
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.status.StatusUtil;

import static ch.qos.logback.core.CoreConstants.PROPERTIES_FILE_EXTENSION;
import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationChangeDetectorRunningEvent;
import static ch.qos.logback.core.spi.ConfigurationEvent.newConfigurationEndedSuccessfullyEvent;

Expand Down Expand Up @@ -58,18 +60,26 @@ public void run() {
addInfo("Empty watch file list. Disabling ");
return;
}

if (!configurationWatchList.changeDetected()) {
File changedFile = configurationWatchList.changeDetected();
if (changedFile == null) {
return;
}
context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectedEvent(this));
cancelFutureInvocationsOfThisTaskInstance();

URL mainConfigurationURL = configurationWatchList.getMainURL();

addInfo(DETECTED_CHANGE_IN_CONFIGURATION_FILES);
addInfo(CoreConstants.RESET_MSG_PREFIX + "named [" + context.getName() + "]");

if(changedFile.getName().endsWith(PROPERTIES_FILE_EXTENSION)) {
runPropertiesConfigurator(changedFile);
// no further processing
return;
}

// ========

cancelFutureInvocationsOfThisTaskInstance();
URL mainConfigurationURL = configurationWatchList.getMainURL();

LoggerContext lc = (LoggerContext) context;
if (mainConfigurationURL.toString().endsWith("xml")) {
performXMLConfiguration(lc, mainConfigurationURL);
Expand All @@ -79,6 +89,17 @@ public void run() {
//fireDoneReconfiguring();
}

private void runPropertiesConfigurator(File changedFile) {
addInfo("Will run PropertyConfigurator on "+changedFile.getAbsolutePath());
PropertyConfigurator propertyConfigurator = new PropertyConfigurator();
propertyConfigurator.setContext(context);
try {
propertyConfigurator.doConfigure(changedFile);
} catch (JoranException e) {
addError("Failed to reload "+ changedFile);
}
}

private void cancelFutureInvocationsOfThisTaskInstance() {
boolean result = scheduledFuture.cancel(false);
if(!result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,19 @@

package ch.qos.logback.classic.joran.action;

import ch.qos.logback.classic.model.PropertyConfiguratorModel;
import ch.qos.logback.classic.model.PropertiesConfiguratorModel;
import ch.qos.logback.core.joran.action.ResourceAction;
import ch.qos.logback.core.model.IncludeModel;

/**
* Build an {@link PropertyConfiguratorModel} instance from SAX events.
* Build an {@link PropertiesConfiguratorModel} instance from SAX events.
*
* @author Ceki G&uuml;lc&uuml;
* @since 1.5.8
*/
public class PropertyConfiguratorAction extends ResourceAction {
public class PropertiesConfiguratorAction extends ResourceAction {

protected PropertyConfiguratorModel makeNewResourceModel() {
return new PropertyConfiguratorModel();
protected PropertiesConfiguratorModel makeNewResourceModel() {
return new PropertiesConfiguratorModel();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import ch.qos.logback.core.model.ResourceModel;

public class PropertyConfiguratorModel extends ResourceModel {
public class PropertiesConfiguratorModel extends ResourceModel {

private static final long serialVersionUID = -2009536798661734346L;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import ch.qos.logback.core.Context;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.model.processor.ModelHandlerBase;
import ch.qos.logback.core.model.processor.ModelHandlerException;
import ch.qos.logback.core.model.processor.ModelInterpretationContext;
import ch.qos.logback.core.status.OnConsoleStatusListener;
import ch.qos.logback.core.util.ContextUtil;
Expand Down Expand Up @@ -83,6 +84,8 @@ public void handle(ModelInterpretationContext mic, Model model) {

ContextUtil contextUtil = new ContextUtil(context);
contextUtil.addGroovyPackages(lc.getFrameworkPackages());


}

protected void processScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) {
Expand All @@ -92,5 +95,13 @@ protected void processScanAttrib(ModelInterpretationContext mic, ConfigurationMo
}
}

protected void postProcessScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) {

}

@Override
public void postHandle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
ConfigurationModel configurationModel = (ConfigurationModel) model;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import ch.qos.logback.classic.model.ConfigurationModel;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.model.processor.ModelHandlerBase;
import ch.qos.logback.core.model.processor.ModelHandlerException;
import ch.qos.logback.core.model.processor.ModelInterpretationContext;
import ch.qos.logback.core.spi.ConfigurationEvent;
import ch.qos.logback.core.util.Duration;
Expand All @@ -32,50 +34,60 @@
* This is a subclass of {@link ConfigurationModelHandler} offering configuration reloading support.
*
*/
public class ConfigurationModelHandlerFull extends ConfigurationModelHandler {
public class ConfigurationModelHandlerFull extends ConfigurationModelHandler {

public static String FAILED_WATCH_PREDICATE_MESSAGE_1 = "Missing watchable .xml or .properties files.";
public static String FAILED_WATCH_PREDICATE_MESSAGE_2 = "Watching .xml files requires that the main configuration file is reachable as a URL";

public ConfigurationModelHandlerFull(Context context) {
super(context);
}



static public ModelHandlerBase makeInstance2(Context context, ModelInterpretationContext mic) {
return new ConfigurationModelHandlerFull(context);
}

@Override
protected void processScanAttrib( ModelInterpretationContext mic, ConfigurationModel configurationModel) {

}

@Override
public void postHandle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
ConfigurationModel configurationModel = (ConfigurationModel) model;
postProcessScanAttrib(mic, configurationModel);
}

protected void processScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) {
protected void postProcessScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) {
String scanStr = mic.subst(configurationModel.getScanStr());
if (!OptionHelper.isNullOrEmptyOrAllSpaces(scanStr) && !"false".equalsIgnoreCase(scanStr)) {

ScheduledExecutorService scheduledExecutorService = context.getScheduledExecutorService();
URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context);
if (mainURL == null) {
addWarn("Due to missing top level configuration file, reconfiguration on change (configuration file scanning) cannot be done.");
boolean watchPredicateFulfilled = ConfigurationWatchListUtil.watchPredicateFulfilled(context);
if (!watchPredicateFulfilled) {
addWarn(FAILED_WATCH_PREDICATE_MESSAGE_1);
addWarn(FAILED_WATCH_PREDICATE_MESSAGE_2);
return;
}
ReconfigureOnChangeTask rocTask = new ReconfigureOnChangeTask();
rocTask.setContext(context);

addInfo("Registering a new ReconfigureOnChangeTask "+ rocTask);
addInfo("Registering a new ReconfigureOnChangeTask " + rocTask);

context.fireConfigurationEvent(ConfigurationEvent.newConfigurationChangeDetectorRegisteredEvent(rocTask));

String scanPeriodStr = mic.subst(configurationModel.getScanPeriodStr());
Duration duration = getDurationOfScanPeriodAttribute(scanPeriodStr, SCAN_PERIOD_DEFAULT);

addInfo("Will scan for changes in [" + mainURL + "] ");
addInfo("Will scan for changes in [" + ConfigurationWatchListUtil.getConfigurationWatchList(context) + "] ");
// Given that included files are encountered at a later phase, the complete list
// of files
// to scan can only be determined when the configuration is loaded in full.
// of files to scan can only be determined when the configuration is loaded in full.
// However, scan can be active if mainURL is set. Otherwise, when changes are
// detected
// the top level config file cannot be accessed.
// detected the top level config file cannot be accessed.
addInfo("Setting ReconfigureOnChangeTask scanning period to " + duration);

ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(rocTask,
duration.getMilliseconds(), duration.getMilliseconds(), TimeUnit.MILLISECONDS);
ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(rocTask, duration.getMilliseconds(), duration.getMilliseconds(),
TimeUnit.MILLISECONDS);
rocTask.setScheduredFuture(scheduledFuture);
context.addScheduledFuture(scheduledFuture);
}
Expand All @@ -99,5 +111,4 @@ private Duration getDurationOfScanPeriodAttribute(String scanPeriodAttrib, Durat
}
return duration;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
package ch.qos.logback.classic.model.processor;

import ch.qos.logback.classic.joran.PropertyConfigurator;
import ch.qos.logback.classic.model.PropertyConfiguratorModel;
import ch.qos.logback.classic.model.PropertiesConfiguratorModel;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
import ch.qos.logback.core.model.IncludeModel;
import ch.qos.logback.core.model.Model;
import ch.qos.logback.core.model.ResourceModel;
import ch.qos.logback.core.model.processor.ModelHandlerException;
Expand All @@ -30,20 +29,22 @@
import java.io.InputStream;
import java.net.URL;

public class PropertyConfiguratorModelHandler extends ResourceHandlerBase {
public class PropertiesConfiguratorModelHandler extends ResourceHandlerBase {
boolean inError = false;

public PropertyConfiguratorModelHandler(Context context) {
static final boolean CREATE_CWL_IF_NOT_ALREADY_CREATED = true;

public PropertiesConfiguratorModelHandler(Context context) {
super(context);
}

static public PropertyConfiguratorModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
return new PropertyConfiguratorModelHandler(context);
static public PropertiesConfiguratorModelHandler makeInstance(Context context, ModelInterpretationContext mic) {
return new PropertiesConfiguratorModelHandler(context);
}

@Override
public void handle(ModelInterpretationContext mic, Model model) throws ModelHandlerException {
PropertyConfiguratorModel propertyConfiguratorModel = (PropertyConfiguratorModel) model;
PropertiesConfiguratorModel propertyConfiguratorModel = (PropertiesConfiguratorModel) model;

this.optional = OptionHelper.toBoolean(propertyConfiguratorModel.getOptional(), false);

Expand Down Expand Up @@ -76,7 +77,7 @@ protected InputStream getInputStream(ModelInterpretationContext mic, ResourceMod
if (inputURL == null)
return null;

ConfigurationWatchListUtil.addToWatchList(context, inputURL);
ConfigurationWatchListUtil.addToWatchList(context, inputURL, CREATE_CWL_IF_NOT_ALREADY_CREATED);
return openURL(inputURL);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
* Reconfigure a LoggerContext when the configuration file changes.
*
* @author Ceki Gulcu
* @deprecated replaced by {@link ch.qos.logback.classic.joran.ReconfigureOnChangeTask}
*/
@Deprecated
public class ReconfigureOnChangeFilter extends TurboFilter {

/**
Expand Down Expand Up @@ -162,7 +164,8 @@ void updateNextCheck(long now) {
protected boolean changeDetected(long now) {
if (now >= nextCheck) {
updateNextCheck(now);
return configurationWatchList.changeDetected();
File file = configurationWatchList.changeDetected();
return file != null;
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

<variable name="JO_PREFIX" value="src/test/input/joran" />

<propertyConfigurator file="${JO_PREFIX}/propertyConfigurator/smoke.properties" />
<propertiesConfigurator file="${JO_PREFIX}/propertiesConfigurator/smoke.properties" />

<root level="debug">
<appender-ref ref="LIST" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,9 +694,9 @@ public void dateConverterWithLocale() throws JoranException {

@Test
public void propertyConfiguratorSmoke() throws JoranException {
configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "propertyConfigurator/smoke.xml");
configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "propertiesConfigurator/smoke.xml");
Logger com_foo_Logger = loggerContext.getLogger("com.toto");
StatusPrinter.print(loggerContext);
//StatusPrinter.print(loggerContext);
assertEquals(Level.WARN, com_foo_Logger.getLevel());


Expand Down
Loading

0 comments on commit 4f9fb1f

Please sign in to comment.