Skip to content
Closed
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.waitAtMost;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
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.ComponentBuilder;
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.test.junit.LoggerContextSource;
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 MonitorResourcesTest {

@Test
void test_reconfiguration(@TempDir(cleanup = CleanupMode.ON_SUCCESS) final Path tempDir) throws IOException {
final ConfigurationBuilder<PropertiesConfiguration> configBuilder =
ConfigurationBuilderFactory.newConfigurationBuilder(PropertiesConfiguration.class);
final Path configFile = tempDir.resolve("log4j.xml");
final Path externalResourceFile1 = tempDir.resolve("external-resource-1.txt");
final Path externalResourceFile2 = tempDir.resolve("external-resource-2.txt");
final ConfigurationSource configSource = new ConfigurationSource(new Source(configFile), new byte[] {}, 0);
final int monitorInterval = 3;

final ComponentBuilder<?> monitorResourcesComponent = configBuilder.newComponent("MonitorResources");
monitorResourcesComponent.addComponent(configBuilder
.newComponent("MonitorResource")
.addAttribute("uri", externalResourceFile1.toUri().toString()));
monitorResourcesComponent.addComponent(configBuilder
.newComponent("MonitorResource")
.addAttribute("uri", externalResourceFile2.toUri().toString()));

final Configuration config = configBuilder
.setConfigurationSource(configSource)
.setMonitorInterval(String.valueOf(monitorInterval))
.addComponent(monitorResourcesComponent)
.build();

try (final LoggerContext loggerContext = Configurator.initialize(config)) {
assertMonitorResourceFileNames(
loggerContext,
configFile.getFileName().toString(),
externalResourceFile1.getFileName().toString(),
externalResourceFile2.getFileName().toString());
Files.write(externalResourceFile2, Collections.singletonList("a change"));
waitAtMost(2 * monitorInterval, TimeUnit.SECONDS).until(() -> loggerContext.getConfiguration() != config);
}
}

@Test
@LoggerContextSource("config/MonitorResource/log4j.xml")
void test_config_of_type_XML(final LoggerContext loggerContext) {
assertMonitorResourceFileNames(loggerContext, "log4j.xml");
}

@Test
@LoggerContextSource("config/MonitorResource/log4j.json")
void test_config_of_type_JSON(final LoggerContext loggerContext) {
assertMonitorResourceFileNames(loggerContext, "log4j.json");
}

@Test
@LoggerContextSource("config/MonitorResource/log4j.yaml")
void test_config_of_type_YAML(final LoggerContext loggerContext) {
assertMonitorResourceFileNames(loggerContext, "log4j.yaml");
}

@Test
@LoggerContextSource("config/MonitorResource/log4j.properties")
void test_config_of_type_properties(final LoggerContext loggerContext) {
assertMonitorResourceFileNames(loggerContext, "log4j.properties");
}

private static void assertMonitorResourceFileNames(final LoggerContext loggerContext, final String configFileName) {
assertMonitorResourceFileNames(loggerContext, configFileName, "external-file-1.txt", "external-file-2.txt");
}

private static void assertMonitorResourceFileNames(
final LoggerContext loggerContext, final String configFileName, final String... externalResourceFileNames) {
final Set<Source> sources = loggerContext
.getConfiguration()
.getWatchManager()
.getConfigurationWatchers()
.keySet();
final Set<String> actualFileNames =
sources.stream().map(source -> source.getFile().getName()).collect(Collectors.toSet());
final Set<String> expectedFileNames = new LinkedHashSet<>();
expectedFileNames.add(configFileName);
expectedFileNames.addAll(Arrays.asList(externalResourceFileNames));
assertThat(actualFileNames).as("watch manager sources: %s", sources).isEqualTo(expectedFileNames);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ void testConfigWaitStrategyFactory(final LoggerContext context) {
asyncWaitStrategyFactory instanceof YieldingWaitStrategyFactory);
}

@Test
@LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.properties")
void testConfigWaitStrategyFactoryFromProperties(final LoggerContext context) {
final AsyncWaitStrategyFactory asyncWaitStrategyFactory =
context.getConfiguration().getAsyncWaitStrategyFactory();
assertEquals(YieldingWaitStrategyFactory.class, asyncWaitStrategyFactory.getClass());
assertThat(
"factory is YieldingWaitStrategyFactory",
asyncWaitStrategyFactory instanceof YieldingWaitStrategyFactory);
}

@Test
@LoggerContextSource("AsyncWaitStrategyFactoryConfigTest.xml")
void testWaitStrategy(final LoggerContext context) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# 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.
#

strategy.type = AsyncWaitStrategyFactory
strategy.class = org.apache.logging.log4j.core.async.AsyncWaitStrategyFactoryConfigTest$YieldingWaitStrategyFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"Configuration": {
"monitorInterval": "30",
"MonitorResources": {
"MonitorResource": [
{
"uri": "file://path/to/external-file-1.txt"
},
{
"uri": "file://path/to/external-file-2.txt"
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -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.
#
monitorInterval = 30
monitorResources.type = MonitorResources
monitorResources.0.type = MonitorResource
monitorResources.0.uri = file://path/to/external-file-1.txt
monitorResources.1.type = MonitorResource
monitorResources.1.uri = file://path/to/external-file-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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 xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-config-2.xsd"
monitorInterval="30">
<MonitorResources>
<MonitorResource uri="file://path/to/external-file-1.txt"/>
<MonitorResource uri="file://path/to/external-file-2.txt"/>
</MonitorResources>
</Configuration>
Original file line number Diff line number Diff line change
@@ -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'
MonitorResources:
MonitorResource:
- uri: "file://path/to/external-file-1.txt"
- uri: "file://path/to/external-file-2.txt"
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<>();
private ConcurrentMap<String, LoggerConfig> loggerConfigs = new ConcurrentHashMap<>();
private List<CustomLevelConfig> customLevels = Collections.emptyList();
private Set<MonitorResource> monitorResources = Collections.emptySet();
private final ConcurrentMap<String, String> propertyMap = new ConcurrentHashMap<>();
private final Interpolator tempLookup = new Interpolator(propertyMap);
private final StrSubstitutor runtimeStrSubstitutor = new RuntimeStrSubstitutor(tempLookup);
Expand Down Expand Up @@ -267,6 +268,7 @@ public void initialize() {
setup();
setupAdvertisement();
doConfigure();
watchMonitorResources();
setState(State.INITIALIZED);
LOGGER.debug("Configuration {} initialized", this);
}
Expand Down Expand Up @@ -322,10 +324,10 @@ public void start() {
}
LOGGER.info("Starting configuration {}...", this);
this.setStarting();
if (watchManager.getIntervalSeconds() >= 0) {
if (isConfigurationMonitoringEnabled()) {
LOGGER.info(
"Start watching for changes to {} every {} seconds",
getConfigurationSource(),
watchManager.getConfigurationWatchers().keySet(),
watchManager.getIntervalSeconds());
watchManager.start();
}
Expand All @@ -347,6 +349,21 @@ public void start() {
LOGGER.info("Configuration {} started.", this);
}

private boolean isConfigurationMonitoringEnabled() {
return this instanceof Reconfigurable && watchManager.getIntervalSeconds() > 0;
}

private void watchMonitorResources() {
if (isConfigurationMonitoringEnabled()) {
monitorResources.forEach(monitorResource -> {
Source source = new Source(monitorResource.getUri());
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;
Expand Down Expand Up @@ -729,9 +746,16 @@ protected void doConfigure() {
} else if (child.isInstanceOf(AsyncWaitStrategyFactoryConfig.class)) {
final AsyncWaitStrategyFactoryConfig awsfc = child.getObject(AsyncWaitStrategyFactoryConfig.class);
asyncWaitStrategyFactory = awsfc.createWaitStrategyFactory();
} else if (child.isInstanceOf(MonitorResources.class)) {
monitorResources = child.getObject(MonitorResources.class).getResources();
} else {
final List<String> expected = Arrays.asList(
"\"Appenders\"", "\"Loggers\"", "\"Properties\"", "\"Scripts\"", "\"CustomLevels\"");
"\"Appenders\"",
"\"Loggers\"",
"\"Properties\"",
"\"Scripts\"",
"\"CustomLevels\"",
"\"MonitorResources\"");
LOGGER.error(
"Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.",
child.getName(),
Expand Down
Loading