This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch 2.24.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
The following commit(s) were added to refs/heads/2.24.x by this push:
new 421d8a797f Revert "Fix reloading of the configuration from HTTP(S)"
421d8a797f is described below
commit 421d8a797f48924e6eeaa88ce0d26bc3e9c583df
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Sun Sep 22 21:11:27 2024 +0200
Revert "Fix reloading of the configuration from HTTP(S)"
This reverts commit be24f928f837d6823cbb919a63d3fa5501fbe381.
---
log4j-appserver/pom.xml | 10 -
.../log4j/core/config/ConfigurationSourceTest.java | 62 +++--
.../filter/HttpThreadContextMapFilterTest.java | 202 +++++++++++++++++
.../filter/MutableThreadContextMapFilterTest.java | 250 +++++----------------
.../log4j/core/net/UrlConnectionFactoryTest.java | 213 ++++++++++--------
.../logging/log4j/core/net/WireMockUtil.java | 84 -------
.../logging/log4j/core/util/HttpWatcherTest.java | 160 -------------
.../logging/log4j/core/util/WatchHttpTest.java | 156 +++++++++++++
.../logging/log4j/core/util/WatchManagerTest.java | 177 +++++++--------
.../src/test/resources/emptyConfig.json | 4 +
.../filter/MutableThreadContextMapFilterTest.xml | 35 ---
.../src/test/resources/filterConfig.json | 6 +
...tionSourceTest.xml => log4j2-mutableFilter.xml} | 19 +-
.../apache/logging/log4j/core/LoggerContext.java | 19 +-
.../log4j/core/config/AbstractConfiguration.java | 17 +-
.../log4j/core/config/ConfigurationSource.java | 68 +++---
.../logging/log4j/core/config/HttpWatcher.java | 39 +---
.../log4j/core/config/xml/XmlConfiguration.java | 12 +-
.../core/filter/MutableThreadContextMapFilter.java | 50 ++---
.../logging/log4j/core/util/WatchManager.java | 23 +-
.../core/util/internal/HttpInputStreamUtil.java | 96 ++------
log4j-parent/pom.xml | 16 ++
src/changelog/.2.x.x/2937-http-watcher.xml | 8 -
23 files changed, 810 insertions(+), 916 deletions(-)
diff --git a/log4j-appserver/pom.xml b/log4j-appserver/pom.xml
index 0b9c10b0dd..df0ce0067d 100644
--- a/log4j-appserver/pom.xml
+++ b/log4j-appserver/pom.xml
@@ -52,32 +52,22 @@
org.apache.tomcat.juli;transitive=false,
org.eclipse.jetty.util;transitive=false
</bnd-extra-module-options>
-
- <!-- Dependencies unique for this module -->
- <jetty.version>9.4.56.v20240826</jetty.version>
- <tomcat-juli.version>10.0.27</tomcat-juli.version>
</properties>
<dependencies>
-
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
- <version>${jetty.version}</version>
<scope>provided</scope>
</dependency>
-
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-juli</artifactId>
- <version>${tomcat-juli.version}</version>
<scope>provided</scope>
</dependency>
-
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
-
</dependencies>
</project>
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java
index 5001ad2d44..f17b3b2a55 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java
@@ -16,7 +16,6 @@
*/
package org.apache.logging.log4j.core.config;
-import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -33,28 +32,21 @@ import java.lang.management.OperatingSystemMXBean;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
-import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
public class ConfigurationSourceTest {
- /**
- * The path inside the jar created by {@link #prepareJarConfigURL}
containing the configuration.
- */
- public static final String PATH_IN_JAR = "/config/console.xml";
-
- private static final String CONFIG_FILE =
"/config/ConfigurationSourceTest.xml";
-
- @TempDir
- private Path tempDir;
+ private static final Path JAR_FILE = Paths.get("target", "test-classes",
"jarfile.jar");
+ private static final Path CONFIG_FILE = Paths.get("target",
"test-classes", "log4j2-console.xml");
+ private static final byte[] buffer = new byte[1024];
@Test
- void testJira_LOG4J2_2770_byteArray() throws Exception {
+ public void testJira_LOG4J2_2770_byteArray() throws Exception {
final ConfigurationSource configurationSource =
new ConfigurationSource(new ByteArrayInputStream(new byte[]
{'a', 'b'}));
assertNotNull(configurationSource.resetInputStream());
@@ -62,19 +54,20 @@ public class ConfigurationSourceTest {
/**
* Checks if the usage of 'jar:' URLs does not increase the file descriptor
- * count, and the jar file can be deleted.
+ * count and the jar file can be deleted.
+ *
+ * @throws Exception
*/
@Test
- void testNoJarFileLeak() throws Exception {
- final Path jarFile = prepareJarConfigURL(tempDir);
- final URL jarConfigURL = new URL("jar:" + jarFile.toUri().toURL() +
"!" + PATH_IN_JAR);
+ public void testNoJarFileLeak() throws Exception {
+ final URL jarConfigURL = prepareJarConfigURL();
final long expected = getOpenFileDescriptorCount();
UrlConnectionFactory.createConnection(jarConfigURL).getInputStream().close();
// This can only fail on UNIX
assertEquals(expected, getOpenFileDescriptorCount());
// This can only fail on Windows
try {
- Files.delete(jarFile);
+ Files.delete(JAR_FILE);
} catch (IOException e) {
fail(e);
}
@@ -82,8 +75,7 @@ public class ConfigurationSourceTest {
@Test
public void testLoadConfigurationSourceFromJarFile() throws Exception {
- final Path jarFile = prepareJarConfigURL(tempDir);
- final URL jarConfigURL = new URL("jar:" + jarFile.toUri().toURL() +
"!" + PATH_IN_JAR);
+ final URL jarConfigURL = prepareJarConfigURL();
final long expectedFdCount = getOpenFileDescriptorCount();
ConfigurationSource configSource =
ConfigurationSource.fromUri(jarConfigURL.toURI());
assertNotNull(configSource);
@@ -98,7 +90,7 @@ public class ConfigurationSourceTest {
assertEquals(expectedFdCount, getOpenFileDescriptorCount());
// This can only fail on Windows
try {
- Files.delete(jarFile);
+ Files.delete(JAR_FILE);
} catch (IOException e) {
fail(e);
}
@@ -112,18 +104,22 @@ public class ConfigurationSourceTest {
return 0L;
}
- public static Path prepareJarConfigURL(Path dir) throws IOException {
- Path jarFile = dir.resolve("jarFile.jar");
- final Manifest manifest = new Manifest();
- manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION,
"1.0");
- try (final OutputStream os = Files.newOutputStream(jarFile);
- final JarOutputStream jar = new JarOutputStream(os, manifest);
- final InputStream config =
-
requireNonNull(ConfigurationSourceTest.class.getResourceAsStream(CONFIG_FILE)))
{
- final JarEntry jarEntry = new JarEntry("config/console.xml");
- jar.putNextEntry(jarEntry);
- IOUtils.copy(config, os);
+ public static URL prepareJarConfigURL() throws IOException {
+ if (!Files.exists(JAR_FILE)) {
+ final Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION,
"1.0");
+ try (final OutputStream os = Files.newOutputStream(JAR_FILE);
+ final JarOutputStream jar = new JarOutputStream(os,
manifest);
+ final InputStream config =
Files.newInputStream(CONFIG_FILE)) {
+ final JarEntry jarEntry = new JarEntry("config/console.xml");
+ jar.putNextEntry(jarEntry);
+ int len;
+ while ((len = config.read(buffer)) != -1) {
+ jar.write(buffer, 0, len);
+ }
+ jar.closeEntry();
+ }
}
- return jarFile;
+ return new URL("jar:" + JAR_FILE.toUri().toURL() +
"!/config/console.xml");
}
}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java
new file mode 100644
index 0000000000..6782948a43
--- /dev/null
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.filter;
+
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Base64;
+import java.util.Enumeration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.test.appender.ListAppender;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.Assert;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junitpioneer.jupiter.RetryingTest;
+
+/**
+ * Unit test for simple App.
+ */
+public class HttpThreadContextMapFilterTest implements
MutableThreadContextMapFilter.FilterConfigUpdateListener {
+
+ private static final String BASIC = "Basic ";
+ private static final String expectedCreds = "log4j:log4j";
+ private static Server server;
+ private static final Base64.Decoder decoder = Base64.getDecoder();
+ private static int port;
+ static final String CONFIG = "log4j2-mutableFilter.xml";
+ static LoggerContext loggerContext = null;
+ static final File targetFile = new
File("target/test-classes/testConfig.json");
+ static final Path target = targetFile.toPath();
+ CountDownLatch updated = new CountDownLatch(1);
+
+ @BeforeAll
+ public static void startServer() throws Exception {
+ try {
+ server = new Server(0);
+ final ServletContextHandler context = new ServletContextHandler();
+ final ServletHolder defaultServ = new ServletHolder("default",
TestServlet.class);
+ defaultServ.setInitParameter("resourceBase",
System.getProperty("user.dir"));
+ defaultServ.setInitParameter("dirAllowed", "true");
+ context.addServlet(defaultServ, "/");
+ server.setHandler(context);
+
+ // Start Server
+ server.start();
+ port = ((ServerConnector)
server.getConnectors()[0]).getLocalPort();
+ try {
+ Files.deleteIfExists(target);
+ } catch (IOException ioe) {
+ // Ignore this.
+ }
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ @AfterAll
+ public static void stopServer() throws Exception {
+ if (server != null) {
+ server.stop();
+ }
+ }
+
+ @AfterEach
+ public void after() {
+ try {
+ Files.deleteIfExists(target);
+ } catch (IOException ioe) {
+ // Ignore this.
+ }
+ if (loggerContext != null) {
+ loggerContext.stop();
+ loggerContext = null;
+ }
+ }
+
+ @RetryingTest(maxAttempts = 5, suspendForMs = 10)
+ public void filterTest() throws Exception {
+ System.setProperty("log4j2.Configuration.allowedProtocols", "http");
+ System.setProperty("logging.auth.username", "log4j");
+ System.setProperty("logging.auth.password", "log4j");
+ System.setProperty("configLocation", "http://localhost:" + port +
"/testConfig.json");
+ ThreadContext.put("loginId", "rgoers");
+ Path source = new
File("target/test-classes/emptyConfig.json").toPath();
+ Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+ final long fileTime = targetFile.lastModified() - 2000;
+ assertTrue(targetFile.setLastModified(fileTime));
+ loggerContext = Configurator.initialize(null, CONFIG);
+ assertNotNull(loggerContext);
+ final Appender app =
loggerContext.getConfiguration().getAppender("List");
+ assertNotNull(app);
+ assertTrue(app instanceof ListAppender);
+ final MutableThreadContextMapFilter filter =
+ (MutableThreadContextMapFilter)
loggerContext.getConfiguration().getFilter();
+ assertNotNull(filter);
+ filter.registerListener(this);
+ final Logger logger = loggerContext.getLogger("Test");
+ logger.debug("This is a test");
+ Assertions.assertEquals(0, ((ListAppender) app).getEvents().size());
+ source = new File("target/test-classes/filterConfig.json").toPath();
+ Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+ assertNotEquals(fileTime, targetFile.lastModified());
+ if (!updated.await(5, TimeUnit.SECONDS)) {
+ fail("File update was not detected");
+ }
+ updated = new CountDownLatch(1);
+ logger.debug("This is a test");
+ Assertions.assertEquals(1, ((ListAppender) app).getEvents().size());
+ Assertions.assertTrue(Files.deleteIfExists(target));
+ if (!updated.await(5, TimeUnit.SECONDS)) {
+ fail("File update for delete was not detected");
+ }
+ }
+
+ public static class TestServlet extends DefaultServlet {
+
+ private static final long serialVersionUID = -2885158530511450659L;
+
+ @Override
+ protected void doGet(final HttpServletRequest request, final
HttpServletResponse response)
+ throws ServletException, IOException {
+ final Enumeration<String> headers =
request.getHeaders(HttpHeader.AUTHORIZATION.toString());
+ if (headers == null) {
+ response.sendError(401, "No Auth header");
+ return;
+ }
+ while (headers.hasMoreElements()) {
+ final String authData = headers.nextElement();
+ Assert.assertTrue("Not a Basic auth header",
authData.startsWith(BASIC));
+ final String credentials = new
String(decoder.decode(authData.substring(BASIC.length())));
+ if (!expectedCreds.equals(credentials)) {
+ response.sendError(401, "Invalid credentials");
+ return;
+ }
+ }
+ if (request.getServletPath().equals("/testConfig.json")) {
+ final File file = new
File("target/test-classes/testConfig.json");
+ if (!file.exists()) {
+ response.sendError(404, "File not found");
+ return;
+ }
+ final long modifiedSince =
request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString());
+ final long lastModified = (file.lastModified() / 1000) * 1000;
+ if (modifiedSince > 0 && lastModified <= modifiedSince) {
+ response.setStatus(304);
+ return;
+ }
+ response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(),
lastModified);
+ response.setContentLengthLong(file.length());
+ Files.copy(file.toPath(), response.getOutputStream());
+ response.getOutputStream().flush();
+ response.setStatus(200);
+ } else {
+ response.sendError(400, "Unsupported request");
+ }
+ }
+ }
+
+ @Override
+ public void onEvent() {
+ updated.countDown();
+ }
+}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
index a2d3919d1d..598ce46ac2 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilterTest.java
@@ -16,228 +16,94 @@
*/
package org.apache.logging.log4j.core.filter;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.logging.log4j.core.net.WireMockUtil.createMapping;
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
-import com.github.tomakehurst.wiremock.client.BasicCredentials;
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
-import com.github.tomakehurst.wiremock.junit5.WireMockTest;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
+import java.io.File;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
-import java.nio.file.attribute.FileTime;
-import java.time.Instant;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.ConfigurationFactory;
-import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.test.appender.ListAppender;
-import org.apache.logging.log4j.test.TestProperties;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.apache.logging.log4j.test.junit.UsingTestProperties;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.api.Assertions;
+import org.junitpioneer.jupiter.RetryingTest;
/**
* Unit test for simple App.
*/
-@SetTestProperty(key = "log4j2.configurationAllowedProtocols", value =
"http,https")
-@SetTestProperty(key = "log4j2.configurationPassword", value = "log4j")
-@SetTestProperty(key = "log4j2.configurationUsername", value = "log4j")
-@UsingTestProperties
-@WireMockTest
-class MutableThreadContextMapFilterTest implements
MutableThreadContextMapFilter.FilterConfigUpdateListener {
+public class MutableThreadContextMapFilterTest implements
MutableThreadContextMapFilter.FilterConfigUpdateListener {
- private static final BasicCredentials CREDENTIALS = new
BasicCredentials("log4j", "log4j");
- private static final String FILE_NAME = "testConfig.json";
- private static final String URL_PATH = "/" + FILE_NAME;
- private static final String JSON = "application/json";
-
- private static final byte[] EMPTY_CONFIG = ("{" //
- + " \"configs\":{}" //
- + "}")
- .getBytes(UTF_8);
- private static final byte[] FILTER_CONFIG = ("{" //
- + " \"configs\": {" //
- + " \"loginId\": [\"rgoers\", \"adam\"]," //
- + " \"corpAcctNumber\": [\"30510263\"]" //
- + " }" //
- + "}")
- .getBytes(UTF_8);
-
- private static final String CONFIG =
"filter/MutableThreadContextMapFilterTest.xml";
- private static LoggerContext loggerContext = null;
- private final ReentrantLock lock = new ReentrantLock();
- private final Condition filterUpdated = lock.newCondition();
- private final Condition resultVerified = lock.newCondition();
- private Exception exception;
+ static final String CONFIG = "log4j2-mutableFilter.xml";
+ static LoggerContext loggerContext = null;
+ static File targetFile = new File("target/test-classes/testConfig.json");
+ static Path target = targetFile.toPath();
+ CountDownLatch updated = new CountDownLatch(1);
@AfterEach
- void cleanup() {
- exception = null;
- ThreadContext.clearMap();
- if (loggerContext != null) {
- loggerContext.stop();
- loggerContext = null;
+ public void after() {
+ try {
+ Files.deleteIfExists(target);
+ } catch (IOException ioe) {
+ // Ignore this.
}
+ ThreadContext.clearMap();
+ loggerContext.stop();
+ loggerContext = null;
}
- @Test
- void file_location_works(TestProperties properties, @TempDir Path dir)
throws Exception {
- // Set up the test file.
- Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
- Instant before = now.minus(1, ChronoUnit.MINUTES);
- Instant after = now.plus(1, ChronoUnit.MINUTES);
- Path testConfig = dir.resolve("testConfig.json");
- properties.setProperty("configLocation", testConfig.toString());
- try (final InputStream inputStream = new
ByteArrayInputStream(EMPTY_CONFIG)) {
- Files.copy(inputStream, testConfig);
- Files.setLastModifiedTime(testConfig, FileTime.from(before));
- }
- // Setup Log4j
- ConfigurationSource source =
- ConfigurationSource.fromResource(CONFIG,
getClass().getClassLoader());
- Configuration configuration =
ConfigurationFactory.getInstance().getConfiguration(null, source);
- configuration.initialize(); // To create the components
- final ListAppender app = configuration.getAppender("LIST");
- assertThat(app).isNotNull();
- final MutableThreadContextMapFilter filter =
(MutableThreadContextMapFilter) configuration.getFilter();
+ @RetryingTest(maxAttempts = 5, suspendForMs = 10)
+ public void filterTest() throws Exception {
+ System.setProperty("configLocation",
"target/test-classes/testConfig.json");
+ ThreadContext.put("loginId", "rgoers");
+ Path source = new
File("target/test-classes/emptyConfig.json").toPath();
+ Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+ final long fileTime = targetFile.lastModified() - 1000;
+ assertTrue(targetFile.setLastModified(fileTime));
+ loggerContext = Configurator.initialize(null, CONFIG);
+ assertNotNull(loggerContext);
+ final Appender app =
loggerContext.getConfiguration().getAppender("List");
+ assertNotNull(app);
+ assertTrue(app instanceof ListAppender);
+ final MutableThreadContextMapFilter filter =
+ (MutableThreadContextMapFilter)
loggerContext.getConfiguration().getFilter();
assertNotNull(filter);
filter.registerListener(this);
-
- lock.lock();
- try {
- // Starts the configuration
- loggerContext =
Configurator.initialize(getClass().getClassLoader(), configuration);
- assertNotNull(loggerContext);
-
- final Logger logger =
loggerContext.getLogger(MutableThreadContextMapFilterTest.class);
-
- assertThat(filterUpdated.await(20, TimeUnit.SECONDS))
- .as("Initial configuration was loaded")
- .isTrue();
- ThreadContext.put("loginId", "rgoers");
- logger.debug("This is a test");
- assertThat(app.getEvents()).isEmpty();
-
- // Prepare the second test case: updated config
- try (final InputStream inputStream = new
ByteArrayInputStream(FILTER_CONFIG)) {
- Files.copy(inputStream, testConfig,
StandardCopyOption.REPLACE_EXISTING);
- Files.setLastModifiedTime(testConfig, FileTime.from(after));
+ final Logger logger = loggerContext.getLogger("Test");
+ logger.debug("This is a test");
+ Assertions.assertEquals(0, ((ListAppender) app).getEvents().size());
+ source = new File("target/test-classes/filterConfig.json").toPath();
+ String msg = null;
+ boolean copied = false;
+ for (int i = 0; i < 5 && !copied; ++i) {
+ Thread.sleep(100 + (100 * i));
+ try {
+ Files.copy(source, target,
StandardCopyOption.REPLACE_EXISTING);
+ copied = true;
+ } catch (Exception ex) {
+ msg = ex.getMessage();
}
- resultVerified.signalAll();
-
- assertThat(filterUpdated.await(20, TimeUnit.SECONDS))
- .as("Updated configuration was loaded")
- .isTrue();
- logger.debug("This is a test");
- assertThat(app.getEvents()).hasSize(1);
-
- // Prepare the third test case: removed config
- Files.delete(testConfig);
- resultVerified.signalAll();
-
- assertThat(filterUpdated.await(20, TimeUnit.SECONDS))
- .as("Configuration removal was detected")
- .isTrue();
- logger.debug("This is a test");
- assertThat(app.getEvents()).hasSize(1);
- resultVerified.signalAll();
- } finally {
- lock.unlock();
}
- assertThat(exception).as("Asynchronous exception").isNull();
- }
-
- @Test
- void http_location_works(TestProperties properties, WireMockRuntimeInfo
info) throws Exception {
- WireMock wireMock = info.getWireMock();
- // Setup WireMock
- // The HTTP Last-Modified header has a precision of 1 second
- ZonedDateTime now = LocalDateTime.now().atZone(ZoneOffset.UTC);
- ZonedDateTime before = now.minusMinutes(1);
- ZonedDateTime after = now.plusMinutes(1);
- properties.setProperty("configLocation", info.getHttpBaseUrl() +
URL_PATH);
- // Setup Log4j
- ConfigurationSource source =
- ConfigurationSource.fromResource(CONFIG,
getClass().getClassLoader());
- Configuration configuration =
ConfigurationFactory.getInstance().getConfiguration(null, source);
- configuration.initialize(); // To create the components
- final ListAppender app = configuration.getAppender("LIST");
- assertThat(app).isNotNull();
- final MutableThreadContextMapFilter filter =
(MutableThreadContextMapFilter) configuration.getFilter();
- assertNotNull(filter);
- filter.registerListener(this);
- lock.lock();
- try {
- // Prepare the first test case: original empty config
- wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS,
EMPTY_CONFIG, JSON, before));
- // Starts the configuration
- loggerContext =
Configurator.initialize(getClass().getClassLoader(), configuration);
- assertNotNull(loggerContext);
-
- final Logger logger =
loggerContext.getLogger(MutableThreadContextMapFilterTest.class);
-
- assertThat(filterUpdated.await(2, TimeUnit.SECONDS))
- .as("Initial configuration was loaded")
- .isTrue();
- ThreadContext.put("loginId", "rgoers");
- logger.debug("This is a test");
- assertThat(app.getEvents()).isEmpty();
-
- // Prepare the second test case: updated config
- wireMock.removeMappings();
- wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS,
FILTER_CONFIG, JSON, after));
- resultVerified.signalAll();
-
- assertThat(filterUpdated.await(2, TimeUnit.SECONDS))
- .as("Updated configuration was loaded")
- .isTrue();
- logger.debug("This is a test");
- assertThat(app.getEvents()).hasSize(1);
-
- // Prepare the third test case: removed config
- wireMock.removeMappings();
- resultVerified.signalAll();
-
- assertThat(filterUpdated.await(2, TimeUnit.SECONDS))
- .as("Configuration removal was detected")
- .isTrue();
- logger.debug("This is a test");
- assertThat(app.getEvents()).hasSize(1);
- resultVerified.signalAll();
- } finally {
- lock.unlock();
+ assertTrue(copied, "File not copied: " + msg);
+ assertNotEquals(fileTime, targetFile.lastModified());
+ if (!updated.await(5, TimeUnit.SECONDS)) {
+ fail("File update was not detected");
}
- assertThat(exception).as("Asynchronous exception").isNull();
+ logger.debug("This is a test");
+ Assertions.assertEquals(1, ((ListAppender) app).getEvents().size());
}
@Override
public void onEvent() {
- lock.lock();
- try {
- filterUpdated.signalAll();
- resultVerified.await();
- } catch (final InterruptedException e) {
- exception = e;
- } finally {
- lock.unlock();
- }
+ updated.countDown();
}
}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
index 265c538da8..e6bb7d5928 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
@@ -16,24 +16,20 @@
*/
package org.apache.logging.log4j.core.net;
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static java.util.Objects.requireNonNull;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
import static javax.servlet.http.HttpServletResponse.SC_OK;
-import static
org.apache.logging.log4j.core.config.ConfigurationSourceTest.PATH_IN_JAR;
-import static org.apache.logging.log4j.core.net.WireMockUtil.createMapping;
-import static org.assertj.core.api.Assertions.assertThat;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
-import com.github.tomakehurst.wiremock.client.BasicCredentials;
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
-import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import com.sun.management.UnixOperatingSystemMXBean;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
@@ -42,102 +38,101 @@ import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.ZonedDateTime;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.apache.commons.io.IOUtils;
+import java.util.Base64;
+import java.util.Enumeration;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.ConfigurationSourceTest;
-import org.apache.logging.log4j.core.util.AuthorizationProvider;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.apache.logging.log4j.util.PropertiesUtil;
-import org.junit.jupiter.api.AfterEach;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
-import org.junit.jupiter.api.io.TempDir;
import org.junitpioneer.jupiter.RetryingTest;
/**
* Tests the UrlConnectionFactory
*/
-@WireMockTest
-@SetTestProperty(key = "log4j2.configurationAllowedProtocols", value =
"jar,http")
-class UrlConnectionFactoryTest {
+public class UrlConnectionFactoryTest {
private static final Logger LOGGER =
LogManager.getLogger(UrlConnectionFactoryTest.class);
+ private static final String BASIC = "Basic ";
+ private static final String expectedCreds = "testuser:password";
+ private static Server server;
+ private static final Base64.Decoder decoder = Base64.getDecoder();
+ private static int port;
+ private static final int BUF_SIZE = 1024;
- private static final String URL_PATH = "/log4j2-config.xml";
- private static final BasicCredentials CREDENTIALS = new
BasicCredentials("testUser", "password");
- private static final byte[] CONFIG_FILE_BODY;
- private static final String CONTENT_TYPE = "application/xml";
-
- static {
- try (InputStream input = requireNonNull(
-
UrlConnectionFactoryTest.class.getClassLoader().getResourceAsStream("log4j2-config.xml")))
{
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- IOUtils.copy(input, output);
- CONFIG_FILE_BODY = output.toByteArray();
- } catch (IOException e) {
- throw new AssertionError(e);
+ @BeforeAll
+ public static void startServer() throws Exception {
+ try {
+ server = new Server(0);
+ final ServletContextHandler context = new ServletContextHandler();
+ final ServletHolder defaultServ = new ServletHolder("default",
TestServlet.class);
+ defaultServ.setInitParameter("resourceBase",
System.getProperty("user.dir"));
+ defaultServ.setInitParameter("dirAllowed", "true");
+ context.addServlet(defaultServ, "/");
+ server.setHandler(context);
+
+ // Start Server
+ server.start();
+ port = ((ServerConnector)
server.getConnectors()[0]).getLocalPort();
+ } catch (Throwable ex) {
+ ex.printStackTrace();
+ throw ex;
}
}
- @AfterEach
- void cleanup(WireMockRuntimeInfo info) {
- info.getWireMock().removeMappings();
+ @AfterAll
+ public static void stopServer() throws Exception {
+ server.stop();
}
@Test
- @SetTestProperty(key = "log4j2.configurationUsername", value = "foo")
- @SetTestProperty(key = "log4j2.configurationPassword", value = "bar")
- void testBadCredentials(WireMockRuntimeInfo info) throws Exception {
- WireMock wireMock = info.getWireMock();
- // RFC 1123 format rounds to full seconds
- ZonedDateTime now =
ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
- wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS,
CONFIG_FILE_BODY, CONTENT_TYPE, now));
- final URI uri = new URI(info.getHttpBaseUrl() + URL_PATH);
+ public void testBadCrdentials() throws Exception {
+ System.setProperty("log4j2.Configuration.username", "foo");
+ System.setProperty("log4j2.Configuration.password", "bar");
+ System.setProperty("log4j2.Configuration.allowedProtocols", "http");
+ final URI uri = new URI("http://localhost:" + port +
"/log4j2-config.xml");
final ConfigurationSource source = ConfigurationSource.fromUri(uri);
assertNull(source, "A ConfigurationSource should not have been
returned");
}
@Test
- @SetTestProperty(key = "log4j2.configurationUsername", value = "testUser")
- @SetTestProperty(key = "log4j2.configurationPassword", value = "password")
- public void withAuthentication(WireMockRuntimeInfo info) throws Exception {
- WireMock wireMock = info.getWireMock();
- // RFC 1123 format rounds to full seconds
- ZonedDateTime now =
ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
- wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS,
CONFIG_FILE_BODY, CONTENT_TYPE, now));
- final URI uri = new URI(info.getHttpBaseUrl() + URL_PATH);
+ public void withAuthentication() throws Exception {
+ System.setProperty("log4j2.Configuration.username", "testuser");
+ System.setProperty("log4j2.Configuration.password", "password");
+ System.setProperty("log4j2.Configuration.allowedProtocols", "http");
+ final URI uri = new URI("http://localhost:" + port +
"/log4j2-config.xml");
final ConfigurationSource source = ConfigurationSource.fromUri(uri);
assertNotNull(source, "No ConfigurationSource returned");
final InputStream is = source.getInputStream();
assertNotNull(is, "No data returned");
is.close();
final long lastModified = source.getLastModified();
- assertThat(lastModified).isEqualTo(now.toInstant().toEpochMilli());
int result = verifyNotModified(uri, lastModified);
assertEquals(SC_NOT_MODIFIED, result, "File was modified");
-
- wireMock.removeMappings();
- now = now.plusMinutes(5);
- wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS,
CONFIG_FILE_BODY, CONTENT_TYPE, now));
+ final File file = new File("target/classes/log4j2-config.xml");
+ if (!file.setLastModified(System.currentTimeMillis())) {
+ fail("Unable to set last modified time");
+ }
result = verifyNotModified(uri, lastModified);
assertEquals(SC_OK, result, "File was not modified");
}
private int verifyNotModified(final URI uri, final long
lastModifiedMillis) throws Exception {
- AuthorizationProvider provider =
ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
- HttpURLConnection urlConnection =
- UrlConnectionFactory.createConnection(uri.toURL(),
lastModifiedMillis, null, provider);
+ final HttpURLConnection urlConnection =
+ UrlConnectionFactory.createConnection(uri.toURL(),
lastModifiedMillis, null, null);
urlConnection.connect();
try {
@@ -150,39 +145,20 @@ class UrlConnectionFactoryTest {
@RetryingTest(maxAttempts = 5, suspendForMs = 1000)
@DisabledOnOs(value = OS.WINDOWS, disabledReason = "Fails frequently on
Windows (#2011)")
- @SetTestProperty(key = "log4j2.configurationUsername", value = "testUser")
- @SetTestProperty(key = "log4j2.configurationPassword", value = "password")
- void testNoJarFileLeak(@TempDir Path dir, WireMockRuntimeInfo info) throws
Exception {
- Path jarFile = ConfigurationSourceTest.prepareJarConfigURL(dir);
+ public void testNoJarFileLeak() throws Exception {
+ ConfigurationSourceTest.prepareJarConfigURL();
+ final URL url = new
File("target/test-classes/jarfile.jar").toURI().toURL();
// Retrieve using 'file:'
- URL jarUrl = new URL("jar:" + jarFile.toUri().toURL() + "!" +
PATH_IN_JAR);
+ URL jarUrl = new URL("jar:" + url.toString() + "!/config/console.xml");
long expected = getOpenFileDescriptorCount();
UrlConnectionFactory.createConnection(jarUrl).getInputStream().close();
assertEquals(expected, getOpenFileDescriptorCount());
-
- // Prepare mock
- ByteArrayOutputStream body = new ByteArrayOutputStream();
- try (InputStream inputStream = Files.newInputStream(jarFile)) {
- IOUtils.copy(inputStream, body);
- }
- WireMock wireMock = info.getWireMock();
- wireMock.register(WireMock.get("/jarFile.jar")
- .willReturn(
-
aResponse().withStatus(200).withBodyFile("jarFile.jar").withBody(body.toByteArray())));
// Retrieve using 'http:'
- jarUrl = new URL("jar:" + info.getHttpBaseUrl() + "/jarFile.jar!" +
PATH_IN_JAR);
- // URLConnection leaves JAR files in the temporary directory
- Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir"));
- List<Path> expectedFiles;
- try (Stream<Path> stream = Files.list(tmpDir)) {
- expectedFiles = stream.collect(Collectors.toList());
- }
+ jarUrl = new URL("jar:http://localhost:" + port +
"/jarfile.jar!/config/console.xml");
+ final File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+ expected = tmpDir.list().length;
UrlConnectionFactory.createConnection(jarUrl).getInputStream().close();
- List<Path> actualFiles;
- try (Stream<Path> stream = Files.list(tmpDir)) {
- actualFiles = stream.collect(Collectors.toList());
- }
- assertThat(actualFiles).containsExactlyElementsOf(expectedFiles);
+ assertEquals(expected, tmpDir.list().length, "File descriptor leak");
}
private long getOpenFileDescriptorCount() {
@@ -192,4 +168,53 @@ class UrlConnectionFactoryTest {
}
return 0L;
}
+
+ public static class TestServlet extends DefaultServlet {
+
+ private static final long serialVersionUID = -2885158530511450659L;
+
+ @Override
+ protected void doGet(final HttpServletRequest request, final
HttpServletResponse response)
+ throws ServletException, IOException {
+ final Enumeration<String> headers =
request.getHeaders(HttpHeader.AUTHORIZATION.toString());
+ if (headers == null) {
+ response.sendError(SC_UNAUTHORIZED, "No Auth header");
+ return;
+ }
+ while (headers.hasMoreElements()) {
+ final String authData = headers.nextElement();
+ assertTrue(authData.startsWith(BASIC), "Not a Basic auth
header");
+ final String credentials = new
String(decoder.decode(authData.substring(BASIC.length())));
+ if (!expectedCreds.equals(credentials)) {
+ response.sendError(SC_UNAUTHORIZED, "Invalid credentials");
+ return;
+ }
+ }
+ final String servletPath = request.getServletPath();
+ if (servletPath != null) {
+ File file = new File("target/classes" + servletPath);
+ if (!file.exists()) {
+ file = new File("target/test-classes" + servletPath);
+ }
+ if (!file.exists()) {
+ response.sendError(SC_NOT_FOUND);
+ return;
+ }
+ final long modifiedSince =
request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString());
+ final long lastModified = (file.lastModified() / 1000) * 1000;
+ LOGGER.debug("LastModified: {}, modifiedSince: {}",
lastModified, modifiedSince);
+ if (modifiedSince > 0 && lastModified <= modifiedSince) {
+ response.setStatus(SC_NOT_MODIFIED);
+ return;
+ }
+ response.setDateHeader(HttpHeader.LAST_MODIFIED.toString(),
lastModified);
+ response.setContentLengthLong(file.length());
+ Files.copy(file.toPath(), response.getOutputStream());
+ response.getOutputStream().flush();
+ response.setStatus(SC_OK);
+ } else {
+ response.sendError(SC_BAD_REQUEST, "Unsupported request");
+ }
+ }
+ }
}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.java
deleted file mode 100644
index f865574232..0000000000
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.net;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.absent;
-import static com.github.tomakehurst.wiremock.client.WireMock.after;
-import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
-import static com.github.tomakehurst.wiremock.client.WireMock.before;
-import static com.github.tomakehurst.wiremock.client.WireMock.equalToDateTime;
-import static com.github.tomakehurst.wiremock.client.WireMock.get;
-import static com.github.tomakehurst.wiremock.client.WireMock.notContaining;
-import static com.github.tomakehurst.wiremock.stubbing.StubImport.stubImport;
-import static com.google.common.net.HttpHeaders.AUTHORIZATION;
-import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
-import static com.google.common.net.HttpHeaders.IF_MODIFIED_SINCE;
-import static com.google.common.net.HttpHeaders.LAST_MODIFIED;
-
-import com.github.tomakehurst.wiremock.client.BasicCredentials;
-import com.github.tomakehurst.wiremock.stubbing.StubImport;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-
-public class WireMockUtil {
-
- private static final DateTimeFormatter formatter =
DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
-
- /**
- * Establishes a set of mapping to serve a file
- *
- * @param urlPath The URL path of the served file
- * @param credentials The credentials to use for authentication
- * @param body The body of the file
- * @param contentType The MIME content type of the file
- * @param lastModified The last modification date of the file
- * @return A set of mappings
- */
- public static StubImport createMapping(
- String urlPath, BasicCredentials credentials, byte[] body, String
contentType, ZonedDateTime lastModified) {
- int idx = urlPath.lastIndexOf('/');
- String fileName = idx == -1 ? urlPath : urlPath.substring(idx + 1);
- return stubImport()
- // Lack of authentication data
- .stub(get(anyUrl())
- .withHeader(AUTHORIZATION, absent())
-
.willReturn(aResponse().withStatus(401).withStatusMessage("Not Authenticated")))
- // Wrong authentication data
- .stub(get(anyUrl())
- .withHeader(AUTHORIZATION,
notContaining(credentials.asAuthorizationHeaderValue()))
-
.willReturn(aResponse().withStatus(403).withStatusMessage("Not Authorized")))
- // Serves the file
- .stub(get(urlPath)
- .withBasicAuth(credentials.username,
credentials.password)
- .withHeader(IF_MODIFIED_SINCE,
before(lastModified).or(absent()))
- .willReturn(aResponse()
- .withStatus(200)
- .withBodyFile(fileName)
- .withBody(body)
- .withHeader(LAST_MODIFIED,
formatter.format(lastModified))
- .withHeader(CONTENT_TYPE, contentType)))
- // The file was not updated since lastModified
- .stub(get(urlPath)
- .withBasicAuth(credentials.username,
credentials.password)
- .withHeader(IF_MODIFIED_SINCE,
after(lastModified).or(equalToDateTime(lastModified)))
- .willReturn(
-
aResponse().withStatus(304).withHeader(LAST_MODIFIED,
formatter.format(lastModified))))
- .build();
- }
-}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java
deleted file mode 100644
index 774f36d5aa..0000000000
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.util;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.get;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
-import com.github.tomakehurst.wiremock.junit5.WireMockTest;
-import com.github.tomakehurst.wiremock.stubbing.StubMapping;
-import java.net.URL;
-import java.time.Instant;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.ChronoUnit;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import org.apache.logging.log4j.core.config.AbstractConfiguration;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.ConfigurationListener;
-import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.apache.logging.log4j.core.config.HttpWatcher;
-import org.apache.logging.log4j.core.config.Reconfigurable;
-import org.apache.logging.log4j.core.net.UrlConnectionFactory;
-import org.apache.logging.log4j.test.junit.SetTestProperty;
-import org.junit.jupiter.api.Test;
-
-/**
- * Test the WatchManager
- */
-@SetTestProperty(key = UrlConnectionFactory.ALLOWED_PROTOCOLS, value =
"http,https")
-@WireMockTest
-class HttpWatcherTest {
-
- private static final DateTimeFormatter formatter =
DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
- private static final String XML = "application/xml";
-
- @Test
- void testModified(final WireMockRuntimeInfo info) throws Exception {
- final WireMock wireMock = info.getWireMock();
-
- final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
- List<ConfigurationListener> listeners = singletonList(new
TestConfigurationListener(queue, "log4j-test1.xml"));
- // HTTP Last-Modified is in seconds
- Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
- Instant previous = now.minus(5, ChronoUnit.MINUTES);
- final URL url = new URL(info.getHttpBaseUrl() + "/log4j-test1.xml");
- final Configuration configuration = createConfiguration(url);
-
- final StubMapping stubMapping =
wireMock.register(get("/log4j-test1.xml")
- .willReturn(aResponse()
- .withBodyFile("log4j-test1.xml")
- .withStatus(200)
- .withHeader("Last-Modified",
formatter.format(previous))
- .withHeader("Content-Type", XML)));
- Watcher watcher = new HttpWatcher(configuration, null, listeners,
previous.toEpochMilli());
- watcher.watching(new Source(url));
- try {
- assertThat(watcher.isModified()).as("File was modified").isTrue();
- assertThat(watcher.getLastModified()).as("File modification
time").isEqualTo(previous.toEpochMilli());
- // Check if listeners are correctly called
- // Note: listeners are called asynchronously
- watcher.modified();
- String str = queue.poll(1, TimeUnit.SECONDS);
- assertThat(str).isEqualTo("log4j-test1.xml");
- ConfigurationSource configurationSource =
configuration.getConfigurationSource();
- // Check that the last modified time of the ConfigurationSource
was modified as well
- // See: https://github.com/apache/logging-log4j2/issues/2937
- assertThat(configurationSource.getLastModified())
- .as("Last modification time of current
ConfigurationSource")
- .isEqualTo(0L);
- configurationSource = configurationSource.resetInputStream();
- assertThat(configurationSource.getLastModified())
- .as("Last modification time of next ConfigurationSource")
- .isEqualTo(previous.toEpochMilli());
- } finally {
- wireMock.removeStubMapping(stubMapping);
- }
- }
-
- @Test
- void testNotModified(final WireMockRuntimeInfo info) throws Exception {
- final WireMock wireMock = info.getWireMock();
-
- final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
- final List<ConfigurationListener> listeners =
- singletonList(new TestConfigurationListener(queue,
"log4j-test2.xml"));
- // HTTP Last-Modified is in seconds
- Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
- Instant previous = now.minus(5, ChronoUnit.MINUTES);
- final URL url = new URL(info.getHttpBaseUrl() + "/log4j-test2.xml");
- final Configuration configuration = createConfiguration(url);
-
- final StubMapping stubMapping =
wireMock.register(get("/log4j-test2.xml")
- .willReturn(aResponse()
- .withStatus(304)
- .withHeader("Last-Modified", formatter.format(now) + "
GMT")
- .withHeader("Content-Type", XML)));
- Watcher watcher = new HttpWatcher(configuration, null, listeners,
previous.toEpochMilli());
- watcher.watching(new Source(url));
- try {
- assertThat(watcher.isModified()).as("File was modified").isFalse();
- // If the file was not modified, neither should be the last
modification time
-
assertThat(watcher.getLastModified()).isEqualTo(previous.toEpochMilli());
- // Check that the last modified time of the ConfigurationSource
was not modified either
- ConfigurationSource configurationSource =
configuration.getConfigurationSource();
- assertThat(configurationSource.getLastModified())
- .as("Last modification time of current
ConfigurationSource")
- .isEqualTo(0L);
- configurationSource = configurationSource.resetInputStream();
- assertThat(configurationSource.getLastModified())
- .as("Last modification time of next ConfigurationSource")
- .isEqualTo(0L);
- } finally {
- wireMock.removeStubMapping(stubMapping);
- }
- }
-
- // Creates a configuration with a predefined configuration source
- private static Configuration createConfiguration(URL url) {
- ConfigurationSource configurationSource = new ConfigurationSource(new
Source(url), new byte[0], 0L);
- return new AbstractConfiguration(null, configurationSource) {};
- }
-
- private static class TestConfigurationListener implements
ConfigurationListener {
- private final Queue<String> queue;
- private final String name;
-
- public TestConfigurationListener(final Queue<String> queue, final
String name) {
- this.queue = queue;
- this.name = name;
- }
-
- @Override
- public void onChange(final Reconfigurable reconfigurable) {
- // System.out.println("Reconfiguration detected for " + name);
- queue.add(name);
- }
- }
-}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java
new file mode 100644
index 0000000000..c433f83486
--- /dev/null
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.util;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Queue;
+import java.util.TimeZone;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationListener;
+import org.apache.logging.log4j.core.config.ConfigurationScheduler;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.HttpWatcher;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
+import org.apache.logging.log4j.core.util.datetime.FastDateFormat;
+import org.apache.logging.log4j.test.junit.SetTestProperty;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test the WatchManager
+ */
+@SetTestProperty(key = UrlConnectionFactory.ALLOWED_PROTOCOLS, value =
"http,https")
+@WireMockTest
+public class WatchHttpTest {
+
+ private static final String FORCE_RUN_KEY =
WatchHttpTest.class.getSimpleName() + ".forceRun";
+ private final String file = "log4j-test1.xml";
+ private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+ private static FastDateFormat formatter = FastDateFormat.getInstance("EEE,
dd MMM yyyy HH:mm:ss", UTC);
+ private static final String XML = "application/xml";
+
+ private static final boolean IS_WINDOWS =
PropertiesUtil.getProperties().isOsWindows();
+
+ @Test
+ public void testWatchManager(final WireMockRuntimeInfo info) throws
Exception {
+ assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY));
+ final WireMock wireMock = info.getWireMock();
+
+ final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
+ final List<ConfigurationListener> listeners = new ArrayList<>();
+ listeners.add(new TestConfigurationListener(queue, "log4j-test1.xml"));
+ final Calendar now = Calendar.getInstance(UTC);
+ final Calendar previous = now;
+ previous.add(Calendar.MINUTE, -5);
+ final Configuration configuration = new DefaultConfiguration();
+ final URL url = new URL(info.getHttpBaseUrl() + "/log4j-test1.xml");
+ final StubMapping stubMapping =
wireMock.register(get(urlPathEqualTo("/log4j-test1.xml"))
+ .willReturn(aResponse()
+ .withBodyFile(file)
+ .withStatus(200)
+ .withHeader("Last-Modified",
formatter.format(previous) + " GMT")
+ .withHeader("Content-Type", XML)));
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
+ scheduler.incrementScheduledItems();
+ final WatchManager watchManager = new WatchManager(scheduler);
+ watchManager.setIntervalSeconds(1);
+ scheduler.start();
+ watchManager.start();
+ try {
+ watchManager.watch(
+ new Source(url), new HttpWatcher(configuration, null,
listeners, previous.getTimeInMillis()));
+ final String str = queue.poll(3, TimeUnit.SECONDS);
+ assertNotNull("File change not detected", str);
+ } finally {
+ watchManager.stop();
+ scheduler.stop();
+ wireMock.removeStubMapping(stubMapping);
+ }
+ }
+
+ @Test
+ public void testNotModified(final WireMockRuntimeInfo info) throws
Exception {
+ assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY));
+ final WireMock wireMock = info.getWireMock();
+
+ final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
+ final List<ConfigurationListener> listeners = new ArrayList<>();
+ listeners.add(new TestConfigurationListener(queue, "log4j-test2.xml"));
+ final TimeZone timeZone = TimeZone.getTimeZone("UTC");
+ final Calendar now = Calendar.getInstance(timeZone);
+ final Calendar previous = now;
+ previous.add(Calendar.MINUTE, -5);
+ final Configuration configuration = new DefaultConfiguration();
+ final URL url = new URL(info.getHttpBaseUrl() + "/log4j-test2.xml");
+ final StubMapping stubMapping =
wireMock.register(get(urlPathEqualTo("/log4j-test2.xml"))
+ .willReturn(aResponse()
+ .withBodyFile(file)
+ .withStatus(304)
+ .withHeader("Last-Modified", formatter.format(now) + "
GMT")
+ .withHeader("Content-Type", XML)));
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
+ scheduler.incrementScheduledItems();
+ final WatchManager watchManager = new WatchManager(scheduler);
+ watchManager.setIntervalSeconds(1);
+ scheduler.start();
+ watchManager.start();
+ try {
+ watchManager.watch(
+ new Source(url), new HttpWatcher(configuration, null,
listeners, previous.getTimeInMillis()));
+ final String str = queue.poll(2, TimeUnit.SECONDS);
+ assertNull("File changed.", str);
+ } finally {
+ watchManager.stop();
+ scheduler.stop();
+ wireMock.removeStubMapping(stubMapping);
+ }
+ }
+
+ private static class TestConfigurationListener implements
ConfigurationListener {
+ private final Queue<String> queue;
+ private final String name;
+
+ public TestConfigurationListener(final Queue<String> queue, final
String name) {
+ this.queue = queue;
+ this.name = name;
+ }
+
+ @Override
+ public void onChange(final Reconfigurable reconfigurable) {
+ // System.out.println("Reconfiguration detected for " + name);
+ queue.add(name);
+ }
+ }
+}
diff --git
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
index 49269d7134..68b3baaca6 100644
---
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
+++
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchManagerTest.java
@@ -18,12 +18,6 @@ package org.apache.logging.log4j.core.util;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import java.io.File;
import java.io.FileOutputStream;
@@ -36,9 +30,6 @@ import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.config.ConfigurationScheduler;
-import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
@@ -49,110 +40,106 @@ import org.junit.jupiter.api.condition.OS;
*/
@DisabledOnOs(OS.WINDOWS)
@EnabledIfSystemProperty(named = "WatchManagerTest.forceRun", matches = "true")
-class WatchManagerTest {
+public class WatchManagerTest {
private final String testFile = "target/testWatchFile";
private final String originalFile = "target/test-classes/log4j-test1.xml";
private final String newFile = "target/test-classes/log4j-test1.yaml";
- private ConfigurationScheduler scheduler;
- private WatchManager watchManager;
-
- @BeforeEach
- void setUp() {
- scheduler = new ConfigurationScheduler();
+ @Test
+ public void testWatchManager() throws Exception {
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
scheduler.incrementScheduledItems();
- watchManager = new WatchManager(scheduler);
+ final WatchManager watchManager = new WatchManager(scheduler);
watchManager.setIntervalSeconds(1);
scheduler.start();
watchManager.start();
- }
-
- @AfterEach
- void tearDown() {
- watchManager.stop();
- scheduler.stop();
- watchManager = null;
- scheduler = null;
- }
-
- @Test
- void testWatchManager() throws Exception {
- final File sourceFile = new File(originalFile);
- Path source = Paths.get(sourceFile.toURI());
- try (final FileOutputStream targetStream = new
FileOutputStream(testFile)) {
- Files.copy(source, targetStream);
+ try {
+ final File sourceFile = new File(originalFile);
+ Path source = Paths.get(sourceFile.toURI());
+ try (final FileOutputStream targetStream = new
FileOutputStream(testFile)) {
+ Files.copy(source, targetStream);
+ }
+ final File updateFile = new File(newFile);
+ final File targetFile = new File(testFile);
+ final BlockingQueue<File> queue = new LinkedBlockingQueue<>();
+ watchManager.watchFile(targetFile, new TestWatcher(queue));
+ Thread.sleep(1000);
+ source = Paths.get(updateFile.toURI());
+ Files.copy(source, Paths.get(targetFile.toURI()),
StandardCopyOption.REPLACE_EXISTING);
+ Thread.sleep(1000);
+ final File f = queue.poll(1, TimeUnit.SECONDS);
+ assertNotNull(f, "File change not detected");
+ } finally {
+ watchManager.stop();
+ scheduler.stop();
}
- final File updateFile = new File(newFile);
- final File targetFile = new File(testFile);
- final BlockingQueue<File> queue = new LinkedBlockingQueue<>();
- watchManager.watchFile(targetFile, new TestWatcher(queue));
- Thread.sleep(1000);
- source = Paths.get(updateFile.toURI());
- Files.copy(source, Paths.get(targetFile.toURI()),
StandardCopyOption.REPLACE_EXISTING);
- Thread.sleep(1000);
- final File f = queue.poll(1, TimeUnit.SECONDS);
- assertNotNull(f, "File change not detected");
}
@Test
- void testWatchManagerReset() throws Exception {
- final File sourceFile = new File(originalFile);
- Path source = Paths.get(sourceFile.toURI());
- try (final FileOutputStream targetStream = new
FileOutputStream(testFile)) {
- Files.copy(source, targetStream);
- }
- final File updateFile = new File(newFile);
- final File targetFile = new File(testFile);
- final BlockingQueue<File> queue = new LinkedBlockingQueue<>();
- watchManager.watchFile(targetFile, new TestWatcher(queue));
- watchManager.stop();
- Thread.sleep(1000);
- source = Paths.get(updateFile.toURI());
- Files.copy(source, Paths.get(targetFile.toURI()),
StandardCopyOption.REPLACE_EXISTING);
- watchManager.reset();
+ public void testWatchManagerReset() throws Exception {
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
+ scheduler.incrementScheduledItems();
+ final WatchManager watchManager = new WatchManager(scheduler);
+ watchManager.setIntervalSeconds(1);
+ scheduler.start();
watchManager.start();
- Thread.sleep(1000);
- final File f = queue.poll(1, TimeUnit.SECONDS);
- assertNull(f, "File change detected");
- }
-
- @Test
- void testWatchManagerResetFile() throws Exception {
- final File sourceFile = new File(originalFile);
- Path source = Paths.get(sourceFile.toURI());
- try (final FileOutputStream targetStream = new
FileOutputStream(testFile)) {
- Files.copy(source, targetStream);
+ try {
+ final File sourceFile = new File(originalFile);
+ Path source = Paths.get(sourceFile.toURI());
+ try (final FileOutputStream targetStream = new
FileOutputStream(testFile)) {
+ Files.copy(source, targetStream);
+ }
+ final File updateFile = new File(newFile);
+ final File targetFile = new File(testFile);
+ final BlockingQueue<File> queue = new LinkedBlockingQueue<>();
+ watchManager.watchFile(targetFile, new TestWatcher(queue));
+ watchManager.stop();
+ Thread.sleep(1000);
+ source = Paths.get(updateFile.toURI());
+ Files.copy(source, Paths.get(targetFile.toURI()),
StandardCopyOption.REPLACE_EXISTING);
+ watchManager.reset();
+ watchManager.start();
+ Thread.sleep(1000);
+ final File f = queue.poll(1, TimeUnit.SECONDS);
+ assertNull(f, "File change detected");
+ } finally {
+ watchManager.stop();
+ scheduler.stop();
}
- final File updateFile = new File(newFile);
- final File targetFile = new File(testFile);
- final BlockingQueue<File> queue = new LinkedBlockingQueue<>();
- watchManager.watchFile(targetFile, new TestWatcher(queue));
- watchManager.stop();
- Thread.sleep(1000);
- source = Paths.get(updateFile.toURI());
- Files.copy(source, Paths.get(targetFile.toURI()),
StandardCopyOption.REPLACE_EXISTING);
- watchManager.reset(targetFile);
- watchManager.start();
- Thread.sleep(1000);
- final File f = queue.poll(1, TimeUnit.SECONDS);
- assertNull(f, "File change detected");
}
- /**
- * Verify the
- */
@Test
- void testWatchManagerCallsWatcher() {
- Watcher watcher = mock(Watcher.class);
- when(watcher.isModified()).thenReturn(false);
- watchManager.watch(new Source(ConfigurationSource.NULL_SOURCE),
watcher);
- verify(watcher, timeout(2000)).isModified();
- verify(watcher, never()).modified();
- when(watcher.isModified()).thenReturn(true);
- clearInvocations(watcher);
- verify(watcher, timeout(2000)).isModified();
- verify(watcher).modified();
+ public void testWatchManagerResetFile() throws Exception {
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
+ scheduler.incrementScheduledItems();
+ final WatchManager watchManager = new WatchManager(scheduler);
+ watchManager.setIntervalSeconds(1);
+ scheduler.start();
+ watchManager.start();
+ try {
+ final File sourceFile = new File(originalFile);
+ Path source = Paths.get(sourceFile.toURI());
+ try (final FileOutputStream targetStream = new
FileOutputStream(testFile)) {
+ Files.copy(source, targetStream);
+ }
+ final File updateFile = new File(newFile);
+ final File targetFile = new File(testFile);
+ final BlockingQueue<File> queue = new LinkedBlockingQueue<>();
+ watchManager.watchFile(targetFile, new TestWatcher(queue));
+ watchManager.stop();
+ Thread.sleep(1000);
+ source = Paths.get(updateFile.toURI());
+ Files.copy(source, Paths.get(targetFile.toURI()),
StandardCopyOption.REPLACE_EXISTING);
+ watchManager.reset(targetFile);
+ watchManager.start();
+ Thread.sleep(1000);
+ final File f = queue.poll(1, TimeUnit.SECONDS);
+ assertNull(f, "File change detected");
+ } finally {
+ watchManager.stop();
+ scheduler.stop();
+ }
}
private static class TestWatcher implements FileWatcher {
diff --git a/log4j-core-test/src/test/resources/emptyConfig.json
b/log4j-core-test/src/test/resources/emptyConfig.json
new file mode 100644
index 0000000000..37086f2b1f
--- /dev/null
+++ b/log4j-core-test/src/test/resources/emptyConfig.json
@@ -0,0 +1,4 @@
+{
+ "configs": {
+ }
+}
\ No newline at end of file
diff --git
a/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml
b/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml
deleted file mode 100644
index 34eb059c4e..0000000000
---
a/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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">
- <MutableThreadContextMapFilter configLocation="${test:configLocation}"
- pollInterval="1"
- onMatch="ACCEPT"
- onMismatch="NEUTRAL"/>
- <Appenders>
- <List name="LIST"/>
- </Appenders>
- <Loggers>
- <Root level="ERROR">
- <AppenderRef ref="LIST"/>
- </Root>
- </Loggers>
-</Configuration>
diff --git a/log4j-core-test/src/test/resources/filterConfig.json
b/log4j-core-test/src/test/resources/filterConfig.json
new file mode 100644
index 0000000000..91c8143ec2
--- /dev/null
+++ b/log4j-core-test/src/test/resources/filterConfig.json
@@ -0,0 +1,6 @@
+{
+ "configs": {
+ "loginId": ["rgoers", "adam"],
+ "corpAcctNumber": ["30510263"]
+ }
+}
\ No newline at end of file
diff --git
a/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml
b/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml
similarity index 69%
rename from
log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml
rename to log4j-core-test/src/test/resources/log4j2-mutableFilter.xml
index 54c7f846f3..2f4e391d13 100644
--- a/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml
+++ b/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml
@@ -15,18 +15,21 @@
~ 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">
+<Configuration name="ConfigTest" status="ERROR">
+ <MutableThreadContextMapFilter configLocation="${sys:configLocation}"
pollInterval="1" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<Appenders>
- <Console name="Console">
- <PatternLayout/>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
</Console>
+ <List name="List">
+ </List>
</Appenders>
<Loggers>
- <Root level="TRACE">
- <AppenderRef ref="Console"/>
+ <Logger name="Test" level="error">
+ <AppenderRef ref="List"/>
+ </Logger>
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index 88f66ac364..ee8e7c6a3c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
@@ -93,7 +93,7 @@ public class LoggerContext extends AbstractLifeCycle
private volatile Configuration configuration = new DefaultConfiguration();
private static final String EXTERNAL_CONTEXT_KEY =
"__EXTERNAL_CONTEXT_KEY__";
- private final ConcurrentMap<String, Object> externalMap = new
ConcurrentHashMap<>();
+ private ConcurrentMap<String, Object> externalMap = new
ConcurrentHashMap<>();
private String contextName;
private volatile URI configLocation;
private Cancellable shutdownCallback;
@@ -128,7 +128,9 @@ public class LoggerContext extends AbstractLifeCycle
*/
public LoggerContext(final String name, final Object externalContext,
final URI configLocn) {
this.contextName = name;
- if (externalContext != null) {
+ if (externalContext == null) {
+ externalMap.remove(EXTERNAL_CONTEXT_KEY);
+ } else {
externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
}
this.configLocation = configLocn;
@@ -147,7 +149,9 @@ public class LoggerContext extends AbstractLifeCycle
justification = "The configLocn comes from a secure source (Log4j
properties)")
public LoggerContext(final String name, final Object externalContext,
final String configLocn) {
this.contextName = name;
- if (externalContext != null) {
+ if (externalContext == null) {
+ externalMap.remove(EXTERNAL_CONTEXT_KEY);
+ } else {
externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
}
if (configLocn != null) {
@@ -168,7 +172,7 @@ public class LoggerContext extends AbstractLifeCycle
if (listeners == null) {
synchronized (this) {
if (listeners == null) {
- listeners = new CopyOnWriteArrayList<>();
+ listeners = new
CopyOnWriteArrayList<LoggerContextShutdownAware>();
}
}
}
@@ -279,7 +283,7 @@ public class LoggerContext extends AbstractLifeCycle
* @param config The new Configuration.
*/
public void start(final Configuration config) {
- LOGGER.info("Starting {}[name={}] with configuration {}...",
getClass().getSimpleName(), getName(), config);
+ LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration
{}...", getName(), this, config);
if (configLock.tryLock()) {
try {
if (this.isInitialized() || this.isStopped()) {
@@ -293,7 +297,7 @@ public class LoggerContext extends AbstractLifeCycle
}
}
setConfiguration(config);
- LOGGER.info("{}[name={}] started with configuration {}.",
getClass().getSimpleName(), getName(), config);
+ LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration
{}.", getName(), this, config);
}
private void setUpShutdownHook() {
@@ -308,6 +312,7 @@ public class LoggerContext extends AbstractLifeCycle
this.shutdownCallback = ((ShutdownCallbackRegistry)
factory).addShutdownCallback(new Runnable() {
@Override
public void run() {
+ @SuppressWarnings("resource")
final LoggerContext context = LoggerContext.this;
LOGGER.debug(
SHUTDOWN_HOOK_MARKER,
@@ -709,7 +714,7 @@ public class LoggerContext extends AbstractLifeCycle
*/
private void reconfigure(final URI configURI) {
final Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY);
- final ClassLoader cl = externalContext instanceof ClassLoader ?
(ClassLoader) externalContext : null;
+ final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ?
(ClassLoader) externalContext : null;
LOGGER.debug(
"Reconfiguration started for context[name={}] at URI {} ({})
with optional ClassLoader: {}",
contextName,
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 4b160f98ef..3c5069f766 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
@@ -281,9 +281,9 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
watchManager.setIntervalSeconds(monitorIntervalSeconds);
if (configSource.getFile() != null) {
final Source cfgSource = new Source(configSource);
- final long lastModified =
configSource.getFile().lastModified();
+ final long lastModifeid =
configSource.getFile().lastModified();
final ConfigurationFileWatcher watcher =
- new ConfigurationFileWatcher(this, reconfigurable,
listeners, lastModified);
+ new ConfigurationFileWatcher(this, reconfigurable,
listeners, lastModifeid);
watchManager.watch(cfgSource, watcher);
} else if (configSource.getURL() != null) {
monitorSource(reconfigurable, configSource);
@@ -318,13 +318,9 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
if (getState() == State.INITIALIZING) {
initialize();
}
- LOGGER.info("Starting configuration {}...", this);
+ LOGGER.debug("Starting configuration {}", this);
this.setStarting();
if (watchManager.getIntervalSeconds() >= 0) {
- LOGGER.info(
- "Start watching for changes to {} every {} seconds",
- getConfigurationSource(),
- watchManager.getIntervalSeconds());
watchManager.start();
}
if (hasAsyncLoggers()) {
@@ -342,7 +338,7 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
root.start(); // LOG4J2-336
}
super.start();
- LOGGER.info("Configuration {} started.", this);
+ LOGGER.debug("Started configuration {} OK.", this);
}
private boolean hasAsyncLoggers() {
@@ -362,9 +358,9 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
*/
@Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
- LOGGER.info("Stopping configuration {}...", this);
this.setStopping();
super.stop(timeout, timeUnit, false);
+ LOGGER.trace("Stopping {}...", this);
// Stop the components that are closest to the application first:
// 1. Notify all LoggerConfigs' ReliabilityStrategy that the
configuration will be stopped.
@@ -460,7 +456,7 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
advertiser.unadvertise(advertisement);
}
setStopped();
- LOGGER.info("Configuration {} stopped.", this);
+ LOGGER.debug("Stopped {} OK", this);
return true;
}
@@ -488,7 +484,6 @@ public abstract class AbstractConfiguration extends
AbstractFilterable implement
// default does nothing, subclasses do work.
}
- @SuppressWarnings("deprecation")
protected Level getDefaultStatus() {
final PropertiesUtil properties = PropertiesUtil.getProperties();
String statusLevel =
properties.getStringProperty(StatusLogger.DEFAULT_STATUS_LISTENER_LEVEL);
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 0368c5d705..fbcf278a07 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
@@ -33,12 +33,10 @@ import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.apache.logging.log4j.core.util.FileUtils;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.Source;
-import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.LoaderUtil;
@@ -47,8 +45,6 @@ import org.apache.logging.log4j.util.LoaderUtil;
*/
public class ConfigurationSource {
- private static final Logger LOGGER = StatusLogger.getLogger();
-
/**
* ConfigurationSource to use with Configurations that do not require a
"real" configuration source.
*/
@@ -62,7 +58,7 @@ public class ConfigurationSource {
private final InputStream stream;
private volatile byte[] data;
- private final Source source;
+ private volatile Source source;
private final long lastModified;
// Set when the configuration has been updated so reset can use it for the
next lastModified timestamp.
private volatile long modifiedMillis;
@@ -84,7 +80,7 @@ public class ConfigurationSource {
} catch (Exception ex) {
// There is a problem with the file. It will be handled somewhere
else.
}
- this.modifiedMillis = this.lastModified = modified;
+ this.lastModified = modified;
}
/**
@@ -104,7 +100,7 @@ public class ConfigurationSource {
} catch (Exception ex) {
// There is a problem with the file. It will be handled somewhere
else.
}
- this.modifiedMillis = this.lastModified = modified;
+ this.lastModified = modified;
}
/**
@@ -115,7 +111,10 @@ public class ConfigurationSource {
* @param url the URL where the input stream originated
*/
public ConfigurationSource(final InputStream stream, final URL url) {
- this(stream, url, 0);
+ this.stream = Objects.requireNonNull(stream, "stream is null");
+ this.data = null;
+ this.lastModified = 0;
+ this.source = new Source(url);
}
/**
@@ -129,7 +128,7 @@ public class ConfigurationSource {
public ConfigurationSource(final InputStream stream, final URL url, final
long lastModified) {
this.stream = Objects.requireNonNull(stream, "stream is null");
this.data = null;
- this.modifiedMillis = this.lastModified = lastModified;
+ this.lastModified = lastModified;
this.source = new Source(url);
}
@@ -155,15 +154,19 @@ public class ConfigurationSource {
Objects.requireNonNull(source, "source is null");
this.data = Objects.requireNonNull(data, "data is null");
this.stream = new ByteArrayInputStream(data);
- this.modifiedMillis = this.lastModified = lastModified;
+ this.lastModified = lastModified;
this.source = source;
}
private ConfigurationSource(final byte[] data, final URL url, final long
lastModified) {
this.data = Objects.requireNonNull(data, "data is null");
this.stream = new ByteArrayInputStream(data);
- this.modifiedMillis = this.lastModified = lastModified;
- this.source = url == null ? null : new Source(url);
+ this.lastModified = lastModified;
+ if (url == null) {
+ this.data = data;
+ } else {
+ this.source = new Source(url);
+ }
}
/**
@@ -196,8 +199,16 @@ public class ConfigurationSource {
return source == null ? null : source.getFile();
}
+ private boolean isFile() {
+ return source == null ? false : source.getFile() != null;
+ }
+
+ private boolean isURL() {
+ return source == null ? false : source.getURI() != null;
+ }
+
private boolean isLocation() {
- return source != null && source.getLocation() != null;
+ return source == null ? false : source.getLocation() != null;
}
/**
@@ -211,11 +222,11 @@ public class ConfigurationSource {
}
/**
- * @deprecated Not used internally, no replacement.
+ * @deprecated Not used internally, no replacement. TODO remove and make
source final.
*/
@Deprecated
- public void setSource(final Source ignored) {
- LOGGER.warn("Ignoring call of deprecated method
`ConfigurationSource#setSource()`.");
+ public void setSource(final Source source) {
+ this.source = source;
}
public void setData(final byte[] data) {
@@ -269,23 +280,16 @@ public class ConfigurationSource {
*/
public ConfigurationSource resetInputStream() throws IOException {
if (source != null && data != null) {
- return new ConfigurationSource(source, data, modifiedMillis);
- }
- File file = getFile();
- if (file != null) {
- return new
ConfigurationSource(Files.newInputStream(file.toPath()), getFile());
- }
- URL url = getURL();
- if (url != null && data != null) {
+ return new ConfigurationSource(source, data, this.lastModified);
+ } else if (isFile()) {
+ return new ConfigurationSource(new FileInputStream(getFile()),
getFile());
+ } else if (isURL() && data != null) {
// Creates a ConfigurationSource without accessing the URL since
the data was provided.
- return new ConfigurationSource(data, url, modifiedMillis);
- }
- URI uri = getURI();
- if (uri != null) {
- return fromUri(uri);
- }
- if (data != null) {
- return new ConfigurationSource(data, null, modifiedMillis);
+ return new ConfigurationSource(data, getURL(), modifiedMillis == 0
? lastModified : modifiedMillis);
+ } else if (isURL()) {
+ return fromUri(getURI());
+ } else if (data != null) {
+ return new ConfigurationSource(data, null, lastModified);
}
return null;
}
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
index b6d7f57d66..08e840d93b 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/HttpWatcher.java
@@ -16,19 +16,16 @@
*/
package org.apache.logging.log4j.core.config;
-import static java.util.Objects.requireNonNull;
-import static
org.apache.logging.log4j.core.util.internal.HttpInputStreamUtil.readStream;
-import static org.apache.logging.log4j.util.Strings.toRootUpperCase;
-
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
-import java.time.Instant;
import java.util.List;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAliases;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
import org.apache.logging.log4j.core.util.AbstractWatcher;
import org.apache.logging.log4j.core.util.AuthorizationProvider;
import org.apache.logging.log4j.core.util.Source;
@@ -47,6 +44,7 @@ public class HttpWatcher extends AbstractWatcher {
private final Logger LOGGER = StatusLogger.getLogger();
+ private final SslConfiguration sslConfiguration;
private AuthorizationProvider authorizationProvider;
private URL url;
private volatile long lastModifiedMillis;
@@ -59,6 +57,7 @@ public class HttpWatcher extends AbstractWatcher {
final List<ConfigurationListener> configurationListeners,
final long lastModifiedMillis) {
super(configuration, reconfigurable, configurationListeners);
+ sslConfiguration = SslConfigurationFactory.getSslConfiguration();
this.lastModifiedMillis = lastModifiedMillis;
}
@@ -104,50 +103,34 @@ public class HttpWatcher extends AbstractWatcher {
try {
final LastModifiedSource source = new
LastModifiedSource(url.toURI(), lastModifiedMillis);
final HttpInputStreamUtil.Result result =
HttpInputStreamUtil.getInputStream(source, authorizationProvider);
- // Update lastModifiedMillis
- // https://github.com/apache/logging-log4j2/issues/2937
- lastModifiedMillis = source.getLastModified();
- // The result of the HTTP/HTTPS request is already logged at
`DEBUG` by `HttpInputStreamUtil`
- // We only log the important events at `INFO` or more.
switch (result.getStatus()) {
case NOT_MODIFIED: {
+ LOGGER.debug("Configuration Not Modified");
return false;
}
case SUCCESS: {
final ConfigurationSource configSource =
getConfiguration().getConfigurationSource();
try {
- // In this case `result.getInputStream()` is not null.
-
configSource.setData(readStream(requireNonNull(result.getInputStream())));
+
configSource.setData(HttpInputStreamUtil.readStream(result.getInputStream()));
configSource.setModifiedMillis(source.getLastModified());
- LOGGER.info(
- "{} resource at {} was modified on {}",
- () -> toRootUpperCase(url.getProtocol()),
- () -> url.toExternalForm(),
- () ->
Instant.ofEpochMilli(source.getLastModified()));
+ LOGGER.debug("Content was modified for {}",
url.toString());
return true;
} catch (final IOException e) {
- // Dead code since result.getInputStream() is a
ByteArrayInputStream
- LOGGER.error("Error accessing configuration at {}",
url.toExternalForm(), e);
+ LOGGER.error("Error accessing configuration at {}:
{}", url, e.getMessage());
return false;
}
}
case NOT_FOUND: {
- LOGGER.warn(
- "{} resource at {} was not found",
- () -> toRootUpperCase(url.getProtocol()),
- () -> url.toExternalForm());
+ LOGGER.info("Unable to locate configuration at {}",
url.toString());
return false;
}
default: {
- LOGGER.warn(
- "Unexpected error retrieving {} resource at {}",
- () -> toRootUpperCase(url.getProtocol()),
- () -> url.toExternalForm());
+ LOGGER.warn("Unexpected error accessing configuration at
{}", url.toString());
return false;
}
}
} catch (final URISyntaxException ex) {
- LOGGER.error("Bad configuration file URL {}",
url.toExternalForm(), ex);
+ LOGGER.error("Bad configuration URL: {}, {}", url.toString(),
ex.getMessage());
return false;
}
}
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 31e42d843f..48502de22e 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
@@ -18,9 +18,9 @@ package org.apache.logging.log4j.core.config.xml;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.ByteArrayInputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -73,6 +73,7 @@ public class XmlConfiguration extends AbstractConfiguration
implements Reconfigu
justification = "The `newDocumentBuilder` method disables DTD
processing.")
public XmlConfiguration(final LoggerContext loggerContext, final
ConfigurationSource configSource) {
super(loggerContext, configSource);
+ final File configFile = configSource.getFile();
byte[] buffer = null;
try {
@@ -174,7 +175,7 @@ public class XmlConfiguration extends AbstractConfiguration
implements Reconfigu
*
* @param xIncludeAware enabled XInclude
* @return a new DocumentBuilder
- * @throws ParserConfigurationException if a DocumentBuilder cannot be
created, which satisfies the configuration requested.
+ * @throws ParserConfigurationException
*/
static DocumentBuilder newDocumentBuilder(final boolean xIncludeAware)
throws ParserConfigurationException {
final DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
@@ -240,7 +241,7 @@ public class XmlConfiguration extends AbstractConfiguration
implements Reconfigu
return;
}
constructHierarchy(rootNode, rootElement);
- if (!status.isEmpty()) {
+ if (status.size() > 0) {
for (final Status s : status) {
LOGGER.error("Error processing element {} ({}): {}", s.name,
s.element, s.errorType);
}
@@ -294,7 +295,7 @@ public class XmlConfiguration extends AbstractConfiguration
implements Reconfigu
}
final String text = buffer.toString().trim();
- if (!text.isEmpty() || (!node.hasChildren() && !node.isRoot())) {
+ if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) {
node.setValue(text);
}
}
@@ -336,8 +337,7 @@ public class XmlConfiguration extends AbstractConfiguration
implements Reconfigu
@Override
public String toString() {
- return getClass().getSimpleName() + "[location=" +
getConfigurationSource() + ", lastModified="
- +
Instant.ofEpochMilli(getConfigurationSource().getLastModified()) + "]";
+ return getClass().getSimpleName() + "[location=" +
getConfigurationSource() + "]";
}
/**
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
index c7c9137715..3ceb4599b7 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MutableThreadContextMapFilter.java
@@ -20,9 +20,9 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
+import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URI;
-import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -65,9 +65,6 @@ import org.apache.logging.log4j.util.PropertiesUtil;
@PerformanceSensitive("allocation")
public class MutableThreadContextMapFilter extends AbstractFilter {
- private static final String HTTP = "http";
- private static final String HTTPS = "https";
-
private static final ObjectMapper MAPPER =
new
ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
private static final KeyValuePair[] EMPTY_ARRAY = {};
@@ -367,29 +364,23 @@ public class MutableThreadContextMapFilter extends
AbstractFilter {
@Override
public void run() {
final ConfigResult result = getConfig(source,
authorizationProvider);
- switch (result.status) {
- case SUCCESS:
- filter = ThreadContextMapFilter.createFilter(result.pairs,
"or", getOnMatch(), getOnMismatch());
- LOGGER.info("MutableThreadContextMapFilter configuration
was updated: {}", filter.toString());
- break;
- case NOT_FOUND:
- if (!(filter instanceof NoOpFilter)) {
- LOGGER.info("MutableThreadContextMapFilter
configuration was removed");
- filter = new NoOpFilter();
- }
- break;
- case EMPTY:
- LOGGER.debug("MutableThreadContextMapFilter configuration
is empty");
+ if (result.status == Status.SUCCESS) {
+ filter = ThreadContextMapFilter.createFilter(result.pairs,
"or", getOnMatch(), getOnMismatch());
+ LOGGER.info("Filter configuration was updated: {}",
filter.toString());
+ for (FilterConfigUpdateListener listener : listeners) {
+ listener.onEvent();
+ }
+ } else if (result.status == Status.NOT_FOUND) {
+ if (!(filter instanceof NoOpFilter)) {
+ LOGGER.info("Filter configuration was removed");
filter = new NoOpFilter();
- break;
- }
- switch (result.status) {
- case SUCCESS:
- case NOT_FOUND:
- case EMPTY:
for (FilterConfigUpdateListener listener : listeners) {
listener.onEvent();
}
+ }
+ } else if (result.status == Status.EMPTY) {
+ LOGGER.debug("Filter configuration is empty");
+ filter = new NoOpFilter();
}
}
}
@@ -398,7 +389,7 @@ public class MutableThreadContextMapFilter extends
AbstractFilter {
value = "PATH_TRAVERSAL_IN",
justification = "The location of the file comes from a
configuration value.")
private static LastModifiedSource getSource(final String configLocation) {
- LastModifiedSource source;
+ LastModifiedSource source = null;
try {
final URI uri = new URI(configLocation);
if (uri.getScheme() != null) {
@@ -417,15 +408,14 @@ public class MutableThreadContextMapFilter extends
AbstractFilter {
final LastModifiedSource source, final AuthorizationProvider
authorizationProvider) {
final File inputFile = source.getFile();
InputStream inputStream = null;
- HttpInputStreamUtil.Result result;
+ HttpInputStreamUtil.Result result = null;
final long lastModified = source.getLastModified();
- URI uri = source.getURI();
if (inputFile != null && inputFile.exists()) {
try {
final long modified = inputFile.lastModified();
if (modified > lastModified) {
source.setLastModified(modified);
- inputStream = Files.newInputStream(inputFile.toPath());
+ inputStream = new FileInputStream(inputFile);
result = new HttpInputStreamUtil.Result(Status.SUCCESS);
} else {
result = new
HttpInputStreamUtil.Result(Status.NOT_MODIFIED);
@@ -433,7 +423,7 @@ public class MutableThreadContextMapFilter extends
AbstractFilter {
} catch (Exception ex) {
result = new HttpInputStreamUtil.Result(Status.ERROR);
}
- } else if (uri != null && (HTTP.equalsIgnoreCase(uri.getScheme()) ||
HTTPS.equalsIgnoreCase(uri.getScheme()))) {
+ } else if (source.getURI() != null) {
try {
result = HttpInputStreamUtil.getInputStream(source,
authorizationProvider);
inputStream = result.getInputStream();
@@ -450,7 +440,7 @@ public class MutableThreadContextMapFilter extends
AbstractFilter {
final KeyValuePairConfig keyValuePairConfig =
MAPPER.readValue(inputStream, KeyValuePairConfig.class);
if (keyValuePairConfig != null) {
final Map<String, String[]> configs =
keyValuePairConfig.getConfigs();
- if (configs != null && !configs.isEmpty()) {
+ if (configs != null && configs.size() > 0) {
final List<KeyValuePair> pairs = new ArrayList<>();
for (Map.Entry<String, String[]> entry :
configs.entrySet()) {
final String key = entry.getKey();
@@ -462,7 +452,7 @@ public class MutableThreadContextMapFilter extends
AbstractFilter {
}
}
}
- if (!pairs.isEmpty()) {
+ if (pairs.size() > 0) {
configResult.pairs = pairs.toArray(EMPTY_ARRAY);
configResult.status = Status.SUCCESS;
} else {
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 7165bfe6f1..15e9009b75 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.Cardinality;
import aQute.bnd.annotation.Resolution;
import aQute.bnd.annotation.spi.ServiceConsumer;
import java.io.File;
-import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -50,9 +49,8 @@ import org.apache.logging.log4j.util.ServiceLoaderUtil;
@ServiceConsumer(value = WatchEventService.class, resolution =
Resolution.OPTIONAL, cardinality = Cardinality.MULTIPLE)
public class WatchManager extends AbstractLifeCycle {
- private static final class ConfigurationMonitor {
+ private final class ConfigurationMonitor {
private final Watcher watcher;
- // Only used for logging
private volatile long lastModifiedMillis;
public ConfigurationMonitor(final long lastModifiedMillis, final
Watcher watcher) {
@@ -78,6 +76,7 @@ public class WatchManager extends AbstractLifeCycle {
private static final long LOW_MASK = 0xffffffffL;
private static final long MID_MASK = 0xffff00000000L;
private static final long HIGH_MASK = 0xfff000000000000L;
+ private static final int NODE_SIZE = 8;
private static final int SHIFT_2 = 16;
private static final int SHIFT_4 = 32;
private static final int SHIFT_6 = 48;
@@ -85,6 +84,8 @@ public class WatchManager extends AbstractLifeCycle {
private static final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH =
0x01b21dd213814000L;
private static final AtomicInteger COUNT = new AtomicInteger(0);
private static final long TYPE1 = 0x1000L;
+ private static final byte VARIANT = (byte) 0x80;
+ private static final int SEQUENCE_MASK = 0x3FFF;
public static UUID get() {
final long time =
@@ -111,11 +112,15 @@ public class WatchManager extends AbstractLifeCycle {
final ConfigurationMonitor monitor = entry.getValue();
if (monitor.getWatcher().isModified()) {
final long lastModified =
monitor.getWatcher().getLastModified();
- logger.info(
- "Configuration source at {} was modified on {},
previous modification was on {}",
- () -> source,
- () -> Instant.ofEpochMilli(lastModified),
- () ->
Instant.ofEpochMilli(monitor.lastModifiedMillis));
+ if (logger.isInfoEnabled()) {
+ logger.info(
+ "Source '{}' was modified on {} ({}), previous
modification was on {} ({})",
+ source,
+ millisToString(lastModified),
+ lastModified,
+ millisToString(monitor.lastModifiedMillis),
+ monitor.lastModifiedMillis);
+ }
monitor.lastModifiedMillis = lastModified;
monitor.getWatcher().modified();
}
@@ -195,7 +200,7 @@ public class WatchManager extends AbstractLifeCycle {
}
public boolean hasEventListeners() {
- return !eventServiceList.isEmpty();
+ return eventServiceList.size() > 0;
}
private String millisToString(final long millis) {
diff --git
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
index 2c25ed2d25..c146958f9f 100644
---
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
+++
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/HttpInputStreamUtil.java
@@ -16,24 +16,17 @@
*/
package org.apache.logging.log4j.core.util.internal;
-import static org.apache.logging.log4j.util.Strings.toRootUpperCase;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
-import java.time.Instant;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
import org.apache.logging.log4j.core.util.AuthorizationProvider;
-import org.apache.logging.log4j.core.util.Source;
import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Supplier;
-import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
/**
* Utility method for reading data from an HTTP InputStream.
@@ -43,21 +36,10 @@ public final class HttpInputStreamUtil {
private static final Logger LOGGER = StatusLogger.getLogger();
private static final int NOT_MODIFIED = 304;
private static final int NOT_AUTHORIZED = 401;
- private static final int FORBIDDEN = 403;
private static final int NOT_FOUND = 404;
private static final int OK = 200;
private static final int BUF_SIZE = 1024;
- /**
- * Retrieves an HTTP resource if it has been modified.
- * <p>
- * Side effects: if the request is successful, the last modified time
of the {@code source}
- * parameter is modified.
- * </p>
- * @param source The location of the HTTP resource
- * @param authorizationProvider The authentication data for the HTTP
request
- * @return A {@link Result} object containing the status code and body of
the response
- */
public static Result getInputStream(
final LastModifiedSource source, final AuthorizationProvider
authorizationProvider) {
final Result result = new Result();
@@ -73,16 +55,12 @@ public final class HttpInputStreamUtil {
final int code = connection.getResponseCode();
switch (code) {
case NOT_MODIFIED: {
- LOGGER.debug(
- "{} resource {}: not modified since {}",
- formatProtocol(source),
- () -> source,
- () -> Instant.ofEpochMilli(lastModified));
+ LOGGER.debug("Configuration not modified");
result.status = Status.NOT_MODIFIED;
return result;
}
case NOT_FOUND: {
- LOGGER.debug("{} resource {}: not found",
formatProtocol(source), () -> source);
+ LOGGER.debug("Unable to access {}: Not Found",
source.toString());
result.status = Status.NOT_FOUND;
return result;
}
@@ -90,65 +68,45 @@ public final class HttpInputStreamUtil {
try (final InputStream is =
connection.getInputStream()) {
source.setLastModified(connection.getLastModified());
LOGGER.debug(
- "{} resource {}: last modified on {}",
- formatProtocol(source),
- () -> source,
- () ->
Instant.ofEpochMilli(connection.getLastModified()));
+ "Content was modified for {}. previous
lastModified: {}, new lastModified: {}",
+ source.toString(),
+ lastModified,
+ connection.getLastModified());
result.status = Status.SUCCESS;
- result.bytes = readStream(is);
+ result.inputStream = new
ByteArrayInputStream(readStream(is));
return result;
} catch (final IOException e) {
try (final InputStream es =
connection.getErrorStream()) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(
- "Error accessing {} resource at
{}: {}",
- formatProtocol(source).get(),
- source,
- readStream(es),
- e);
- }
+ LOGGER.info(
+ "Error accessing configuration at {}:
{}", source.toString(), readStream(es));
} catch (final IOException ioe) {
- LOGGER.debug(
- "Error accessing {} resource at {}",
- formatProtocol(source),
- () -> source,
- () -> e);
+ LOGGER.error(
+ "Error accessing configuration at {}:
{}", source.toString(), e.getMessage());
}
- throw new ConfigurationException("Unable to access
" + source, e);
+ throw new ConfigurationException("Unable to access
" + source.toString(), e);
}
}
case NOT_AUTHORIZED: {
- throw new ConfigurationException("Authentication
required for " + source);
- }
- case FORBIDDEN: {
- throw new ConfigurationException("Access denied to " +
source);
+ throw new ConfigurationException("Authorization
failed");
}
default: {
if (code < 0) {
- LOGGER.debug("{} resource {}: invalid response
code", formatProtocol(source), source);
+ LOGGER.info("Invalid response code returned");
} else {
- LOGGER.debug(
- "{} resource {}: unexpected response code
{}",
- formatProtocol(source),
- source,
- code);
+ LOGGER.info("Unexpected response code returned
{}", code);
}
- throw new ConfigurationException("Unable to access " +
source);
+ throw new ConfigurationException("Unable to access " +
source.toString());
}
}
} finally {
connection.disconnect();
}
} catch (IOException e) {
- LOGGER.debug("Error accessing {} resource at {}",
formatProtocol(source), source, e);
- throw new ConfigurationException("Unable to access " + source, e);
+ LOGGER.warn("Error accessing {}: {}", source.toString(),
e.getMessage());
+ throw new ConfigurationException("Unable to access " +
source.toString(), e);
}
}
- private static Supplier<String> formatProtocol(Source source) {
- return () -> toRootUpperCase(source.getURI().getScheme());
- }
-
public static byte[] readStream(final InputStream is) throws IOException {
final ByteArrayOutputStream result = new ByteArrayOutputStream();
final byte[] buffer = new byte[BUF_SIZE];
@@ -159,29 +117,19 @@ public final class HttpInputStreamUtil {
return result.toByteArray();
}
- @NullMarked
public static class Result {
- private byte @Nullable [] bytes = null;
+ private InputStream inputStream;
private Status status;
- public Result() {
- this(Status.ERROR);
- }
+ public Result() {}
public Result(final Status status) {
this.status = status;
}
- /**
- * Returns the data if the status is {@link Status#SUCCESS}.
- * <p>
- * In any other case the result is {@code null}.
- * </p>
- * @return The contents of the HTTP response or null if empty.
- */
- public @Nullable InputStream getInputStream() {
- return bytes != null ? new ByteArrayInputStream(bytes) : null;
+ public InputStream getInputStream() {
+ return inputStream;
}
public Status getStatus() {
diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml
index 1faa00a710..e41e585c50 100644
--- a/log4j-parent/pom.xml
+++ b/log4j-parent/pom.xml
@@ -105,6 +105,7 @@
<jconsole.version>1.7.0</jconsole.version>
<jctools.version>4.0.5</jctools.version>
<jeromq.version>0.6.0</jeromq.version>
+ <jetty.version>9.4.55.v20240627</jetty.version>
<jmdns.version>3.5.12</jmdns.version>
<jmh.version>1.37</jmh.version>
<json-unit.version>2.40.1</json-unit.version>
@@ -130,6 +131,7 @@
<spring-boot.version>2.7.18</spring-boot.version>
<spring-framework.version>5.3.39</spring-framework.version>
<system-stubs.version>2.0.3</system-stubs.version>
+ <tomcat-juli.version>10.0.27</tomcat-juli.version>
<velocity.version>1.7</velocity.version>
<wiremock.version>2.35.2</wiremock.version>
<xmlunit.version>2.10.0</xmlunit.version>
@@ -195,6 +197,14 @@
<scope>import</scope>
</dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-bom</artifactId>
+ <version>${jetty.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
@@ -776,6 +786,12 @@
<version>${system-stubs.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-juli</artifactId>
+ <version>${tomcat-juli.version}</version>
+ </dependency>
+
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
diff --git a/src/changelog/.2.x.x/2937-http-watcher.xml
b/src/changelog/.2.x.x/2937-http-watcher.xml
deleted file mode 100644
index ec3e1a1426..0000000000
--- a/src/changelog/.2.x.x/2937-http-watcher.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="https://logging.apache.org/xml/ns"
- xsi:schemaLocation="https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
- type="fixed">
- <issue id="2937"
link="https://github.com/apache/logging-log4j2/issues/2937"/>
- <description format="asciidoc">Fix reloading of the configuration from an
HTTP(S) source</description>
-</entry>