From eaf383788e8dc8e3592ae39328e92334c699d801 Mon Sep 17 00:00:00 2001 From: "Achtenberg, Alan" Date: Tue, 1 Mar 2016 17:36:14 -0600 Subject: [PATCH] LOG4J2-1301 added optionalMonitorFiles attribute for automatic reconfiguration --- .../core/config/xml/XmlConfiguration.java | 45 +++++- .../config/xml/XmlReconfigurationTest.java | 146 ++++++++++++++++++ .../LOG4J2-1301/log4j2-reconfiguration-1.xml | 30 ++++ .../LOG4J2-1301/log4j2-reconfiguration-2.xml | 31 ++++ .../LOG4J2-1301/log4j2-reconfiguration-3.xml | 31 ++++ .../log4j2-reconfiguration-bad-filename.xml | 31 ++++ .../log4j2-reconfiguration-bad-separator.xml | 31 ++++ 7 files changed, 338 insertions(+), 7 deletions(-) create mode 100644 log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlReconfigurationTest.java create mode 100644 log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-1.xml create mode 100644 log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-2.xml create mode 100644 log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-3.xml create mode 100644 log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-filename.xml create mode 100644 log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-separator.xml diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java index 4369b89a5f1..45b7c648ea3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java @@ -16,10 +16,7 @@ */ package org.apache.logging.log4j.core.config.xml; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -67,7 +64,7 @@ public class XmlConfiguration extends AbstractConfiguration implements Reconfigu "http://apache.org/xml/features/xinclude/fixup-language"; private static final String XINCLUDE_FIXUP_BASE_URIS = "http://apache.org/xml/features/xinclude/fixup-base-uris"; - private static final String[] VERBOSE_CLASSES = new String[] {ResolverUtil.class.getName()}; + private static final String[] VERBOSE_CLASSES = new String[]{ResolverUtil.class.getName()}; private static final String LOG4J_XSD = "Log4j-config.xsd"; private final List status = new ArrayList<>(); @@ -99,7 +96,7 @@ public XmlConfiguration(final ConfigurationSource configSource) { if (throwable instanceof UnsupportedOperationException) { LOGGER.warn( "The DocumentBuilder {} does not support an operation: {}." - + "Trying again without XInclude...", + + "Trying again without XInclude...", documentBuilder, e); document = newDocumentBuilder(false).parse(source); } else { @@ -136,6 +133,9 @@ public XmlConfiguration(final ConfigurationSource configSource) { if (configFile != null) { FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners); getWatchManager().watchFile(configFile, watcher); + for (File file : getOptionalWatchFiles(configFile.getParent(), attrs)) { + getWatchManager().watchFile(file, watcher); + } } } } else if ("advertiser".equalsIgnoreCase(key)) { @@ -145,6 +145,8 @@ public XmlConfiguration(final ConfigurationSource configSource) { statusConfig.initialize(); } catch (final SAXException domEx) { LOGGER.error("Error parsing {}", configSource.getLocation(), domEx); + } catch (final FileNotFoundException fnfe) { + LOGGER.error("Error parsing {}", configSource.getLocation(), fnfe); } catch (final IOException ioe) { LOGGER.error("Error parsing {}", configSource.getLocation(), ioe); } catch (final ParserConfigurationException pex) { @@ -184,9 +186,38 @@ public XmlConfiguration(final ConfigurationSource configSource) { } } + /** + * Retrieves a comma separated list of files to monitor for automatic reconfiguration. + * File names must be a valid file and already exist. No restrictions on fileType. + * + * @param baseDir baseDir to search for files + * @param attributeMap map containing optionalMonitorFiles key and value + * @return a new DocumentBuilder + * @throws ParserConfigurationException + */ + public List getOptionalWatchFiles(String baseDir, Map attributeMap) throws FileNotFoundException { + final String attr_name = "optionalMonitorFiles"; //attr name consistent with monitorInterval + final List fileList = new ArrayList<>(); + final String rawFileNames = attributeMap.get(attr_name); + if (rawFileNames == null) { + return fileList; + } + for (String fileName : rawFileNames.split(Patterns.COMMA_SEPARATOR)) { + final String absoluteFileName = baseDir + File.separator + fileName.trim(); + File optionalWatchFile = new File(absoluteFileName); + if (optionalWatchFile.exists()) { + fileList.add(optionalWatchFile); + } else { + LOGGER.error("optionalMonitorFile: {} was not found or is not a valid file", absoluteFileName); + throw new FileNotFoundException(absoluteFileName); + } + } + return fileList; + } + /** * Creates a new DocumentBuilder suitable for parsing a configuration file. - * + * * @param xIncludeAware enabled XInclude * @return a new DocumentBuilder * @throws ParserConfigurationException diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlReconfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlReconfigurationTest.java new file mode 100644 index 00000000000..e72918eddc2 --- /dev/null +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/xml/XmlReconfigurationTest.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ + +package org.apache.logging.log4j.core.config.xml; + +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.ConfigurationFactory; +import org.apache.logging.log4j.core.config.ConfigurationListener; +import org.apache.logging.log4j.core.config.Reconfigurable; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertTrue; + +/** + * Tests automatic reconfiguration of xml + */ +public class XmlReconfigurationTest { + private static final String BASE_DIR = "src/test/resources/"; + private static final String SUB_DIR = BASE_DIR + "LOG4J2-1301/"; + private static final int WATCH_DELAY_MS = 1200; + + private static File configFile; + private static File appendersFile; + private static File loggersFile; + + private static int reconfigurationCount = 0; + + private static LoggerContext ctx; + + public XmlReconfigurationTest() { + } + + @BeforeClass + public static void setUpClass() { + appendersFile = new File(BASE_DIR + "log4j-xinclude-appenders.xml"); + loggersFile = new File(BASE_DIR + "log4j-xinclude-loggers.xml"); + } + + @Before + public void setUp() { + reconfigurationCount = 0; + } + @After + public void cleanUp(){ + this.ctx.stop(); + } + + public void setUpContext(String configFileName) { + System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, + SUB_DIR + configFileName); + final LoggerContext context = LoggerContext.getContext(false); + context.reconfigure(); + this.ctx = context; + this.configFile = new File(SUB_DIR + configFileName); + } + + public void testVanillaReconfiguration() throws Exception{ + assertTrue(reconfigurationCount == 0); + modifyFileAndListenForChange(configFile); + assertTrue(reconfigurationCount == 1); + modifyFileAndListenForChange(configFile); + assertTrue(reconfigurationCount == 2); + } + + @Test + public void testWithoutOptionalFiles() throws Exception { + setUpContext("log4j2-reconfiguration-1.xml"); + + testVanillaReconfiguration(); + + modifyFileAndListenForChange(appendersFile); + assertTrue(reconfigurationCount == 2); + modifyFileAndListenForChange(loggersFile); + assertTrue(reconfigurationCount == 2); + } + + @Test + public void testWithSingleOptionalFile() throws Exception { + setUpContext("log4j2-reconfiguration-2.xml"); + testVanillaReconfiguration(); + + modifyFileAndListenForChange(appendersFile);//not watched file, should not trigger reconfig + assertTrue(reconfigurationCount == 2); + + modifyFileAndListenForChange(loggersFile); + assertTrue(reconfigurationCount == 3); + } + + @Test + public void testWithTwoOptionalFiles() throws Exception { + setUpContext("log4j2-reconfiguration-3.xml"); + testVanillaReconfiguration(); + + modifyFileAndListenForChange(appendersFile); + assertTrue(reconfigurationCount == 3); + + modifyFileAndListenForChange(loggersFile); + assertTrue(reconfigurationCount == 4); + } + + @Test + public void testWithBadOptionalFileName() throws Exception { + setUpContext("log4j2-reconfiguration-bad-filename.xml"); + testVanillaReconfiguration();//bad value for optionalMonitorFiles should not break normal reconfiguration. + } + + public void testWithBadFileSeparator() throws Exception { + setUpContext("log4j2-reconfiguration-bad-separator.xml"); + testVanillaReconfiguration();//bad value for optionalMonitorFiles should not break normal reconfiguration. + } + + + private void modifyFileAndListenForChange(File file) throws InterruptedException { + ctx.getConfiguration().removeListener(configurationListener);//in case reconfig does not occur + ctx.getConfiguration().addListener(configurationListener); + file.setLastModified(System.currentTimeMillis()); + Thread.sleep(WATCH_DELAY_MS); + } + + private ConfigurationListener configurationListener = new ConfigurationListener() { + @Override + public void onChange(Reconfigurable reconfigurable) { + ++reconfigurationCount; + } + }; + +} diff --git a/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-1.xml b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-1.xml new file mode 100644 index 00000000000..f545bc4fdef --- /dev/null +++ b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-1.xml @@ -0,0 +1,30 @@ + + + + + + + + target/test-xinclude.log + + + + + diff --git a/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-2.xml b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-2.xml new file mode 100644 index 00000000000..956cfa19e0d --- /dev/null +++ b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-2.xml @@ -0,0 +1,31 @@ + + + + + + + + target/test-xinclude.log + + + + + diff --git a/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-3.xml b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-3.xml new file mode 100644 index 00000000000..c3b9197abc1 --- /dev/null +++ b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-3.xml @@ -0,0 +1,31 @@ + + + + + + + + target/test-xinclude.log + + + + + diff --git a/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-filename.xml b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-filename.xml new file mode 100644 index 00000000000..0c4bcea9e1a --- /dev/null +++ b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-filename.xml @@ -0,0 +1,31 @@ + + + + + + + + target/test-xinclude.log + + + + + diff --git a/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-separator.xml b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-separator.xml new file mode 100644 index 00000000000..d3018a0073d --- /dev/null +++ b/log4j-core/src/test/resources/LOG4J2-1301/log4j2-reconfiguration-bad-separator.xml @@ -0,0 +1,31 @@ + + + + + + + + target/test-xinclude.log + + + + +