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 static ch.qos.logback.core.model.ModelConstants.DEBUG_SYSTEM_PROPERTY_KEY; 017import static ch.qos.logback.core.model.ModelConstants.NULL_STR; 018import static java.lang.Boolean.FALSE; 019 020import java.net.URL; 021import java.util.concurrent.ScheduledExecutorService; 022import java.util.concurrent.ScheduledFuture; 023import java.util.concurrent.TimeUnit; 024 025import ch.qos.logback.classic.LoggerContext; 026import ch.qos.logback.classic.joran.ReconfigureOnChangeTask; 027import ch.qos.logback.classic.model.ConfigurationModel; 028import ch.qos.logback.core.Context; 029import ch.qos.logback.core.CoreConstants; 030import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil; 031import ch.qos.logback.core.model.Model; 032import ch.qos.logback.core.model.processor.ModelHandlerBase; 033import ch.qos.logback.core.model.processor.ModelInterpretationContext; 034import ch.qos.logback.core.status.OnConsoleStatusListener; 035import ch.qos.logback.core.util.ContextUtil; 036import ch.qos.logback.core.util.Duration; 037import ch.qos.logback.core.util.OptionHelper; 038import ch.qos.logback.core.util.StatusListenerConfigHelper; 039 040public class ConfigurationModelHandler extends ModelHandlerBase { 041 042 static final Duration SCAN_PERIOD_DEFAULT = Duration.buildByMinutes(1); 043 044 public ConfigurationModelHandler(Context context) { 045 super(context); 046 } 047 048 static public ModelHandlerBase makeInstance(Context context, ModelInterpretationContext mic) { 049 return new ConfigurationModelHandler(context); 050 } 051 052 protected Class<ConfigurationModel> getSupportedModelClass() { 053 return ConfigurationModel.class; 054 } 055 056 @Override 057 public void handle(ModelInterpretationContext mic, Model model) { 058 059 ConfigurationModel configurationModel = (ConfigurationModel) model; 060 061 // See LOGBACK-527 (the system property is looked up first). Thus, it overrides 062 // the equivalent property in the config file. This reversal of scope priority 063 // is justified 064 // by the use case: the admin trying to chase rogue config file 065 String debugAttrib = OptionHelper.getSystemProperty(DEBUG_SYSTEM_PROPERTY_KEY, null); 066 if (debugAttrib == null) { 067 debugAttrib = mic.subst(configurationModel.getDebugStr()); 068 } 069 070 071 if (!(OptionHelper.isNullOrEmpty(debugAttrib) || debugAttrib.equalsIgnoreCase(FALSE.toString()) 072 || debugAttrib.equalsIgnoreCase(NULL_STR))) { 073 StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener()); 074 } 075 076 processScanAttrib(mic, configurationModel); 077 078 LoggerContext lc = (LoggerContext) context; 079 boolean packagingData = OptionHelper.toBoolean(mic.subst(configurationModel.getPackagingDataStr()), 080 LoggerContext.DEFAULT_PACKAGING_DATA); 081 lc.setPackagingDataEnabled(packagingData); 082 083 ContextUtil contextUtil = new ContextUtil(context); 084 contextUtil.addGroovyPackages(lc.getFrameworkPackages()); 085 } 086 087 void processScanAttrib(ModelInterpretationContext mic, ConfigurationModel configurationModel) { 088 String scanStr = mic.subst(configurationModel.getScanStr()); 089 if (!OptionHelper.isNullOrEmpty(scanStr) && !"false".equalsIgnoreCase(scanStr)) { 090 091 ScheduledExecutorService scheduledExecutorService = context.getScheduledExecutorService(); 092 URL mainURL = ConfigurationWatchListUtil.getMainWatchURL(context); 093 if (mainURL == null) { 094 addWarn("Due to missing top level configuration file, reconfiguration on change (configuration file scanning) cannot be done."); 095 return; 096 } 097 ReconfigureOnChangeTask rocTask = new ReconfigureOnChangeTask(); 098 rocTask.setContext(context); 099 100 addInfo("Registering a new ReconfigureOnChangeTask "+ rocTask); 101 context.putObject(CoreConstants.RECONFIGURE_ON_CHANGE_TASK, rocTask); 102 103 String scanPeriodStr = mic.subst(configurationModel.getScanPeriodStr()); 104 Duration duration = getDurationOfScanPeriodAttribute(scanPeriodStr, SCAN_PERIOD_DEFAULT); 105 106 addInfo("Will scan for changes in [" + mainURL + "] "); 107 // Given that included files are encountered at a later phase, the complete list 108 // of files 109 // to scan can only be determined when the configuration is loaded in full. 110 // However, scan can be active if mainURL is set. Otherwise, when changes are 111 // detected 112 // the top level config file cannot be accessed. 113 addInfo("Setting ReconfigureOnChangeTask scanning period to " + duration); 114 115 ScheduledFuture<?> scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(rocTask, 116 duration.getMilliseconds(), duration.getMilliseconds(), TimeUnit.MILLISECONDS); 117 context.addScheduledFuture(scheduledFuture); 118 } 119 } 120 121 private Duration getDurationOfScanPeriodAttribute(String scanPeriodAttrib, Duration defaultDuration) { 122 Duration duration = null; 123 124 if (!OptionHelper.isNullOrEmpty(scanPeriodAttrib)) { 125 try { 126 duration = Duration.valueOf(scanPeriodAttrib); 127 } catch (IllegalStateException | IllegalArgumentException e) { 128 addWarn("Failed to parse 'scanPeriod' attribute [" + scanPeriodAttrib + "]", e); 129 // default duration will be set below 130 } 131 } 132 133 if (duration == null) { 134 addInfo("No 'scanPeriod' specified. Defaulting to " + defaultDuration.toString()); 135 duration = defaultDuration; 136 } 137 return duration; 138 } 139 140}