From de15d2d84f56e0f494394950fbe60717f26e19fe Mon Sep 17 00:00:00 2001 From: MichaelMorris Date: Wed, 19 Feb 2025 11:17:26 +0000 Subject: [PATCH 1/4] Add option to provide URIs to monitor in addition to the config file A change detected in either the config file itself or the provided additional URIs shall result in a reconfigure Signed-off-by: MichaelMorris --- .../apache/logging/log4j/core/LoggerTest.java | 24 +++++++++++++ .../log4j/core/PropertiesFileConfigTest.java | 21 +++++++++++ .../src/test/resources/log4j-test2.properties | 1 + .../src/test/resources/log4j-test2.xml | 2 +- .../core/config/AbstractConfiguration.java | 10 +++++- .../core/config/ConfigurationFileWatcher.java | 35 +++++++++++++++++-- .../builder/api/ConfigurationBuilder.java | 9 +++++ .../core/config/builder/api/package-info.java | 2 +- .../builder/impl/BuiltConfiguration.java | 27 +++++++++++++- .../impl/DefaultConfigurationBuilder.java | 13 ++++++- .../config/builder/impl/package-info.java | 2 +- .../core/config/composite/package-info.java | 2 +- .../core/config/json/JsonConfiguration.java | 11 +++++- .../log4j/core/config/json/package-info.java | 2 +- .../log4j/core/config/package-info.java | 2 +- .../PropertiesConfigurationBuilder.java | 2 ++ .../core/config/properties/package-info.java | 2 +- .../core/config/xml/XmlConfiguration.java | 14 ++++++-- .../log4j/core/config/xml/package-info.java | 2 +- .../log4j/core/config/yaml/package-info.java | 2 +- .../logging/log4j/core/util/WatchManager.java | 21 +++++++++++ .../.2.x.x/3074_monitor_additional_files.xml | 8 +++++ 22 files changed, 197 insertions(+), 17 deletions(-) create mode 100644 src/changelog/.2.x.x/3074_monitor_additional_files.xml diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java index bcd8d7bd26f..4da29b26a1e 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java @@ -544,6 +544,30 @@ void testReconfiguration(final LoggerContext context) throws Exception { assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); } + @Test + void testReconfigurationMonitorUris(final LoggerContext context) throws Exception { + final Configuration oldConfig = context.getConfiguration(); + final int MONITOR_INTERVAL_SECONDS = 5; + final File file = new File("target/test-classes/org/apache/logging/log4j/core/net/ssl/keyStore.p12"); + final long orig = file.lastModified(); + final long newTime = orig + 10000; + assertTrue(file.setLastModified(newTime), "setLastModified should have succeeded."); + TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); + for (int i = 0; i < 17; ++i) { + logger.debug("Reconfigure"); + } + Thread.sleep(100); + for (int i = 0; i < 20; i++) { + if (context.getConfiguration() != oldConfig) { + break; + } + Thread.sleep(50); + } + final Configuration newConfig = context.getConfiguration(); + assertNotNull(newConfig, "No configuration"); + assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); + } + @Test void testSuppressedThrowable(final LoggerContext context) { final org.apache.logging.log4j.Logger testLogger = context.getLogger("org.apache.logging.log4j.nothrown"); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java index 4a4bf8dba0a..c10910bb751 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java @@ -64,4 +64,25 @@ void testReconfiguration(final LoggerContext context) throws Exception { } while (newConfig == oldConfig && loopCount++ < 5); assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); } + + @Test + void testReconfigurationMonitorUris(final LoggerContext context) throws Exception { + final Configuration oldConfig = context.getConfiguration(); + final int MONITOR_INTERVAL_SECONDS = 5; + final File file = new File("target/test-classes/org/apache/logging/log4j/core/net/ssl/keyStore.p12"); + final long orig = file.lastModified(); + final long newTime = orig + 10000; + assertTrue(file.setLastModified(newTime), "setLastModified should have succeeded."); + TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); + for (int i = 0; i < 17; ++i) { + logger.info("Reconfigure"); + } + int loopCount = 0; + Configuration newConfig; + do { + Thread.sleep(100); + newConfig = context.getConfiguration(); + } while (newConfig == oldConfig && loopCount++ < 5); + assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); + } } diff --git a/log4j-core-test/src/test/resources/log4j-test2.properties b/log4j-core-test/src/test/resources/log4j-test2.properties index d463dd69688..fa264acfe95 100644 --- a/log4j-core-test/src/test/resources/log4j-test2.properties +++ b/log4j-core-test/src/test/resources/log4j-test2.properties @@ -18,6 +18,7 @@ status = debug name = PropertiesConfigTest monitorInterval = 1 +monitorUris = target/test-classes/org/apache/logging/log4j/core/net/ssl/keyStore.p12 property.filename = target/test-properties.log diff --git a/log4j-core-test/src/test/resources/log4j-test2.xml b/log4j-core-test/src/test/resources/log4j-test2.xml index 3300d56400b..e01b928183b 100644 --- a/log4j-core-test/src/test/resources/log4j-test2.xml +++ b/log4j-core-test/src/test/resources/log4j-test2.xml @@ -15,7 +15,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index 5b60a7727b3..7748a709109 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -275,6 +275,14 @@ protected void initializeWatchers( final Reconfigurable reconfigurable, final ConfigurationSource configSource, final int monitorIntervalSeconds) { + initializeWatchers(reconfigurable, configSource, Collections.emptySet(), monitorIntervalSeconds); + } + + protected void initializeWatchers( + final Reconfigurable reconfigurable, + final ConfigurationSource configSource, + final Collection auxiliarySources, + final int monitorIntervalSeconds) { if (configSource != null && (configSource.getFile() != null || configSource.getURL() != null)) { if (monitorIntervalSeconds > 0) { watchManager.setIntervalSeconds(monitorIntervalSeconds); @@ -284,7 +292,7 @@ protected void initializeWatchers( final long lastModified = file.lastModified(); final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable, listeners, lastModified); - watchManager.watch(cfgSource, watcher); + watchManager.watch(cfgSource, auxiliarySources, watcher); } else if (configSource.getURL() != null) { monitorSource(reconfigurable, configSource); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java index 9b920a45241..90d796f66ba 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java @@ -17,7 +17,10 @@ package org.apache.logging.log4j.core.config; import java.io.File; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.logging.log4j.core.util.AbstractWatcher; import org.apache.logging.log4j.core.util.FileWatcher; import org.apache.logging.log4j.core.util.Source; @@ -30,6 +33,7 @@ public class ConfigurationFileWatcher extends AbstractWatcher implements FileWat private File file; private long lastModifiedMillis; + private Map auxiliaryFiles = new HashMap<>(); public ConfigurationFileWatcher( final Configuration configuration, @@ -42,12 +46,23 @@ public ConfigurationFileWatcher( @Override public long getLastModified() { - return file != null ? file.lastModified() : 0; + Long latestModifiedAuxFile = 0L; + for (final File auxFile : auxiliaryFiles.keySet()) { + if (auxFile.lastModified() > latestModifiedAuxFile) { + latestModifiedAuxFile = auxFile.lastModified(); + } + } + + return file != null + ? file.lastModified() > latestModifiedAuxFile ? file.lastModified() : latestModifiedAuxFile + : 0; } @Override public void fileModified(final File file) { lastModifiedMillis = file.lastModified(); + auxiliaryFiles.entrySet().stream() + .forEach(auxFile -> auxFile.setValue(auxFile.getKey().lastModified())); } @Override @@ -57,9 +72,25 @@ public void watching(final Source source) { super.watching(source); } + /** + * Called when the Watcher is registered. + * @param source the Source that is being watched. + * @param auxiliarySources auxiliary sources also being watched. + */ + public void watching(final Source source, final Collection auxiliarySources) { + file = source.getFile(); + lastModifiedMillis = file.lastModified(); + auxiliarySources.forEach(auxSource -> { + auxiliaryFiles.put(auxSource.getFile(), auxSource.getFile().lastModified()); + }); + super.watching(source); + } + @Override public boolean isModified() { - return lastModifiedMillis != file.lastModified(); + return lastModifiedMillis != file.lastModified() + || auxiliaryFiles.entrySet().stream() + .anyMatch(file -> file.getValue() != file.getKey().lastModified()); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java index 3b9fc489614..7f0115f3cf4 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java @@ -418,6 +418,15 @@ public interface ConfigurationBuilder extends Builder setMonitorInterval(String intervalSeconds); + /** + * Set the URIs to be monitored in addition to the configuration file + * @param monitorUris the URIs to monitor + * @return this builder instance + */ + default ConfigurationBuilder setMonitorUris(final String monitorUris) { + return this; + } + /** * Sets the list of packages to search for plugins. * @param packages The comma separated list of packages. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java index 273906a5d48..2f63a9e78c3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java @@ -20,7 +20,7 @@ * @since 2.4 */ @Export -@Version("2.20.1") +@Version("2.21.0") package org.apache.logging.log4j.core.config.builder.api; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java index f1ec35e06a0..61a3f0fdc50 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java @@ -18,7 +18,11 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; @@ -30,6 +34,8 @@ import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.core.util.Source; +import org.apache.logging.log4j.util.Strings; /** * This is the general version of the Configuration created by the Builder. It may be extended to @@ -148,9 +154,28 @@ public void setShutdownTimeoutMillis(final long shutdownTimeoutMillis) { } public void setMonitorInterval(final int intervalSeconds) { + initializeMonitoring(intervalSeconds, ""); + } + + public void initializeMonitoring(final int intervalSeconds, final String monitorUris) { if (this instanceof Reconfigurable && intervalSeconds > 0) { - initializeWatchers((Reconfigurable) this, getConfigurationSource(), intervalSeconds); + initializeWatchers( + (Reconfigurable) this, getConfigurationSource(), getAuxiliarySources(monitorUris), intervalSeconds); + } + } + + private Collection getAuxiliarySources(final String monitorUris) { + final Collection auxiliarySources = new HashSet<>(); + if (Strings.isNotBlank(monitorUris)) { + for (final String uri : Arrays.asList(monitorUris.split(Patterns.COMMA_SEPARATOR))) { + try { + auxiliarySources.add(new Source(new URI(uri))); + } catch (URISyntaxException e) { + LOGGER.error("Error parsing monitorUris: " + monitorUris, e); + } + } } + return auxiliarySources; } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java index 633e619b281..fd77afc49d2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java @@ -61,6 +61,7 @@ import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder; import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Throwables; +import org.apache.logging.log4j.util.Strings; /** * @param The BuiltConfiguration type. @@ -80,6 +81,7 @@ public class DefaultConfigurationBuilder implement private final Class clazz; private ConfigurationSource source; private int monitorInterval; + private String monitorUris; private Level level; private String destination; private String packages; @@ -214,7 +216,7 @@ public T build(final boolean initialize) { if (advertiser != null) { configuration.createAdvertiser(advertiser, source); } - configuration.setMonitorInterval(monitorInterval); + configuration.initializeMonitoring(monitorInterval, monitorUris); } catch (final Exception ex) { throw new IllegalArgumentException("Invalid Configuration class specified", ex); } @@ -287,6 +289,9 @@ private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLSt if (monitorInterval > 0) { xmlWriter.writeAttribute("monitorInterval", String.valueOf(monitorInterval)); } + if (Strings.isNotBlank(monitorUris)) { + xmlWriter.writeAttribute("monitorUris", monitorUris); + } writeXmlSection(xmlWriter, properties); writeXmlSection(xmlWriter, scripts); @@ -565,6 +570,12 @@ public ConfigurationBuilder setMonitorInterval(final String intervalSeconds) return this; } + @Override + public ConfigurationBuilder setMonitorUris(final String monitorUris) { + this.monitorUris = monitorUris; + return this; + } + @Override public ConfigurationBuilder setPackages(final String packages) { this.packages = packages; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java index c56f92230f3..4c630135252 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java @@ -20,7 +20,7 @@ * @since 2.4 */ @Export -@Version("2.20.2") +@Version("2.21.0") package org.apache.logging.log4j.core.config.builder.impl; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java index 787cf180434..dd5accfeeea 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java @@ -19,7 +19,7 @@ * Support for composite configurations. */ @Export -@Version("2.20.1") +@Version("2.21.0") package org.apache.logging.log4j.core.config.composite; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java index b574070a433..6076088c48e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java @@ -23,8 +23,11 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -39,6 +42,7 @@ import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.core.util.Source; /** * Creates a Node hierarchy from a JSON file. @@ -66,6 +70,7 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS processAttributes(rootNode, root); final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(getDefaultStatus()); int monitorIntervalSeconds = 0; + Collection auxiliarySources = new HashSet<>(); for (final Map.Entry entry : rootNode.getAttributes().entrySet()) { final String key = entry.getKey(); @@ -87,9 +92,13 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS monitorIntervalSeconds = Integers.parseInt(value); } else if ("advertiser".equalsIgnoreCase(key)) { createAdvertiser(value, configSource, buffer, "application/json"); + } else if ("monitorUris".equalsIgnoreCase(key)) { + for (final String uri : Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))) { + auxiliarySources.add(new Source(new URI(uri))); + } } } - initializeWatchers(this, configSource, monitorIntervalSeconds); + initializeWatchers(this, configSource, auxiliarySources, monitorIntervalSeconds); statusConfig.initialize(); if (getName() == null) { setName(configSource.getLocation()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java index 9d9e5d60eb3..5615f2cc73d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java @@ -18,7 +18,7 @@ * Classes and interfaces supporting configuration of Log4j 2 with JSON. */ @Export -@Version("2.20.1") +@Version("2.21.0") package org.apache.logging.log4j.core.config.json; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java index 111d1644f68..3db1c7abd6b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/package-info.java @@ -18,7 +18,7 @@ * Configuration of Log4j 2. */ @Export -@Version("2.24.1") +@Version("2.25.0") package org.apache.logging.log4j.core.config; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java index 6dc9fd6957a..594b15dfd4f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java @@ -61,6 +61,7 @@ public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory private static final String CONFIG_NAME = "name"; private static final String MONITOR_INTERVAL = "monitorInterval"; private static final String CONFIG_TYPE = "type"; + private static final String MONITOR_URIS = "monitorUris"; private final ConfigurationBuilder builder; private LoggerContext loggerContext; @@ -95,6 +96,7 @@ public PropertiesConfiguration build() { .setPackages(rootProperties.getProperty(PACKAGES)) .setConfigurationName(rootProperties.getProperty(CONFIG_NAME)) .setMonitorInterval(rootProperties.getProperty(MONITOR_INTERVAL, "0")) + .setMonitorUris(rootProperties.getProperty(MONITOR_URIS)) .setAdvertiser(rootProperties.getProperty(ADVERTISER_KEY)); final Properties propertyPlaceholders = PropertiesUtil.extractSubset(rootProperties, "property"); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java index 1563a62e0ec..57419616d65 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java @@ -18,7 +18,7 @@ * Configuration using Properties files. */ @Export -@Version("2.20.1") +@Version("2.21.0") package org.apache.logging.log4j.core.config.properties; import org.osgi.annotation.bundle.Export; 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 6caeec59ae5..1d86382f8b7 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 @@ -20,9 +20,13 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import javax.xml.XMLConstants; @@ -45,6 +49,7 @@ import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.Patterns; +import org.apache.logging.log4j.core.util.Source; import org.apache.logging.log4j.core.util.Throwables; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -106,6 +111,7 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo final Map attrs = processAttributes(rootNode, rootElement); final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(getDefaultStatus()); int monitorIntervalSeconds = 0; + Collection auxiliarySources = new HashSet<>(); for (final Map.Entry entry : attrs.entrySet()) { final String key = entry.getKey(); final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); @@ -129,11 +135,15 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo monitorIntervalSeconds = Integers.parseInt(value); } else if ("advertiser".equalsIgnoreCase(key)) { createAdvertiser(value, configSource, buffer, "text/xml"); + } else if ("monitorUris".equalsIgnoreCase(key)) { + for (final String uri : Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))) { + auxiliarySources.add(new Source(new URI(uri))); + } } } - initializeWatchers(this, configSource, monitorIntervalSeconds); + initializeWatchers(this, configSource, auxiliarySources, monitorIntervalSeconds); statusConfig.initialize(); - } catch (final SAXException | IOException | ParserConfigurationException e) { + } catch (final SAXException | IOException | ParserConfigurationException | URISyntaxException e) { LOGGER.error("Error parsing " + configSource.getLocation(), e); } if (strict && schemaResource != null && buffer != null) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java index f0a6c9d7e4f..79858cbdfd6 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java @@ -18,7 +18,7 @@ * Classes and interfaces supporting configuration of Log4j 2 with XML. */ @Export -@Version("2.20.2") +@Version("2.21.0") package org.apache.logging.log4j.core.config.xml; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java index c476f1d82c1..ae6af3f58b7 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java @@ -18,7 +18,7 @@ * Classes and interfaces supporting configuration of Log4j 2 with YAML. */ @Export -@Version("2.20.1") +@Version("2.21.0") package org.apache.logging.log4j.core.config.yaml; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java index 3f2856340fd..d3e7133def2 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java @@ -21,6 +21,7 @@ import aQute.bnd.annotation.spi.ServiceConsumer; import java.io.File; import java.time.Instant; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -354,6 +355,26 @@ public void watch(final Source source, final Watcher watcher) { watchers.put(source, new ConfigurationMonitor(lastModified, watcher)); } + /** + * Watches the given file. + * + * @param source the source to watch. + * @param auxiliarySources auxiliary sources to also watch + * @param watcher the watcher to notify of file changes. + */ + public void watch( + final Source source, final Collection auxiliarySources, final ConfigurationFileWatcher watcher) { + watcher.watching(source, auxiliarySources); + final long lastModified = watcher.getLastModified(); + if (logger.isDebugEnabled()) { + String message = auxiliarySources.isEmpty() + ? "Watching configuration '{}' for lastModified {} ({})" + : "Watching configuration '{}' for lastModified {} ({}) and files {}"; + logger.debug(message, source, millisToString(lastModified), lastModified); + } + watchers.put(source, new ConfigurationMonitor(lastModified, watcher)); + } + /** * Watches the given file. * diff --git a/src/changelog/.2.x.x/3074_monitor_additional_files.xml b/src/changelog/.2.x.x/3074_monitor_additional_files.xml new file mode 100644 index 00000000000..dcc2d028def --- /dev/null +++ b/src/changelog/.2.x.x/3074_monitor_additional_files.xml @@ -0,0 +1,8 @@ + + + + Support configuration option to provide URIs to monitor in addition to the config file. + From 02d64a30b974b2a80e43802788711e7d8e22ca8b Mon Sep 17 00:00:00 2001 From: MichaelMorris Date: Thu, 20 Mar 2025 15:12:46 +0000 Subject: [PATCH 2/4] Reworked to to make monitorUris a dedicated element instead of an attribute of the Configuration element Signed-off-by: MichaelMorris --- .../apache/logging/log4j/core/LoggerTest.java | 24 ----- .../logging/log4j/core/MonitorUriTest.java | 61 +++++++++++++ .../log4j/core/PropertiesFileConfigTest.java | 21 ----- .../src/test/resources/log4j-test2.properties | 1 - .../src/test/resources/log4j-test2.xml | 2 +- .../core/config/AbstractConfiguration.java | 50 ++++++++--- .../core/config/ConfigurationFileWatcher.java | 51 +++++------ .../core/config/ConfigurationSource.java | 4 + .../log4j/core/config/MonitorUris.java | 58 ++++++++++++ .../apache/logging/log4j/core/config/Uri.java | 88 +++++++++++++++++++ .../builder/api/ConfigurationBuilder.java | 27 ++++-- .../api/MonitorUriComponentBuilder.java | 22 +++++ .../core/config/builder/api/package-info.java | 2 +- .../builder/impl/BuiltConfiguration.java | 35 ++------ .../impl/DefaultConfigurationBuilder.java | 29 +++--- .../DefaultMonitorUriComponentBuilder.java | 33 +++++++ .../config/builder/impl/package-info.java | 2 +- .../core/config/composite/package-info.java | 2 +- .../core/config/json/JsonConfiguration.java | 11 +-- .../log4j/core/config/json/package-info.java | 2 +- .../PropertiesConfigurationBuilder.java | 17 +++- .../core/config/properties/package-info.java | 2 +- .../core/config/xml/XmlConfiguration.java | 14 +-- .../log4j/core/config/xml/package-info.java | 2 +- .../log4j/core/config/yaml/package-info.java | 2 +- .../logging/log4j/core/util/WatchManager.java | 35 +++----- 26 files changed, 406 insertions(+), 191 deletions(-) create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/MonitorUriTest.java create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorUris.java create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/config/Uri.java create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/MonitorUriComponentBuilder.java create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultMonitorUriComponentBuilder.java diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java index 4da29b26a1e..bcd8d7bd26f 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerTest.java @@ -544,30 +544,6 @@ void testReconfiguration(final LoggerContext context) throws Exception { assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); } - @Test - void testReconfigurationMonitorUris(final LoggerContext context) throws Exception { - final Configuration oldConfig = context.getConfiguration(); - final int MONITOR_INTERVAL_SECONDS = 5; - final File file = new File("target/test-classes/org/apache/logging/log4j/core/net/ssl/keyStore.p12"); - final long orig = file.lastModified(); - final long newTime = orig + 10000; - assertTrue(file.setLastModified(newTime), "setLastModified should have succeeded."); - TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); - for (int i = 0; i < 17; ++i) { - logger.debug("Reconfigure"); - } - Thread.sleep(100); - for (int i = 0; i < 20; i++) { - if (context.getConfiguration() != oldConfig) { - break; - } - Thread.sleep(50); - } - final Configuration newConfig = context.getConfiguration(); - assertNotNull(newConfig, "No configuration"); - assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); - } - @Test void testSuppressedThrowable(final LoggerContext context) { final org.apache.logging.log4j.Logger testLogger = context.getLogger("org.apache.logging.log4j.nothrown"); diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/MonitorUriTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/MonitorUriTest.java new file mode 100644 index 00000000000..20c519afa55 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/MonitorUriTest.java @@ -0,0 +1,61 @@ +/* + * 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; + +import static org.awaitility.Awaitility.waitAtMost; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.ConfigurationSource; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; +import org.apache.logging.log4j.core.config.properties.PropertiesConfiguration; +import org.apache.logging.log4j.core.util.Source; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +public class MonitorUriTest { + + private static final int MONITOR_INTERVAL = 3; + + @Test + void testReconfigureOnChangeInMonitorUri(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) + throws IOException { + ConfigurationBuilder configBuilder = + ConfigurationBuilderFactory.newConfigurationBuilder(PropertiesConfiguration.class); + Path config = tempDir.resolve("config.xml"); + Path monitorUri = tempDir.resolve("monitorUri.xml"); + ConfigurationSource configSource = new ConfigurationSource(new Source(config), new byte[] {}, 0); + Configuration configuration = configBuilder + .setConfigurationSource(configSource) + .setMonitorInterval(String.valueOf(MONITOR_INTERVAL)) + .add(configBuilder.newMonitorUri(monitorUri.toUri().toString())) + .build(); + + try (LoggerContext loggerContext = Configurator.initialize(configuration)) { + Files.write(monitorUri, Collections.singletonList("a change")); + waitAtMost(MONITOR_INTERVAL + 2, TimeUnit.SECONDS) + .until(() -> loggerContext.getConfiguration() != configuration); + } + } +} diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java index c10910bb751..4a4bf8dba0a 100644 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/PropertiesFileConfigTest.java @@ -64,25 +64,4 @@ void testReconfiguration(final LoggerContext context) throws Exception { } while (newConfig == oldConfig && loopCount++ < 5); assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); } - - @Test - void testReconfigurationMonitorUris(final LoggerContext context) throws Exception { - final Configuration oldConfig = context.getConfiguration(); - final int MONITOR_INTERVAL_SECONDS = 5; - final File file = new File("target/test-classes/org/apache/logging/log4j/core/net/ssl/keyStore.p12"); - final long orig = file.lastModified(); - final long newTime = orig + 10000; - assertTrue(file.setLastModified(newTime), "setLastModified should have succeeded."); - TimeUnit.SECONDS.sleep(MONITOR_INTERVAL_SECONDS + 1); - for (int i = 0; i < 17; ++i) { - logger.info("Reconfigure"); - } - int loopCount = 0; - Configuration newConfig; - do { - Thread.sleep(100); - newConfig = context.getConfiguration(); - } while (newConfig == oldConfig && loopCount++ < 5); - assertNotSame(newConfig, oldConfig, "Reconfiguration failed"); - } } diff --git a/log4j-core-test/src/test/resources/log4j-test2.properties b/log4j-core-test/src/test/resources/log4j-test2.properties index fa264acfe95..d463dd69688 100644 --- a/log4j-core-test/src/test/resources/log4j-test2.properties +++ b/log4j-core-test/src/test/resources/log4j-test2.properties @@ -18,7 +18,6 @@ status = debug name = PropertiesConfigTest monitorInterval = 1 -monitorUris = target/test-classes/org/apache/logging/log4j/core/net/ssl/keyStore.p12 property.filename = target/test-properties.log diff --git a/log4j-core-test/src/test/resources/log4j-test2.xml b/log4j-core-test/src/test/resources/log4j-test2.xml index e01b928183b..3300d56400b 100644 --- a/log4j-core-test/src/test/resources/log4j-test2.xml +++ b/log4j-core-test/src/test/resources/log4j-test2.xml @@ -15,7 +15,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index 7748a709109..a53dfe2581e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -132,6 +133,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement private ConcurrentMap appenders = new ConcurrentHashMap<>(); private ConcurrentMap loggerConfigs = new ConcurrentHashMap<>(); private List customLevels = Collections.emptyList(); + private List uris = Collections.emptyList(); private final ConcurrentMap propertyMap = new ConcurrentHashMap<>(); private final Interpolator tempLookup = new Interpolator(propertyMap); private final StrSubstitutor runtimeStrSubstitutor = new RuntimeStrSubstitutor(tempLookup); @@ -275,14 +277,6 @@ protected void initializeWatchers( final Reconfigurable reconfigurable, final ConfigurationSource configSource, final int monitorIntervalSeconds) { - initializeWatchers(reconfigurable, configSource, Collections.emptySet(), monitorIntervalSeconds); - } - - protected void initializeWatchers( - final Reconfigurable reconfigurable, - final ConfigurationSource configSource, - final Collection auxiliarySources, - final int monitorIntervalSeconds) { if (configSource != null && (configSource.getFile() != null || configSource.getURL() != null)) { if (monitorIntervalSeconds > 0) { watchManager.setIntervalSeconds(monitorIntervalSeconds); @@ -292,7 +286,7 @@ protected void initializeWatchers( final long lastModified = file.lastModified(); final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable, listeners, lastModified); - watchManager.watch(cfgSource, auxiliarySources, watcher); + watchManager.watch(cfgSource, watcher); } else if (configSource.getURL() != null) { monitorSource(reconfigurable, configSource); } @@ -331,10 +325,19 @@ public void start() { LOGGER.info("Starting configuration {}...", this); this.setStarting(); if (watchManager.getIntervalSeconds() >= 0) { - LOGGER.info( - "Start watching for changes to {} every {} seconds", - getConfigurationSource(), - watchManager.getIntervalSeconds()); + if (uris != null && uris.size() > 0) { + LOGGER.info( + "Start watching for changes to {} and {} every {} seconds", + getConfigurationSource(), + uris, + watchManager.getIntervalSeconds()); + watchManager.addMonitorUris(configurationSource.getSource(), uris); + } else { + LOGGER.info( + "Start watching for changes to {} every {} seconds", + getConfigurationSource(), + watchManager.getIntervalSeconds()); + } watchManager.start(); } if (hasAsyncLoggers()) { @@ -737,9 +740,16 @@ protected void doConfigure() { } else if (child.isInstanceOf(AsyncWaitStrategyFactoryConfig.class)) { final AsyncWaitStrategyFactoryConfig awsfc = child.getObject(AsyncWaitStrategyFactoryConfig.class); asyncWaitStrategyFactory = awsfc.createWaitStrategyFactory(); + } else if (child.isInstanceOf(MonitorUris.class)) { + uris = convertToJavaNetUris(child.getObject(MonitorUris.class).getUris()); } else { final List expected = Arrays.asList( - "\"Appenders\"", "\"Loggers\"", "\"Properties\"", "\"Scripts\"", "\"CustomLevels\""); + "\"Appenders\"", + "\"Loggers\"", + "\"Properties\"", + "\"Scripts\"", + "\"CustomLevels\"", + "\"MonitorUris\""); LOGGER.error( "Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.", child.getName(), @@ -775,6 +785,18 @@ protected void doConfigure() { setParents(); } + private List convertToJavaNetUris(final List uris) { + final List javaNetUris = new ArrayList<>(); + for (Uri uri : uris) { + try { + javaNetUris.add(new URI(uri.getUri())); + } catch (URISyntaxException e) { + LOGGER.error("Error parsing monitor URI: " + uri, e); + } + } + return javaNetUris; + } + public static Level getDefaultLevel() { final String levelName = PropertiesUtil.getProperties() .getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, Level.ERROR.name()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java index 90d796f66ba..0dfdb8582fe 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java @@ -17,7 +17,7 @@ package org.apache.logging.log4j.core.config; import java.io.File; -import java.util.Collection; +import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,9 +31,7 @@ */ public class ConfigurationFileWatcher extends AbstractWatcher implements FileWatcher { - private File file; - private long lastModifiedMillis; - private Map auxiliaryFiles = new HashMap<>(); + private Map monitoredFiles = new HashMap<>(); public ConfigurationFileWatcher( final Configuration configuration, @@ -41,56 +39,49 @@ public ConfigurationFileWatcher( final List configurationListeners, long lastModifiedMillis) { super(configuration, reconfigurable, configurationListeners); - this.lastModifiedMillis = lastModifiedMillis; } @Override public long getLastModified() { - Long latestModifiedAuxFile = 0L; - for (final File auxFile : auxiliaryFiles.keySet()) { - if (auxFile.lastModified() > latestModifiedAuxFile) { - latestModifiedAuxFile = auxFile.lastModified(); + Long lastModifiedMillis = 0L; + for (final File monitoredFile : monitoredFiles.keySet()) { + if (monitoredFile.lastModified() > lastModifiedMillis) { + lastModifiedMillis = monitoredFile.lastModified(); } } - - return file != null - ? file.lastModified() > latestModifiedAuxFile ? file.lastModified() : latestModifiedAuxFile - : 0; + return lastModifiedMillis; } @Override public void fileModified(final File file) { - lastModifiedMillis = file.lastModified(); - auxiliaryFiles.entrySet().stream() - .forEach(auxFile -> auxFile.setValue(auxFile.getKey().lastModified())); + monitoredFiles.entrySet().stream() + .forEach(monitoredFile -> + monitoredFile.setValue(monitoredFile.getKey().lastModified())); } @Override public void watching(final Source source) { - file = source.getFile(); - lastModifiedMillis = file.lastModified(); + File file = source.getFile(); + monitoredFiles.put(file, file.lastModified()); super.watching(source); } /** - * Called when the Watcher is registered. - * @param source the Source that is being watched. - * @param auxiliarySources auxiliary sources also being watched. + * Add the given URIs to be watched. + * + * @param monitorUris URIs to also watch */ - public void watching(final Source source, final Collection auxiliarySources) { - file = source.getFile(); - lastModifiedMillis = file.lastModified(); - auxiliarySources.forEach(auxSource -> { - auxiliaryFiles.put(auxSource.getFile(), auxSource.getFile().lastModified()); + public void addMonitorUris(final List monitorUris) { + monitorUris.forEach(uri -> { + File additionalFile = new Source(uri).getFile(); + monitoredFiles.put(additionalFile, additionalFile.lastModified()); }); - super.watching(source); } @Override public boolean isModified() { - return lastModifiedMillis != file.lastModified() - || auxiliaryFiles.entrySet().stream() - .anyMatch(file -> file.getValue() != file.getKey().lastModified()); + return monitoredFiles.entrySet().stream() + .anyMatch(file -> file.getValue() != file.getKey().lastModified()); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java index d6143cdc158..ecef1c831fa 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java @@ -382,4 +382,8 @@ public String toString() { return null; } } + + Source getSource() { + return this.source; + } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorUris.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorUris.java new file mode 100644 index 00000000000..d294e8fa083 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/MonitorUris.java @@ -0,0 +1,58 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; + +/** + * Container for MonitorUri objects. + */ +@Plugin(name = "MonitorUris", category = Core.CATEGORY_NAME, printObject = true) +public final class MonitorUris { + + private final List uris; + + private MonitorUris(final Uri[] uris) { + this.uris = new ArrayList<>(Arrays.asList(uris)); + } + + /** + * Create a MonitorUris object to contain all the URIs to be monitored. + * + * @param uris An array of URIs. + * @return A MonitorUris object. + */ + @PluginFactory + public static MonitorUris createMonitorUris( // + @PluginElement("Uris") final Uri[] uris) { + return new MonitorUris(uris == null ? Uri.EMPTY_ARRAY : uris); + } + + /** + * Returns a list of the {@code Uri} objects created during configuration. + * @return the URIs to be monitored + */ + public List getUris() { + return uris; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Uri.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Uri.java new file mode 100644 index 00000000000..a07dc3e2a03 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Uri.java @@ -0,0 +1,88 @@ +/* + * 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; + +import java.util.Objects; +import org.apache.logging.log4j.core.Core; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.config.plugins.PluginValue; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Descriptor of a URI object that is created via configuration. + */ +@Plugin(name = "Uri", category = Core.CATEGORY_NAME, printObject = true) +public final class Uri { + + /** + * The empty array. + */ + static final Uri[] EMPTY_ARRAY = {}; + + private final String uri; + + private Uri(final String uri) { + this.uri = Objects.requireNonNull(uri, "uri is null"); + } + + /** + * Creates a Uri object. + * + * @param uri the URI. + * @return A Uri object. + */ + @PluginFactory + public static Uri createUri( // @formatter:off + @PluginValue("uri") final String uri) { + // @formatter:on + + StatusLogger.getLogger().debug("Creating Uri('{}')", uri); + return new Uri(uri); + } + + /** + * Returns the URI. + * + * @return the URI + */ + public String getUri() { + return uri; + } + + @Override + public int hashCode() { + return uri.hashCode(); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof Uri)) { + return false; + } + final Uri other = (Uri) object; + return this.uri == other.uri; + } + + @Override + public String toString() { + return "Uri[" + uri + "]"; + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java index 7f0115f3cf4..badbd346cda 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java @@ -61,6 +61,15 @@ public interface ConfigurationBuilder extends Builder add(CustomLevelComponentBuilder builder); + /** + * Adds a MonitorUri component. + * @param builder The MonitorUriComponentBuilder with all of its attributes set. + * @return this builder instance. + */ + default ConfigurationBuilder add(MonitorUriComponentBuilder builder) { + return this; + } + /** * Adds a Filter component. * @param builder the FilterComponentBuilder with all of its attributes and sub components set. @@ -272,6 +281,15 @@ public interface ConfigurationBuilder extends Builder extends Builder setMonitorInterval(String intervalSeconds); - /** - * Set the URIs to be monitored in addition to the configuration file - * @param monitorUris the URIs to monitor - * @return this builder instance - */ - default ConfigurationBuilder setMonitorUris(final String monitorUris) { - return this; - } - /** * Sets the list of packages to search for plugins. * @param packages The comma separated list of packages. diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/MonitorUriComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/MonitorUriComponentBuilder.java new file mode 100644 index 00000000000..4942dc02c37 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/MonitorUriComponentBuilder.java @@ -0,0 +1,22 @@ +/* + * 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.builder.api; + +/** + * Assembler for constructing MonitorUri Components. + */ +public interface MonitorUriComponentBuilder extends ComponentBuilder {} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java index 2f63a9e78c3..6d91c2d89d8 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/package-info.java @@ -20,7 +20,7 @@ * @since 2.4 */ @Export -@Version("2.21.0") +@Version("2.25.0") package org.apache.logging.log4j.core.config.builder.api; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java index 61a3f0fdc50..8cf02dde440 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/BuiltConfiguration.java @@ -18,11 +18,7 @@ import java.io.IOException; import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; import java.util.List; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.AbstractConfiguration; @@ -34,8 +30,6 @@ import org.apache.logging.log4j.core.config.plugins.util.PluginType; import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Patterns; -import org.apache.logging.log4j.core.util.Source; -import org.apache.logging.log4j.util.Strings; /** * This is the general version of the Configuration created by the Builder. It may be extended to @@ -52,6 +46,7 @@ public class BuiltConfiguration extends AbstractConfiguration { private Component propertiesComponent; private Component customLevelsComponent; private Component scriptsComponent; + private Component monitorUriComponent; private String contentType = "text"; public BuiltConfiguration( @@ -84,6 +79,10 @@ public BuiltConfiguration( customLevelsComponent = component; break; } + case "MonitorUris": { + monitorUriComponent = component; + break; + } } } this.rootComponent = rootComponent; @@ -101,6 +100,9 @@ public void setup() { if (customLevelsComponent.getComponents().size() > 0) { children.add(convertToNode(rootNode, customLevelsComponent)); } + if (monitorUriComponent.getComponents().size() > 0) { + children.add(convertToNode(rootNode, monitorUriComponent)); + } children.add(convertToNode(rootNode, loggersComponent)); children.add(convertToNode(rootNode, appendersComponent)); if (filtersComponent.getComponents().size() > 0) { @@ -154,28 +156,9 @@ public void setShutdownTimeoutMillis(final long shutdownTimeoutMillis) { } public void setMonitorInterval(final int intervalSeconds) { - initializeMonitoring(intervalSeconds, ""); - } - - public void initializeMonitoring(final int intervalSeconds, final String monitorUris) { if (this instanceof Reconfigurable && intervalSeconds > 0) { - initializeWatchers( - (Reconfigurable) this, getConfigurationSource(), getAuxiliarySources(monitorUris), intervalSeconds); - } - } - - private Collection getAuxiliarySources(final String monitorUris) { - final Collection auxiliarySources = new HashSet<>(); - if (Strings.isNotBlank(monitorUris)) { - for (final String uri : Arrays.asList(monitorUris.split(Patterns.COMMA_SEPARATOR))) { - try { - auxiliarySources.add(new Source(new URI(uri))); - } catch (URISyntaxException e) { - LOGGER.error("Error parsing monitorUris: " + monitorUris, e); - } - } + initializeWatchers((Reconfigurable) this, getConfigurationSource(), intervalSeconds); } - return auxiliarySources; } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java index fd77afc49d2..1dc4a66ac1e 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java @@ -55,13 +55,13 @@ import org.apache.logging.log4j.core.config.builder.api.KeyValuePairComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.MonitorUriComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.PropertyComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder; import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Throwables; -import org.apache.logging.log4j.util.Strings; /** * @param The BuiltConfiguration type. @@ -78,10 +78,10 @@ public class DefaultConfigurationBuilder implement private Component properties; private Component customLevels; private Component scripts; + private Component monitorUris; private final Class clazz; private ConfigurationSource source; private int monitorInterval; - private String monitorUris; private Level level; private String destination; private String packages; @@ -126,6 +126,8 @@ public DefaultConfigurationBuilder(final Class clazz) { components.add(appenders); loggers = new Component("Loggers"); components.add(loggers); + monitorUris = new Component("MonitorUris"); + components.add(monitorUris); } protected ConfigurationBuilder add(final Component parent, final ComponentBuilder builder) { @@ -138,6 +140,11 @@ public ConfigurationBuilder add(final AppenderComponentBuilder builder) { return add(appenders, builder); } + @Override + public ConfigurationBuilder add(final MonitorUriComponentBuilder builder) { + return add(monitorUris, builder); + } + @Override public ConfigurationBuilder add(final CustomLevelComponentBuilder builder) { return add(customLevels, builder); @@ -216,7 +223,7 @@ public T build(final boolean initialize) { if (advertiser != null) { configuration.createAdvertiser(advertiser, source); } - configuration.initializeMonitoring(monitorInterval, monitorUris); + configuration.setMonitorInterval(monitorInterval); } catch (final Exception ex) { throw new IllegalArgumentException("Invalid Configuration class specified", ex); } @@ -289,13 +296,11 @@ private void writeXmlConfiguration(final XMLStreamWriter xmlWriter) throws XMLSt if (monitorInterval > 0) { xmlWriter.writeAttribute("monitorInterval", String.valueOf(monitorInterval)); } - if (Strings.isNotBlank(monitorUris)) { - xmlWriter.writeAttribute("monitorUris", monitorUris); - } writeXmlSection(xmlWriter, properties); writeXmlSection(xmlWriter, scripts); writeXmlSection(xmlWriter, customLevels); + writeXmlSection(xmlWriter, monitorUris); if (filters.getComponents().size() == 1) { writeXmlComponent(xmlWriter, filters.getComponents().get(0)); } else if (filters.getComponents().size() > 1) { @@ -342,7 +347,6 @@ private void writeXmlAttributes(final XMLStreamWriter xmlWriter, final Component } } - @Override public ScriptComponentBuilder newScript(final String name, final String language, final String text) { return new DefaultScriptComponentBuilder(this, name, language, text); } @@ -458,6 +462,11 @@ public CustomLevelComponentBuilder newCustomLevel(final String name, final int l return new DefaultCustomLevelComponentBuilder(this, name, level); } + @Override + public MonitorUriComponentBuilder newMonitorUri(final String uri) { + return new DefaultMonitorUriComponentBuilder(this, uri); + } + @Override public FilterComponentBuilder newFilter( final String type, final Filter.Result onMatch, final Filter.Result onMismatch) { @@ -570,12 +579,6 @@ public ConfigurationBuilder setMonitorInterval(final String intervalSeconds) return this; } - @Override - public ConfigurationBuilder setMonitorUris(final String monitorUris) { - this.monitorUris = monitorUris; - return this; - } - @Override public ConfigurationBuilder setPackages(final String packages) { this.packages = packages; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultMonitorUriComponentBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultMonitorUriComponentBuilder.java new file mode 100644 index 00000000000..21299003cf8 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultMonitorUriComponentBuilder.java @@ -0,0 +1,33 @@ +/* + * 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.builder.impl; + +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.builder.api.MonitorUriComponentBuilder; + +/** + * + */ +class DefaultMonitorUriComponentBuilder extends DefaultComponentAndConfigurationBuilder + implements MonitorUriComponentBuilder { + + public DefaultMonitorUriComponentBuilder( + final DefaultConfigurationBuilder builder, final String uri) { + super(builder, "Uri"); + addAttribute("uri", uri); + } +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java index 4c630135252..32a69487d75 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/package-info.java @@ -20,7 +20,7 @@ * @since 2.4 */ @Export -@Version("2.21.0") +@Version("2.25.0") package org.apache.logging.log4j.core.config.builder.impl; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java index dd5accfeeea..787cf180434 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java @@ -19,7 +19,7 @@ * Support for composite configurations. */ @Export -@Version("2.21.0") +@Version("2.20.1") package org.apache.logging.log4j.core.config.composite; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java index 6076088c48e..b574070a433 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java @@ -23,11 +23,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -42,7 +39,6 @@ import org.apache.logging.log4j.core.config.status.StatusConfiguration; import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Patterns; -import org.apache.logging.log4j.core.util.Source; /** * Creates a Node hierarchy from a JSON file. @@ -70,7 +66,6 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS processAttributes(rootNode, root); final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(getDefaultStatus()); int monitorIntervalSeconds = 0; - Collection auxiliarySources = new HashSet<>(); for (final Map.Entry entry : rootNode.getAttributes().entrySet()) { final String key = entry.getKey(); @@ -92,13 +87,9 @@ public JsonConfiguration(final LoggerContext loggerContext, final ConfigurationS monitorIntervalSeconds = Integers.parseInt(value); } else if ("advertiser".equalsIgnoreCase(key)) { createAdvertiser(value, configSource, buffer, "application/json"); - } else if ("monitorUris".equalsIgnoreCase(key)) { - for (final String uri : Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))) { - auxiliarySources.add(new Source(new URI(uri))); - } } } - initializeWatchers(this, configSource, auxiliarySources, monitorIntervalSeconds); + initializeWatchers(this, configSource, monitorIntervalSeconds); statusConfig.initialize(); if (getName() == null) { setName(configSource.getLocation()); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java index 5615f2cc73d..9d9e5d60eb3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/package-info.java @@ -18,7 +18,7 @@ * Classes and interfaces supporting configuration of Log4j 2 with JSON. */ @Export -@Version("2.21.0") +@Version("2.20.1") package org.apache.logging.log4j.core.config.json; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java index 594b15dfd4f..8f7cb3e5b1b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java @@ -35,6 +35,7 @@ import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.LoggableComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder; +import org.apache.logging.log4j.core.config.builder.api.MonitorUriComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder; import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder; @@ -61,7 +62,6 @@ public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory private static final String CONFIG_NAME = "name"; private static final String MONITOR_INTERVAL = "monitorInterval"; private static final String CONFIG_TYPE = "type"; - private static final String MONITOR_URIS = "monitorUris"; private final ConfigurationBuilder builder; private LoggerContext loggerContext; @@ -96,7 +96,6 @@ public PropertiesConfiguration build() { .setPackages(rootProperties.getProperty(PACKAGES)) .setConfigurationName(rootProperties.getProperty(CONFIG_NAME)) .setMonitorInterval(rootProperties.getProperty(MONITOR_INTERVAL, "0")) - .setMonitorUris(rootProperties.getProperty(MONITOR_URIS)) .setAdvertiser(rootProperties.getProperty(ADVERTISER_KEY)); final Properties propertyPlaceholders = PropertiesUtil.extractSubset(rootProperties, "property"); @@ -126,6 +125,12 @@ public PropertiesConfiguration build() { } } + final Map monitorUris = + PropertiesUtil.partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "monitorUri")); + for (final Map.Entry entry : monitorUris.entrySet()) { + builder.add(createMonitorUri(entry.getKey().trim(), entry.getValue())); + } + final String filterProp = rootProperties.getProperty("filters"); if (filterProp != null) { final String[] filterNames = filterProp.split(","); @@ -252,6 +257,14 @@ private AppenderRefComponentBuilder createAppenderRef(final String key, final Pr return addFiltersToComponent(appenderRefBuilder, properties); } + private MonitorUriComponentBuilder createMonitorUri(final String key, final Properties properties) { + final String uri = (String) properties.remove("uri"); + if (Strings.isEmpty(uri)) { + throw new ConfigurationException("No uri attribute provided for MonitorUri " + key); + } + return builder.newMonitorUri(uri); + } + private LoggerComponentBuilder createLogger(final String key, final Properties properties) { final String levelAndRefs = properties.getProperty(""); final String name = (String) properties.remove(CONFIG_NAME); diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java index 57419616d65..1563a62e0ec 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/package-info.java @@ -18,7 +18,7 @@ * Configuration using Properties files. */ @Export -@Version("2.21.0") +@Version("2.20.1") package org.apache.logging.log4j.core.config.properties; import org.osgi.annotation.bundle.Export; 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 1d86382f8b7..6caeec59ae5 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 @@ -20,13 +20,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Map; import javax.xml.XMLConstants; @@ -49,7 +45,6 @@ import org.apache.logging.log4j.core.util.Integers; import org.apache.logging.log4j.core.util.Loader; import org.apache.logging.log4j.core.util.Patterns; -import org.apache.logging.log4j.core.util.Source; import org.apache.logging.log4j.core.util.Throwables; import org.w3c.dom.Attr; import org.w3c.dom.Document; @@ -111,7 +106,6 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo final Map attrs = processAttributes(rootNode, rootElement); final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(getDefaultStatus()); int monitorIntervalSeconds = 0; - Collection auxiliarySources = new HashSet<>(); for (final Map.Entry entry : attrs.entrySet()) { final String key = entry.getKey(); final String value = getConfigurationStrSubstitutor().replace(entry.getValue()); @@ -135,15 +129,11 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo monitorIntervalSeconds = Integers.parseInt(value); } else if ("advertiser".equalsIgnoreCase(key)) { createAdvertiser(value, configSource, buffer, "text/xml"); - } else if ("monitorUris".equalsIgnoreCase(key)) { - for (final String uri : Arrays.asList(value.split(Patterns.COMMA_SEPARATOR))) { - auxiliarySources.add(new Source(new URI(uri))); - } } } - initializeWatchers(this, configSource, auxiliarySources, monitorIntervalSeconds); + initializeWatchers(this, configSource, monitorIntervalSeconds); statusConfig.initialize(); - } catch (final SAXException | IOException | ParserConfigurationException | URISyntaxException e) { + } catch (final SAXException | IOException | ParserConfigurationException e) { LOGGER.error("Error parsing " + configSource.getLocation(), e); } if (strict && schemaResource != null && buffer != null) { diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java index 79858cbdfd6..f0a6c9d7e4f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/package-info.java @@ -18,7 +18,7 @@ * Classes and interfaces supporting configuration of Log4j 2 with XML. */ @Export -@Version("2.21.0") +@Version("2.20.2") package org.apache.logging.log4j.core.config.xml; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java index ae6af3f58b7..c476f1d82c1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/package-info.java @@ -18,7 +18,7 @@ * Classes and interfaces supporting configuration of Log4j 2 with YAML. */ @Export -@Version("2.21.0") +@Version("2.20.1") package org.apache.logging.log4j.core.config.yaml; import org.osgi.annotation.bundle.Export; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java index d3e7133def2..865759ed13f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java @@ -20,8 +20,8 @@ import aQute.bnd.annotation.Resolution; import aQute.bnd.annotation.spi.ServiceConsumer; import java.io.File; +import java.net.URI; import java.time.Instant; -import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -355,26 +355,6 @@ public void watch(final Source source, final Watcher watcher) { watchers.put(source, new ConfigurationMonitor(lastModified, watcher)); } - /** - * Watches the given file. - * - * @param source the source to watch. - * @param auxiliarySources auxiliary sources to also watch - * @param watcher the watcher to notify of file changes. - */ - public void watch( - final Source source, final Collection auxiliarySources, final ConfigurationFileWatcher watcher) { - watcher.watching(source, auxiliarySources); - final long lastModified = watcher.getLastModified(); - if (logger.isDebugEnabled()) { - String message = auxiliarySources.isEmpty() - ? "Watching configuration '{}' for lastModified {} ({})" - : "Watching configuration '{}' for lastModified {} ({}) and files {}"; - logger.debug(message, source, millisToString(lastModified), lastModified); - } - watchers.put(source, new ConfigurationMonitor(lastModified, watcher)); - } - /** * Watches the given file. * @@ -391,4 +371,17 @@ public void watchFile(final File file, final FileWatcher fileWatcher) { final Source source = new Source(file); watch(source, watcher); } + + /** + * Add the given URIs to be monitored for the given source. + * + * @param source the source being watched. + * @param monitorUris the URIs to also watch + */ + public void addMonitorUris(final Source source, final List monitorUris) { + ConfigurationMonitor monitor = watchers.get(source); + if (monitor != null && monitor.getWatcher() instanceof ConfigurationFileWatcher) { + ((ConfigurationFileWatcher) monitor.getWatcher()).addMonitorUris(monitorUris); + } + } } From 443a932aef80730d5077f80c19340372076d41b5 Mon Sep 17 00:00:00 2001 From: MichaelMorris Date: Tue, 22 Apr 2025 15:11:56 +0100 Subject: [PATCH 3/4] Updated for review comments Signed-off-by: MichaelMorris --- .../core/config/AbstractConfiguration.java | 13 ++++++- .../core/config/ConfigurationFileWatcher.java | 38 ++++--------------- .../core/config/ConfigurationSource.java | 4 -- .../logging/log4j/core/util/WatchManager.java | 14 ------- 4 files changed, 20 insertions(+), 49 deletions(-) diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index a53dfe2581e..523a70da8ca 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -331,7 +331,7 @@ public void start() { getConfigurationSource(), uris, watchManager.getIntervalSeconds()); - watchManager.addMonitorUris(configurationSource.getSource(), uris); + watchMonitorUris(); } else { LOGGER.info( "Start watching for changes to {} every {} seconds", @@ -358,6 +358,17 @@ public void start() { LOGGER.info("Configuration {} started.", this); } + private void watchMonitorUris() { + if (this instanceof Reconfigurable) { + uris.stream().forEach(uri -> { + Source source = new Source(uri); + final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher( + this, (Reconfigurable) this, listeners, source.getFile().lastModified()); + watchManager.watch(source, watcher); + }); + } + } + private boolean hasAsyncLoggers() { if (root instanceof AsyncLoggerConfig) { return true; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java index 0dfdb8582fe..9b920a45241 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java @@ -17,10 +17,7 @@ package org.apache.logging.log4j.core.config; import java.io.File; -import java.net.URI; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.logging.log4j.core.util.AbstractWatcher; import org.apache.logging.log4j.core.util.FileWatcher; import org.apache.logging.log4j.core.util.Source; @@ -31,7 +28,8 @@ */ public class ConfigurationFileWatcher extends AbstractWatcher implements FileWatcher { - private Map monitoredFiles = new HashMap<>(); + private File file; + private long lastModifiedMillis; public ConfigurationFileWatcher( final Configuration configuration, @@ -39,49 +37,29 @@ public ConfigurationFileWatcher( final List configurationListeners, long lastModifiedMillis) { super(configuration, reconfigurable, configurationListeners); + this.lastModifiedMillis = lastModifiedMillis; } @Override public long getLastModified() { - Long lastModifiedMillis = 0L; - for (final File monitoredFile : monitoredFiles.keySet()) { - if (monitoredFile.lastModified() > lastModifiedMillis) { - lastModifiedMillis = monitoredFile.lastModified(); - } - } - return lastModifiedMillis; + return file != null ? file.lastModified() : 0; } @Override public void fileModified(final File file) { - monitoredFiles.entrySet().stream() - .forEach(monitoredFile -> - monitoredFile.setValue(monitoredFile.getKey().lastModified())); + lastModifiedMillis = file.lastModified(); } @Override public void watching(final Source source) { - File file = source.getFile(); - monitoredFiles.put(file, file.lastModified()); + file = source.getFile(); + lastModifiedMillis = file.lastModified(); super.watching(source); } - /** - * Add the given URIs to be watched. - * - * @param monitorUris URIs to also watch - */ - public void addMonitorUris(final List monitorUris) { - monitorUris.forEach(uri -> { - File additionalFile = new Source(uri).getFile(); - monitoredFiles.put(additionalFile, additionalFile.lastModified()); - }); - } - @Override public boolean isModified() { - return monitoredFiles.entrySet().stream() - .anyMatch(file -> file.getValue() != file.getKey().lastModified()); + return lastModifiedMillis != file.lastModified(); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java index ecef1c831fa..d6143cdc158 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java @@ -382,8 +382,4 @@ public String toString() { return null; } } - - Source getSource() { - return this.source; - } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java index 865759ed13f..3f2856340fd 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java @@ -20,7 +20,6 @@ import aQute.bnd.annotation.Resolution; import aQute.bnd.annotation.spi.ServiceConsumer; import java.io.File; -import java.net.URI; import java.time.Instant; import java.util.Date; import java.util.HashMap; @@ -371,17 +370,4 @@ public void watchFile(final File file, final FileWatcher fileWatcher) { final Source source = new Source(file); watch(source, watcher); } - - /** - * Add the given URIs to be monitored for the given source. - * - * @param source the source being watched. - * @param monitorUris the URIs to also watch - */ - public void addMonitorUris(final Source source, final List monitorUris) { - ConfigurationMonitor monitor = watchers.get(source); - if (monitor != null && monitor.getWatcher() instanceof ConfigurationFileWatcher) { - ((ConfigurationFileWatcher) monitor.getWatcher()).addMonitorUris(monitorUris); - } - } } From bf2ad2ced0a415586e07ed2b94684d060608f9d0 Mon Sep 17 00:00:00 2001 From: MichaelMorris Date: Mon, 12 May 2025 11:04:50 +0100 Subject: [PATCH 4/4] Update for code review comments and added documentation Signed-off-by: MichaelMorris --- .../core/config/AbstractConfiguration.java | 22 +++---- .../manual/configuration/monitoruris.json | 15 +++++ .../configuration/monitoruris.properties | 20 ++++++ .../manual/configuration/monitoruris.xml | 28 +++++++++ .../manual/configuration/monitoruris.yaml | 22 +++++++ .../ROOT/pages/manual/configuration.adoc | 63 +++++++++++++++++++ 6 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.json create mode 100644 src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.properties create mode 100644 src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.xml create mode 100644 src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.yaml diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java index 523a70da8ca..ec7611b508b 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java @@ -269,6 +269,7 @@ public void initialize() { setup(); setupAdvertisement(); doConfigure(); + watchMonitorUris(); setState(State.INITIALIZED); LOGGER.debug("Configuration {} initialized", this); } @@ -325,19 +326,10 @@ public void start() { LOGGER.info("Starting configuration {}...", this); this.setStarting(); if (watchManager.getIntervalSeconds() >= 0) { - if (uris != null && uris.size() > 0) { - LOGGER.info( - "Start watching for changes to {} and {} every {} seconds", - getConfigurationSource(), - uris, - watchManager.getIntervalSeconds()); - watchMonitorUris(); - } else { - LOGGER.info( - "Start watching for changes to {} every {} seconds", - getConfigurationSource(), - watchManager.getIntervalSeconds()); - } + LOGGER.info( + "Start watching for changes to {} every {} seconds", + watchManager.getConfigurationWatchers().keySet(), + watchManager.getIntervalSeconds()); watchManager.start(); } if (hasAsyncLoggers()) { @@ -359,7 +351,7 @@ public void start() { } private void watchMonitorUris() { - if (this instanceof Reconfigurable) { + if (this instanceof Reconfigurable && watchManager.getIntervalSeconds() >= 0) { uris.stream().forEach(uri -> { Source source = new Source(uri); final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher( @@ -802,7 +794,7 @@ private List convertToJavaNetUris(final List uris) { try { javaNetUris.add(new URI(uri.getUri())); } catch (URISyntaxException e) { - LOGGER.error("Error parsing monitor URI: " + uri, e); + throw new ConfigurationException("Invalid URI provided for MonitorUri " + uri); } } return javaNetUris; diff --git a/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.json b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.json new file mode 100644 index 00000000000..e8022cc4836 --- /dev/null +++ b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.json @@ -0,0 +1,15 @@ +{ + "Configuration": { + "monitorInterval": "30", + "MonitorUris": { + "Uri": [ + { + "uri": "to-be-monitored-1.file" + }, + { + "uri": "to-be-monitored-2.file" + } + ] + } + } +} diff --git a/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.properties b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.properties new file mode 100644 index 00000000000..1c7010a326c --- /dev/null +++ b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.properties @@ -0,0 +1,20 @@ +# +# 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. +# +## +monitorInterval = 30 +monitorUri.0.uri = to-be-monitored-1.file +monitorUri.1.uri = to-be-monitored-2.file diff --git a/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.xml b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.xml new file mode 100644 index 00000000000..be987894adc --- /dev/null +++ b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.yaml b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.yaml new file mode 100644 index 00000000000..a7ee9d371ec --- /dev/null +++ b/src/site/antora/modules/ROOT/examples/manual/configuration/monitoruris.yaml @@ -0,0 +1,22 @@ +# +# 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. +# +Configuration: + monitorInterval: '30' + MonitorUris: + Uri: + - uri: to-be-monitored-1.file + - uri: to-be-monitored-2.file diff --git a/src/site/antora/modules/ROOT/pages/manual/configuration.adoc b/src/site/antora/modules/ROOT/pages/manual/configuration.adoc index 7a0e738e41c..da37190a1c5 100644 --- a/src/site/antora/modules/ROOT/pages/manual/configuration.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/configuration.adoc @@ -988,6 +988,69 @@ include::example$manual/configuration/routing.properties[tag=appender] Therefore, the dollar `$` sign needs to be escaped. <2> All the attributes of children of the `Route` element have a **deferred** evaluation. Therefore, they need only one `$` sign. +[#monitorUris] +== Monitor URIs + +Log4j can be configured to poll for changes to resources (in addition to the configuration file) using the `MonitorUris` element of the configuration. If a change is detected in any of the specifed resources, Log4j automatically reconfigures the logger context. + +The polling interval is determined by the value of the <> attribute. If set to 0, polling is disabled. See <> for further details. + +This may be useful where the configuration is dependent on a resource that is external to the configuration file. + +The MonitorUris component supports the following nested element: + +[#MonitorUris-elements] +[cols="1m,1,4"] +|=== +| Type | Multiplicity | Description + +| `Uri` +| one or more +| Specifies the URI of a resouce to be polled for changes. +|=== + +The Uri component supports the following attribute: +[#uri-attributes-name] +=== `uri` + +[cols="1h,5"] +|=== +| Type | `String` +|=== + +See example below: + +[tabs] +==== +XML:: ++ +[source,xml] +---- +include::example$manual/configuration/monitoruris.xml[lines=18..] +---- + +JSON:: ++ +[source,json] +---- +include::example$manual/configuration/monitoruris.json[] +---- + +YAML:: ++ +[source,yaml] +---- +include::example$manual/configuration/monitoruris.yaml[lines=17..] +---- + +Properties:: ++ +[source,properties] +---- +include::example$manual/configuration/monitoruris.properties[lines=18..] +---- +==== + [id=arbiters] == [[Arbiters]] Arbiters