From 47c97a10466fe74aa09d8dbf458dbaa7cf049ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Wed, 18 Sep 2024 12:21:13 +0200 Subject: [PATCH 01/11] Add changelog entry for JTL Docker tests (#2953) --- src/changelog/.2.x.x/2953_enable_docker_for_JTL.xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/changelog/.2.x.x/2953_enable_docker_for_JTL.xml diff --git a/src/changelog/.2.x.x/2953_enable_docker_for_JTL.xml b/src/changelog/.2.x.x/2953_enable_docker_for_JTL.xml new file mode 100644 index 00000000000..4d0f8d434f1 --- /dev/null +++ b/src/changelog/.2.x.x/2953_enable_docker_for_JTL.xml @@ -0,0 +1,8 @@ + + + + Enable Docker-based tests in CI for JSON Template Layout + From 80b6a5921eb52980b3e5897f6387467ca0087811 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 18 Sep 2024 12:25:30 +0200 Subject: [PATCH 02/11] Pin `gradle/develocity-actions` --- .github/workflows/develocity-publish-build-scans.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/develocity-publish-build-scans.yaml b/.github/workflows/develocity-publish-build-scans.yaml index eb8d2b3a84c..597d7777ca4 100644 --- a/.github/workflows/develocity-publish-build-scans.yaml +++ b/.github/workflows/develocity-publish-build-scans.yaml @@ -29,10 +29,12 @@ jobs: actions: write pull-requests: write steps: + - name: Setup Build Scan link capture - uses: gradle/develocity-actions/maven-setup@v1 + uses: gradle/develocity-actions/maven-setup@9f1bf05334de7eb619731d5466c35a153742311d # 1.2 + - name: Publish Build Scans - uses: gradle/develocity-actions/maven-publish-build-scan@v1 + uses: gradle/develocity-actions/maven-publish-build-scan@9f1bf05334de7eb619731d5466c35a153742311d # 1.2 with: develocity-url: 'https://ge.apache.org' develocity-access-key: ${{ secrets.GE_ACCESS_TOKEN }} From be24f928f837d6823cbb919a63d3fa5501fbe381 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 10 Sep 2024 19:46:11 +0200 Subject: [PATCH 03/11] Fix reloading of the configuration from HTTP(S) The `HttpWatcher` didn't propagate the observed last modification time back to the configuration. As a result, each new configuration was already deprecated when it started and the reconfiguration process looped. Closes #2937 Rewrite Jetty tests using WireMock Closes #2813 --- log4j-appserver/pom.xml | 10 + .../core/config/ConfigurationSourceTest.java | 62 +++-- .../HttpThreadContextMapFilterTest.java | 202 -------------- .../MutableThreadContextMapFilterTest.java | 250 ++++++++++++++---- .../core/net/UrlConnectionFactoryTest.java | 213 +++++++-------- .../logging/log4j/core/net/WireMockUtil.java | 84 ++++++ .../log4j/core/util/HttpWatcherTest.java | 160 +++++++++++ .../log4j/core/util/WatchHttpTest.java | 156 ----------- .../log4j/core/util/WatchManagerTest.java | 177 +++++++------ .../ConfigurationSourceTest.xml} | 19 +- .../src/test/resources/emptyConfig.json | 4 - .../MutableThreadContextMapFilterTest.xml | 35 +++ .../src/test/resources/filterConfig.json | 6 - .../logging/log4j/core/LoggerContext.java | 19 +- .../core/config/AbstractConfiguration.java | 17 +- .../core/config/ConfigurationSource.java | 68 +++-- .../log4j/core/config/HttpWatcher.java | 39 ++- .../core/config/xml/XmlConfiguration.java | 12 +- .../filter/MutableThreadContextMapFilter.java | 50 ++-- .../logging/log4j/core/util/WatchManager.java | 23 +- .../util/internal/HttpInputStreamUtil.java | 96 +++++-- log4j-parent/pom.xml | 16 -- src/changelog/.2.x.x/2937-http-watcher.xml | 8 + 23 files changed, 916 insertions(+), 810 deletions(-) delete mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.java create mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java delete mode 100644 log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java rename log4j-core-test/src/test/resources/{log4j2-mutableFilter.xml => config/ConfigurationSourceTest.xml} (69%) delete mode 100644 log4j-core-test/src/test/resources/emptyConfig.json create mode 100644 log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml delete mode 100644 log4j-core-test/src/test/resources/filterConfig.json create mode 100644 src/changelog/.2.x.x/2937-http-watcher.xml diff --git a/log4j-appserver/pom.xml b/log4j-appserver/pom.xml index df0ce0067df..0b9c10b0dd2 100644 --- a/log4j-appserver/pom.xml +++ b/log4j-appserver/pom.xml @@ -52,22 +52,32 @@ org.apache.tomcat.juli;transitive=false, org.eclipse.jetty.util;transitive=false + + + 9.4.56.v20240826 + 10.0.27 + org.eclipse.jetty jetty-util + ${jetty.version} provided + org.apache.tomcat tomcat-juli + ${tomcat-juli.version} provided + org.apache.logging.log4j log4j-api + 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 f17b3b2a55a..5001ad2d449 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,6 +16,7 @@ */ 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; @@ -32,21 +33,28 @@ 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 { - 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]; + /** + * 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; @Test - public void testJira_LOG4J2_2770_byteArray() throws Exception { + void testJira_LOG4J2_2770_byteArray() throws Exception { final ConfigurationSource configurationSource = new ConfigurationSource(new ByteArrayInputStream(new byte[] {'a', 'b'})); assertNotNull(configurationSource.resetInputStream()); @@ -54,20 +62,19 @@ public void testJira_LOG4J2_2770_byteArray() throws Exception { /** * Checks if the usage of 'jar:' URLs does not increase the file descriptor - * count and the jar file can be deleted. - * - * @throws Exception + * count, and the jar file can be deleted. */ @Test - public void testNoJarFileLeak() throws Exception { - final URL jarConfigURL = prepareJarConfigURL(); + void testNoJarFileLeak() throws Exception { + final Path jarFile = prepareJarConfigURL(tempDir); + final URL jarConfigURL = new URL("jar:" + jarFile.toUri().toURL() + "!" + PATH_IN_JAR); 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(JAR_FILE); + Files.delete(jarFile); } catch (IOException e) { fail(e); } @@ -75,7 +82,8 @@ public void testNoJarFileLeak() throws Exception { @Test public void testLoadConfigurationSourceFromJarFile() throws Exception { - final URL jarConfigURL = prepareJarConfigURL(); + final Path jarFile = prepareJarConfigURL(tempDir); + final URL jarConfigURL = new URL("jar:" + jarFile.toUri().toURL() + "!" + PATH_IN_JAR); final long expectedFdCount = getOpenFileDescriptorCount(); ConfigurationSource configSource = ConfigurationSource.fromUri(jarConfigURL.toURI()); assertNotNull(configSource); @@ -90,7 +98,7 @@ public void testLoadConfigurationSourceFromJarFile() throws Exception { assertEquals(expectedFdCount, getOpenFileDescriptorCount()); // This can only fail on Windows try { - Files.delete(JAR_FILE); + Files.delete(jarFile); } catch (IOException e) { fail(e); } @@ -104,22 +112,18 @@ private long getOpenFileDescriptorCount() { return 0L; } - 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(); - } + 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); } - return new URL("jar:" + JAR_FILE.toUri().toURL() + "!/config/console.xml"); + return jarFile; } } 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 deleted file mode 100644 index 6782948a43d..00000000000 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/filter/HttpThreadContextMapFilterTest.java +++ /dev/null @@ -1,202 +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.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 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 598ce46ac29..a2d3919d1dd 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,94 +16,228 @@ */ package org.apache.logging.log4j.core.filter; -import static org.junit.jupiter.api.Assertions.assertNotEquals; +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.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 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.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.concurrent.CountDownLatch; +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.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.Assertions; -import org.junitpioneer.jupiter.RetryingTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; /** * Unit test for simple App. */ -public class MutableThreadContextMapFilterTest implements MutableThreadContextMapFilter.FilterConfigUpdateListener { +@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 { - 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); + 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; @AfterEach - public void after() { - try { - Files.deleteIfExists(target); - } catch (IOException ioe) { - // Ignore this. - } + void cleanup() { + exception = null; ThreadContext.clearMap(); - loggerContext.stop(); - loggerContext = null; + if (loggerContext != null) { + loggerContext.stop(); + loggerContext = null; + } } - @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(); + @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(); 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(); - 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(); + + 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)); } + 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(); } - 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(); + } + + @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(); } - logger.debug("This is a test"); - Assertions.assertEquals(1, ((ListAppender) app).getEvents().size()); + assertThat(exception).as("Asynchronous exception").isNull(); } @Override public void onEvent() { - updated.countDown(); + lock.lock(); + try { + filterUpdated.signalAll(); + resultVerified.await(); + } catch (final InterruptedException e) { + exception = e; + } finally { + lock.unlock(); + } } } 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 e6bb7d59281..265c538da88 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,20 +16,24 @@ */ package org.apache.logging.log4j.core.net; -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static java.util.Objects.requireNonNull; 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 javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; +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 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.File; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; @@ -38,101 +42,102 @@ import java.net.URI; import java.net.URL; import java.nio.file.Files; -import java.util.Base64; -import java.util.Enumeration; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +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 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.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.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.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 */ -public class UrlConnectionFactoryTest { +@WireMockTest +@SetTestProperty(key = "log4j2.configurationAllowedProtocols", value = "jar,http") +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; - @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; + 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); } } - @AfterAll - public static void stopServer() throws Exception { - server.stop(); + @AfterEach + void cleanup(WireMockRuntimeInfo info) { + info.getWireMock().removeMappings(); } @Test - 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"); + @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); final ConfigurationSource source = ConfigurationSource.fromUri(uri); assertNull(source, "A ConfigurationSource should not have been returned"); } @Test - 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"); + @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); 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"); - final File file = new File("target/classes/log4j2-config.xml"); - if (!file.setLastModified(System.currentTimeMillis())) { - fail("Unable to set last modified time"); - } + + wireMock.removeMappings(); + now = now.plusMinutes(5); + wireMock.importStubMappings(createMapping(URL_PATH, CREDENTIALS, CONFIG_FILE_BODY, CONTENT_TYPE, now)); result = verifyNotModified(uri, lastModified); assertEquals(SC_OK, result, "File was not modified"); } private int verifyNotModified(final URI uri, final long lastModifiedMillis) throws Exception { - final HttpURLConnection urlConnection = - UrlConnectionFactory.createConnection(uri.toURL(), lastModifiedMillis, null, null); + AuthorizationProvider provider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties()); + HttpURLConnection urlConnection = + UrlConnectionFactory.createConnection(uri.toURL(), lastModifiedMillis, null, provider); urlConnection.connect(); try { @@ -145,20 +150,39 @@ private int verifyNotModified(final URI uri, final long lastModifiedMillis) thro @RetryingTest(maxAttempts = 5, suspendForMs = 1000) @DisabledOnOs(value = OS.WINDOWS, disabledReason = "Fails frequently on Windows (#2011)") - public void testNoJarFileLeak() throws Exception { - ConfigurationSourceTest.prepareJarConfigURL(); - final URL url = new File("target/test-classes/jarfile.jar").toURI().toURL(); + @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); // Retrieve using 'file:' - URL jarUrl = new URL("jar:" + url.toString() + "!/config/console.xml"); + URL jarUrl = new URL("jar:" + jarFile.toUri().toURL() + "!" + PATH_IN_JAR); 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:http://localhost:" + port + "/jarfile.jar!/config/console.xml"); - final File tmpDir = new File(System.getProperty("java.io.tmpdir")); - expected = tmpDir.list().length; + 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 expectedFiles; + try (Stream stream = Files.list(tmpDir)) { + expectedFiles = stream.collect(Collectors.toList()); + } UrlConnectionFactory.createConnection(jarUrl).getInputStream().close(); - assertEquals(expected, tmpDir.list().length, "File descriptor leak"); + List actualFiles; + try (Stream stream = Files.list(tmpDir)) { + actualFiles = stream.collect(Collectors.toList()); + } + assertThat(actualFiles).containsExactlyElementsOf(expectedFiles); } private long getOpenFileDescriptorCount() { @@ -168,53 +192,4 @@ private long getOpenFileDescriptorCount() { } 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 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 new file mode 100644 index 00000000000..f8655742320 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/net/WireMockUtil.java @@ -0,0 +1,84 @@ +/* + * 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 new file mode 100644 index 00000000000..774f36d5aa6 --- /dev/null +++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/HttpWatcherTest.java @@ -0,0 +1,160 @@ +/* + * 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 queue = new LinkedBlockingQueue<>(); + List 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 queue = new LinkedBlockingQueue<>(); + final List 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 queue; + private final String name; + + public TestConfigurationListener(final Queue 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 deleted file mode 100644 index c433f834863..00000000000 --- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java +++ /dev/null @@ -1,156 +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 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 queue = new LinkedBlockingQueue<>(); - final List 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 queue = new LinkedBlockingQueue<>(); - final List 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 queue; - private final String name; - - public TestConfigurationListener(final Queue 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 68b3baaca60..49269d7134f 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,6 +18,12 @@ 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; @@ -30,6 +36,9 @@ 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; @@ -40,106 +49,110 @@ */ @DisabledOnOs(OS.WINDOWS) @EnabledIfSystemProperty(named = "WatchManagerTest.forceRun", matches = "true") -public class WatchManagerTest { +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"; - @Test - public void testWatchManager() throws Exception { - final ConfigurationScheduler scheduler = new ConfigurationScheduler(); + private ConfigurationScheduler scheduler; + private WatchManager watchManager; + + @BeforeEach + void setUp() { + scheduler = new ConfigurationScheduler(); scheduler.incrementScheduledItems(); - final WatchManager watchManager = new WatchManager(scheduler); + 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 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(); - } + } + + @AfterEach + void tearDown() { + watchManager.stop(); + scheduler.stop(); + watchManager = null; + scheduler = null; } @Test - 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(); - 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 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(); + 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); } + final File updateFile = new File(newFile); + final File targetFile = new File(testFile); + final BlockingQueue 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 - public void testWatchManagerResetFile() throws Exception { - final ConfigurationScheduler scheduler = new ConfigurationScheduler(); - scheduler.incrementScheduledItems(); - final WatchManager watchManager = new WatchManager(scheduler); - watchManager.setIntervalSeconds(1); - scheduler.start(); + 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 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(); - 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 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(); + 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); } + final File updateFile = new File(newFile); + final File targetFile = new File(testFile); + final BlockingQueue 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(); } private static class TestWatcher implements FileWatcher { diff --git a/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml b/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml similarity index 69% rename from log4j-core-test/src/test/resources/log4j2-mutableFilter.xml rename to log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml index 2f4e391d139..54c7f846f3f 100644 --- a/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml +++ b/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml @@ -15,21 +15,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - + - - + + - - - - - - - + + diff --git a/log4j-core-test/src/test/resources/emptyConfig.json b/log4j-core-test/src/test/resources/emptyConfig.json deleted file mode 100644 index 37086f2b1f6..00000000000 --- a/log4j-core-test/src/test/resources/emptyConfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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 new file mode 100644 index 00000000000..34eb059c4e0 --- /dev/null +++ b/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/log4j-core-test/src/test/resources/filterConfig.json b/log4j-core-test/src/test/resources/filterConfig.json deleted file mode 100644 index 91c8143ec2b..00000000000 --- a/log4j-core-test/src/test/resources/filterConfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "configs": { - "loginId": ["rgoers", "adam"], - "corpAcctNumber": ["30510263"] - } -} \ No newline at end of file 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 ee8e7c6a3cd..88f66ac364d 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 ConcurrentMap externalMap = new ConcurrentHashMap<>(); + private final ConcurrentMap externalMap = new ConcurrentHashMap<>(); private String contextName; private volatile URI configLocation; private Cancellable shutdownCallback; @@ -128,9 +128,7 @@ public LoggerContext(final String name, final Object externalContext) { */ public LoggerContext(final String name, final Object externalContext, final URI configLocn) { this.contextName = name; - if (externalContext == null) { - externalMap.remove(EXTERNAL_CONTEXT_KEY); - } else { + if (externalContext != null) { externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); } this.configLocation = configLocn; @@ -149,9 +147,7 @@ public LoggerContext(final String name, final Object externalContext, final URI 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) { - externalMap.remove(EXTERNAL_CONTEXT_KEY); - } else { + if (externalContext != null) { externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext); } if (configLocn != null) { @@ -172,7 +168,7 @@ public void addShutdownListener(final LoggerContextShutdownAware listener) { if (listeners == null) { synchronized (this) { if (listeners == null) { - listeners = new CopyOnWriteArrayList(); + listeners = new CopyOnWriteArrayList<>(); } } } @@ -283,7 +279,7 @@ public void start() { * @param config The new Configuration. */ public void start(final Configuration config) { - LOGGER.debug("Starting LoggerContext[name={}, {}] with configuration {}...", getName(), this, config); + LOGGER.info("Starting {}[name={}] with configuration {}...", getClass().getSimpleName(), getName(), config); if (configLock.tryLock()) { try { if (this.isInitialized() || this.isStopped()) { @@ -297,7 +293,7 @@ public void start(final Configuration config) { } } setConfiguration(config); - LOGGER.debug("LoggerContext[name={}, {}] started OK with configuration {}.", getName(), this, config); + LOGGER.info("{}[name={}] started with configuration {}.", getClass().getSimpleName(), getName(), config); } private void setUpShutdownHook() { @@ -312,7 +308,6 @@ private void setUpShutdownHook() { this.shutdownCallback = ((ShutdownCallbackRegistry) factory).addShutdownCallback(new Runnable() { @Override public void run() { - @SuppressWarnings("resource") final LoggerContext context = LoggerContext.this; LOGGER.debug( SHUTDOWN_HOOK_MARKER, @@ -714,7 +709,7 @@ public void setConfigLocation(final URI configLocation) { */ private void reconfigure(final URI configURI) { final Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY); - final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null; + final ClassLoader cl = externalContext instanceof ClassLoader ? (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 3c5069f7661..4b160f98ef9 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 @@ protected void initializeWatchers( watchManager.setIntervalSeconds(monitorIntervalSeconds); if (configSource.getFile() != null) { final Source cfgSource = new Source(configSource); - final long lastModifeid = configSource.getFile().lastModified(); + final long lastModified = configSource.getFile().lastModified(); final ConfigurationFileWatcher watcher = - new ConfigurationFileWatcher(this, reconfigurable, listeners, lastModifeid); + new ConfigurationFileWatcher(this, reconfigurable, listeners, lastModified); watchManager.watch(cfgSource, watcher); } else if (configSource.getURL() != null) { monitorSource(reconfigurable, configSource); @@ -318,9 +318,13 @@ public void start() { if (getState() == State.INITIALIZING) { initialize(); } - LOGGER.debug("Starting configuration {}", this); + LOGGER.info("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()) { @@ -338,7 +342,7 @@ public void start() { root.start(); // LOG4J2-336 } super.start(); - LOGGER.debug("Started configuration {} OK.", this); + LOGGER.info("Configuration {} started.", this); } private boolean hasAsyncLoggers() { @@ -358,9 +362,9 @@ private boolean hasAsyncLoggers() { */ @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. @@ -456,7 +460,7 @@ public boolean stop(final long timeout, final TimeUnit timeUnit) { advertiser.unadvertise(advertisement); } setStopped(); - LOGGER.debug("Stopped {} OK", this); + LOGGER.info("Configuration {} stopped.", this); return true; } @@ -484,6 +488,7 @@ public void setup() { // 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 fbcf278a07b..0368c5d705c 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,10 +33,12 @@ 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; @@ -45,6 +47,8 @@ */ public class ConfigurationSource { + private static final Logger LOGGER = StatusLogger.getLogger(); + /** * ConfigurationSource to use with Configurations that do not require a "real" configuration source. */ @@ -58,7 +62,7 @@ public class ConfigurationSource { private final InputStream stream; private volatile byte[] data; - private volatile Source source; + private final 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; @@ -80,7 +84,7 @@ public ConfigurationSource(final InputStream stream, final File file) { } catch (Exception ex) { // There is a problem with the file. It will be handled somewhere else. } - this.lastModified = modified; + this.modifiedMillis = this.lastModified = modified; } /** @@ -100,7 +104,7 @@ public ConfigurationSource(final InputStream stream, final Path path) { } catch (Exception ex) { // There is a problem with the file. It will be handled somewhere else. } - this.lastModified = modified; + this.modifiedMillis = this.lastModified = modified; } /** @@ -111,10 +115,7 @@ public ConfigurationSource(final InputStream stream, final Path path) { * @param url the URL where the input stream originated */ public ConfigurationSource(final InputStream stream, final URL url) { - this.stream = Objects.requireNonNull(stream, "stream is null"); - this.data = null; - this.lastModified = 0; - this.source = new Source(url); + this(stream, url, 0); } /** @@ -128,7 +129,7 @@ public ConfigurationSource(final InputStream stream, final URL url) { public ConfigurationSource(final InputStream stream, final URL url, final long lastModified) { this.stream = Objects.requireNonNull(stream, "stream is null"); this.data = null; - this.lastModified = lastModified; + this.modifiedMillis = this.lastModified = lastModified; this.source = new Source(url); } @@ -154,19 +155,15 @@ public ConfigurationSource(final Source source, final byte[] data, final long la Objects.requireNonNull(source, "source is null"); this.data = Objects.requireNonNull(data, "data is null"); this.stream = new ByteArrayInputStream(data); - this.lastModified = lastModified; + this.modifiedMillis = 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.lastModified = lastModified; - if (url == null) { - this.data = data; - } else { - this.source = new Source(url); - } + this.modifiedMillis = this.lastModified = lastModified; + this.source = url == null ? null : new Source(url); } /** @@ -199,16 +196,8 @@ public File getFile() { 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 ? false : source.getLocation() != null; + return source != null && source.getLocation() != null; } /** @@ -222,11 +211,11 @@ public URL getURL() { } /** - * @deprecated Not used internally, no replacement. TODO remove and make source final. + * @deprecated Not used internally, no replacement. */ @Deprecated - public void setSource(final Source source) { - this.source = source; + public void setSource(final Source ignored) { + LOGGER.warn("Ignoring call of deprecated method `ConfigurationSource#setSource()`."); } public void setData(final byte[] data) { @@ -280,16 +269,23 @@ public InputStream getInputStream() { */ public ConfigurationSource resetInputStream() throws IOException { if (source != 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) { + 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) { // Creates a ConfigurationSource without accessing the URL since the data was provided. - 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 new ConfigurationSource(data, url, modifiedMillis); + } + URI uri = getURI(); + if (uri != null) { + return fromUri(uri); + } + if (data != null) { + return new ConfigurationSource(data, null, modifiedMillis); } 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 08e840d93bf..b6d7f57d663 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,16 +16,19 @@ */ 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; @@ -44,7 +47,6 @@ 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; @@ -57,7 +59,6 @@ public HttpWatcher( final List configurationListeners, final long lastModifiedMillis) { super(configuration, reconfigurable, configurationListeners); - sslConfiguration = SslConfigurationFactory.getSslConfiguration(); this.lastModifiedMillis = lastModifiedMillis; } @@ -103,34 +104,50 @@ private boolean refreshConfiguration() { 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 { - configSource.setData(HttpInputStreamUtil.readStream(result.getInputStream())); + // In this case `result.getInputStream()` is not null. + configSource.setData(readStream(requireNonNull(result.getInputStream()))); configSource.setModifiedMillis(source.getLastModified()); - LOGGER.debug("Content was modified for {}", url.toString()); + LOGGER.info( + "{} resource at {} was modified on {}", + () -> toRootUpperCase(url.getProtocol()), + () -> url.toExternalForm(), + () -> Instant.ofEpochMilli(source.getLastModified())); return true; } catch (final IOException e) { - LOGGER.error("Error accessing configuration at {}: {}", url, e.getMessage()); + // Dead code since result.getInputStream() is a ByteArrayInputStream + LOGGER.error("Error accessing configuration at {}", url.toExternalForm(), e); return false; } } case NOT_FOUND: { - LOGGER.info("Unable to locate configuration at {}", url.toString()); + LOGGER.warn( + "{} resource at {} was not found", + () -> toRootUpperCase(url.getProtocol()), + () -> url.toExternalForm()); return false; } default: { - LOGGER.warn("Unexpected error accessing configuration at {}", url.toString()); + LOGGER.warn( + "Unexpected error retrieving {} resource at {}", + () -> toRootUpperCase(url.getProtocol()), + () -> url.toExternalForm()); return false; } } } catch (final URISyntaxException ex) { - LOGGER.error("Bad configuration URL: {}, {}", url.toString(), ex.getMessage()); + LOGGER.error("Bad configuration file URL {}", url.toExternalForm(), ex); 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 48502de22e5..31e42d843f4 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 @@ 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,7 +73,6 @@ 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 { @@ -175,7 +174,7 @@ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSo * * @param xIncludeAware enabled XInclude * @return a new DocumentBuilder - * @throws ParserConfigurationException + * @throws ParserConfigurationException if a DocumentBuilder cannot be created, which satisfies the configuration requested. */ static DocumentBuilder newDocumentBuilder(final boolean xIncludeAware) throws ParserConfigurationException { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -241,7 +240,7 @@ public void setup() { return; } constructHierarchy(rootNode, rootElement); - if (status.size() > 0) { + if (!status.isEmpty()) { for (final Status s : status) { LOGGER.error("Error processing element {} ({}): {}", s.name, s.element, s.errorType); } @@ -295,7 +294,7 @@ private void constructHierarchy(final Node node, final Element element) { } final String text = buffer.toString().trim(); - if (text.length() > 0 || (!node.hasChildren() && !node.isRoot())) { + if (!text.isEmpty() || (!node.hasChildren() && !node.isRoot())) { node.setValue(text); } } @@ -337,7 +336,8 @@ private Map processAttributes(final Node node, final Element ele @Override public String toString() { - return getClass().getSimpleName() + "[location=" + getConfigurationSource() + "]"; + return getClass().getSimpleName() + "[location=" + getConfigurationSource() + ", lastModified=" + + Instant.ofEpochMilli(getConfigurationSource().getLastModified()) + "]"; } /** 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 3ceb4599b76..c7c9137715c 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.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,6 +65,9 @@ @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 = {}; @@ -364,23 +367,29 @@ private class FileMonitor implements Runnable { @Override public void run() { final ConfigResult result = getConfig(source, authorizationProvider); - 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"); + 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"); 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(); } } } @@ -389,7 +398,7 @@ public void run() { value = "PATH_TRAVERSAL_IN", justification = "The location of the file comes from a configuration value.") private static LastModifiedSource getSource(final String configLocation) { - LastModifiedSource source = null; + LastModifiedSource source; try { final URI uri = new URI(configLocation); if (uri.getScheme() != null) { @@ -408,14 +417,15 @@ private static ConfigResult getConfig( final LastModifiedSource source, final AuthorizationProvider authorizationProvider) { final File inputFile = source.getFile(); InputStream inputStream = null; - HttpInputStreamUtil.Result result = null; + HttpInputStreamUtil.Result result; 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 = new FileInputStream(inputFile); + inputStream = Files.newInputStream(inputFile.toPath()); result = new HttpInputStreamUtil.Result(Status.SUCCESS); } else { result = new HttpInputStreamUtil.Result(Status.NOT_MODIFIED); @@ -423,7 +433,7 @@ private static ConfigResult getConfig( } catch (Exception ex) { result = new HttpInputStreamUtil.Result(Status.ERROR); } - } else if (source.getURI() != null) { + } else if (uri != null && (HTTP.equalsIgnoreCase(uri.getScheme()) || HTTPS.equalsIgnoreCase(uri.getScheme()))) { try { result = HttpInputStreamUtil.getInputStream(source, authorizationProvider); inputStream = result.getInputStream(); @@ -440,7 +450,7 @@ private static ConfigResult getConfig( final KeyValuePairConfig keyValuePairConfig = MAPPER.readValue(inputStream, KeyValuePairConfig.class); if (keyValuePairConfig != null) { final Map configs = keyValuePairConfig.getConfigs(); - if (configs != null && configs.size() > 0) { + if (configs != null && !configs.isEmpty()) { final List pairs = new ArrayList<>(); for (Map.Entry entry : configs.entrySet()) { final String key = entry.getKey(); @@ -452,7 +462,7 @@ private static ConfigResult getConfig( } } } - if (pairs.size() > 0) { + if (!pairs.isEmpty()) { 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 15e9009b75c..7165bfe6f17 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,6 +20,7 @@ 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; @@ -49,8 +50,9 @@ @ServiceConsumer(value = WatchEventService.class, resolution = Resolution.OPTIONAL, cardinality = Cardinality.MULTIPLE) public class WatchManager extends AbstractLifeCycle { - private final class ConfigurationMonitor { + private static final class ConfigurationMonitor { private final Watcher watcher; + // Only used for logging private volatile long lastModifiedMillis; public ConfigurationMonitor(final long lastModifiedMillis, final Watcher watcher) { @@ -76,7 +78,6 @@ private static class LocalUUID { 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; @@ -84,8 +85,6 @@ private static class LocalUUID { 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 = @@ -112,15 +111,11 @@ public void run() { final ConfigurationMonitor monitor = entry.getValue(); if (monitor.getWatcher().isModified()) { final long lastModified = monitor.getWatcher().getLastModified(); - if (logger.isInfoEnabled()) { - logger.info( - "Source '{}' was modified on {} ({}), previous modification was on {} ({})", - source, - millisToString(lastModified), - lastModified, - millisToString(monitor.lastModifiedMillis), - monitor.lastModifiedMillis); - } + logger.info( + "Configuration source at {} was modified on {}, previous modification was on {}", + () -> source, + () -> Instant.ofEpochMilli(lastModified), + () -> Instant.ofEpochMilli(monitor.lastModifiedMillis)); monitor.lastModifiedMillis = lastModified; monitor.getWatcher().modified(); } @@ -200,7 +195,7 @@ public Map getWatchers() { } public boolean hasEventListeners() { - return eventServiceList.size() > 0; + return !eventServiceList.isEmpty(); } 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 c146958f9fe..2c25ed2d255 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,17 +16,24 @@ */ 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. @@ -36,10 +43,21 @@ 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. + *

+ * Side effects: if the request is successful, the last modified time of the {@code source} + * parameter is modified. + *

+ * @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(); @@ -55,12 +73,16 @@ public static Result getInputStream( final int code = connection.getResponseCode(); switch (code) { case NOT_MODIFIED: { - LOGGER.debug("Configuration not modified"); + LOGGER.debug( + "{} resource {}: not modified since {}", + formatProtocol(source), + () -> source, + () -> Instant.ofEpochMilli(lastModified)); result.status = Status.NOT_MODIFIED; return result; } case NOT_FOUND: { - LOGGER.debug("Unable to access {}: Not Found", source.toString()); + LOGGER.debug("{} resource {}: not found", formatProtocol(source), () -> source); result.status = Status.NOT_FOUND; return result; } @@ -68,45 +90,65 @@ public static Result getInputStream( try (final InputStream is = connection.getInputStream()) { source.setLastModified(connection.getLastModified()); LOGGER.debug( - "Content was modified for {}. previous lastModified: {}, new lastModified: {}", - source.toString(), - lastModified, - connection.getLastModified()); + "{} resource {}: last modified on {}", + formatProtocol(source), + () -> source, + () -> Instant.ofEpochMilli(connection.getLastModified())); result.status = Status.SUCCESS; - result.inputStream = new ByteArrayInputStream(readStream(is)); + result.bytes = readStream(is); return result; } catch (final IOException e) { try (final InputStream es = connection.getErrorStream()) { - LOGGER.info( - "Error accessing configuration at {}: {}", source.toString(), readStream(es)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Error accessing {} resource at {}: {}", + formatProtocol(source).get(), + source, + readStream(es), + e); + } } catch (final IOException ioe) { - LOGGER.error( - "Error accessing configuration at {}: {}", source.toString(), e.getMessage()); + LOGGER.debug( + "Error accessing {} resource at {}", + formatProtocol(source), + () -> source, + () -> e); } - throw new ConfigurationException("Unable to access " + source.toString(), e); + throw new ConfigurationException("Unable to access " + source, e); } } case NOT_AUTHORIZED: { - throw new ConfigurationException("Authorization failed"); + throw new ConfigurationException("Authentication required for " + source); + } + case FORBIDDEN: { + throw new ConfigurationException("Access denied to " + source); } default: { if (code < 0) { - LOGGER.info("Invalid response code returned"); + LOGGER.debug("{} resource {}: invalid response code", formatProtocol(source), source); } else { - LOGGER.info("Unexpected response code returned {}", code); + LOGGER.debug( + "{} resource {}: unexpected response code {}", + formatProtocol(source), + source, + code); } - throw new ConfigurationException("Unable to access " + source.toString()); + throw new ConfigurationException("Unable to access " + source); } } } finally { connection.disconnect(); } } catch (IOException e) { - LOGGER.warn("Error accessing {}: {}", source.toString(), e.getMessage()); - throw new ConfigurationException("Unable to access " + source.toString(), e); + LOGGER.debug("Error accessing {} resource at {}", formatProtocol(source), source, e); + throw new ConfigurationException("Unable to access " + source, e); } } + private static Supplier 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]; @@ -117,19 +159,29 @@ public static byte[] readStream(final InputStream is) throws IOException { return result.toByteArray(); } + @NullMarked public static class Result { - private InputStream inputStream; + private byte @Nullable [] bytes = null; private Status status; - public Result() {} + public Result() { + this(Status.ERROR); + } public Result(final Status status) { this.status = status; } - public InputStream getInputStream() { - return inputStream; + /** + * Returns the data if the status is {@link Status#SUCCESS}. + *

+ * In any other case the result is {@code null}. + *

+ * @return The contents of the HTTP response or null if empty. + */ + public @Nullable InputStream getInputStream() { + return bytes != null ? new ByteArrayInputStream(bytes) : null; } public Status getStatus() { diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 0bfb616f0f4..bd2874e9e1b 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -109,7 +109,6 @@ 1.7.0 4.0.5 0.6.0 - 9.4.55.v20240627 3.5.12 1.37 2.40.1 @@ -135,7 +134,6 @@ 2.7.18 5.3.39 2.0.3 - 10.0.27 1.7 2.35.2 2.10.0 @@ -201,14 +199,6 @@ import - - org.eclipse.jetty - jetty-bom - ${jetty.version} - pom - import - - org.junit junit-bom @@ -814,12 +804,6 @@ ${system-stubs.version} - - org.apache.tomcat - tomcat-juli - ${tomcat-juli.version} - - org.apache.velocity velocity diff --git a/src/changelog/.2.x.x/2937-http-watcher.xml b/src/changelog/.2.x.x/2937-http-watcher.xml new file mode 100644 index 00000000000..ec3e1a14263 --- /dev/null +++ b/src/changelog/.2.x.x/2937-http-watcher.xml @@ -0,0 +1,8 @@ + + + + Fix reloading of the configuration from an HTTP(S) source + From 93483ef03e0643da95692b548311b7c0d1fcdf56 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 17 Sep 2024 17:01:35 +0200 Subject: [PATCH 04/11] Bump `logging-parent` to 11.3.0 --- pom.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ca216b5a2ed..6697e584cb0 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.apache.logging logging-parent - 11.2.0 + 11.3.0 @@ -541,6 +541,16 @@ + + + + false + + apache.staging + https://repository.apache.org/content/repositories/orgapachelogging-1296 + + + From bf97552b44601e7cf26ba2fd83900ddeabcfc724 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 17 Sep 2024 17:06:59 +0200 Subject: [PATCH 05/11] Upgrade from Groovy 3 to 4 --- log4j-core-test/pom.xml | 4 ++-- log4j-parent/pom.xml | 20 ++------------------ 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/log4j-core-test/pom.xml b/log4j-core-test/pom.xml index e23fc2c476a..f770ea88703 100644 --- a/log4j-core-test/pom.xml +++ b/log4j-core-test/pom.xml @@ -174,12 +174,12 @@ test - org.codehaus.groovy + org.apache.groovy groovy-dateutil test - org.codehaus.groovy + org.apache.groovy groovy-jsr223 test diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index bd2874e9e1b..e87e3e7309a 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -53,7 +53,6 @@ 2.2.4 0.45.0 4.13.5 - 3.0.2 2.4 3.5.0 @@ -84,7 +83,7 @@ 7.0.5 4.13.1 1.8.0 - 3.0.22 + 4.0.22 33.3.0-jre 2.2.224 3.0 @@ -176,7 +175,7 @@ - org.codehaus.groovy + org.apache.groovy groovy-bom ${groovy.version} pom @@ -983,21 +982,6 @@ org.codehaus.gmavenplus gmavenplus-plugin - ${gmavenplus-plugin.version} - - - org.codehaus.groovy - groovy-ant - ${groovy.version} - runtime - - - org.codehaus.groovy - groovy - ${groovy.version} - runtime - - ban-static-transitive From c96ead3934ca8f3222a9fcb44166014e8dafa358 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Fri, 6 Sep 2024 08:53:43 +0200 Subject: [PATCH 06/11] Switch MongoDB tests to use Docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MongoDB is a binary server. The current `log4j-mongodb` tests download a **generic** binary MongoDB distribution and try to run it. The distribution is not self-contained and requires several libraries (e.g., OpenSSL) to be available on the target host. Those libraries are not always available in the required version: e.g., currently MongoDB needs OpenSSL 1, but OpenSSL 3 is bundled in the most recent Debian. This PR switches from the binary distribution to the usage of the **latest** Docker image available. The advantages of this approach are: - We always test against the newest server version available. - The success of the tests does not depend on the libraries installed on the host. - Tests can run in parallel. In the current approach, parallel tests failed since each one tried to download MongoDB separately. The main disadvantage is that Docker will be required to test `log4j-mongodb`. This is the case for the CI, but individual developers might need to install it too. Co-authored-by: Volkan Yazıcı --- BUILDING.adoc | 7 + log4j-mongodb/pom.xml | 157 ++++++++++++++---- ...Test.java => AbstractMongoDbCappedIT.java} | 11 +- ...st.java => MongoDbAdditionalFieldsIT.java} | 14 +- ...ureTest.java => MongoDbAuthFailureIT.java} | 15 +- ...dLongTest.java => MongoDbCappedIntIT.java} | 16 +- ...dIntTest.java => MongoDbCappedLongIT.java} | 16 +- .../{MongoDbTest.java => MongoDbIT.java} | 14 +- ...sageTest.java => MongoDbMapMessageIT.java} | 14 +- .../log4j/mongodb/MongoDbResolver.java | 135 +-------------- ...solverTest.java => MongoDbResolverIT.java} | 7 +- .../log4j/mongodb/MongoDbTestConstants.java | 3 +- .../resources/MongoDbAdditionalFields.xml | 37 +++++ .../test/resources/MongoDbAuthFailureIT.xml | 34 ++++ ...-capped-int.xml => MongoDbCappedIntIT.xml} | 12 +- ...apped-long.xml => MongoDbCappedLongIT.xml} | 12 +- ...db-additional-fields.xml => MongoDbIT.xml} | 16 +- ...ap-message.xml => MongoDbMapMessageIT.xml} | 12 +- .../src/test/resources/log4j2-mongodb.xml | 29 ---- log4j-mongodb4/pom.xml | 146 +++++++++++++--- ...est.java => AbstractMongoDb4CappedIT.java} | 11 +- ...t.java => MongoDb4AdditionalFieldsIT.java} | 14 +- ...reTest.java => MongoDb4AuthFailureIT.java} | 15 +- ...LongTest.java => MongoDb4CappedIntIT.java} | 16 +- ...IntTest.java => MongoDb4CappedLongIT.java} | 16 +- .../{MongoDb4Test.java => MongoDb4IT.java} | 14 +- ...ageTest.java => MongoDb4MapMessageIT.java} | 14 +- .../log4j/mongodb4/MongoDb4Resolver.java | 137 +-------------- ...olverTest.java => MongoDb4ResolverIT.java} | 7 +- .../log4j/mongodb4/MongoDb4TestConstants.java | 3 +- .../resources/MongoDb4AdditionalFields.xml | 37 +++++ ...-failure.xml => MongoDb4AuthFailureIT.xml} | 12 +- ...capped-int.xml => MongoDb4CappedIntIT.xml} | 12 +- ...pped-long.xml => MongoDb4CappedLongIT.xml} | 12 +- .../src/test/resources/MongoDb4IT.xml | 13 +- ...p-message.xml => MongoDb4MapMessageIT.xml} | 12 +- .../log4j2-mongodb-additional-fields.xml | 33 ---- .../src/test/resources/log4j2-mongodb.xml | 29 ---- log4j-parent/pom.xml | 22 +-- src/changelog/.2.x.x/2229_mongodb_docker.xml | 8 + 40 files changed, 611 insertions(+), 533 deletions(-) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{AbstractMongoDbCappedTest.java => AbstractMongoDbCappedIT.java} (84%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbAdditionalFieldsTest.java => MongoDbAdditionalFieldsIT.java} (88%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbAuthFailureTest.java => MongoDbAuthFailureIT.java} (77%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbCappedLongTest.java => MongoDbCappedIntIT.java} (64%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbCappedIntTest.java => MongoDbCappedLongIT.java} (64%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbTest.java => MongoDbIT.java} (84%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbMapMessageTest.java => MongoDbMapMessageIT.java} (81%) rename log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/{MongoDbResolverTest.java => MongoDbResolverIT.java} (86%) create mode 100644 log4j-mongodb/src/test/resources/MongoDbAdditionalFields.xml create mode 100644 log4j-mongodb/src/test/resources/MongoDbAuthFailureIT.xml rename log4j-mongodb/src/test/resources/{log4j2-mongodb-capped-int.xml => MongoDbCappedIntIT.xml} (70%) rename log4j-mongodb/src/test/resources/{log4j2-mongodb-capped-long.xml => MongoDbCappedLongIT.xml} (71%) rename log4j-mongodb/src/test/resources/{log4j2-mongodb-additional-fields.xml => MongoDbIT.xml} (68%) rename log4j-mongodb/src/test/resources/{log4j2-mongodb-map-message.xml => MongoDbMapMessageIT.xml} (68%) delete mode 100644 log4j-mongodb/src/test/resources/log4j2-mongodb.xml rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{AbstractMongoDb4CappedTest.java => AbstractMongoDb4CappedIT.java} (84%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4AdditionalFieldsTest.java => MongoDb4AdditionalFieldsIT.java} (88%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4AuthFailureTest.java => MongoDb4AuthFailureIT.java} (76%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4CappedLongTest.java => MongoDb4CappedIntIT.java} (64%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4CappedIntTest.java => MongoDb4CappedLongIT.java} (64%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4Test.java => MongoDb4IT.java} (84%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4MapMessageTest.java => MongoDb4MapMessageIT.java} (80%) rename log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/{MongoDb4ResolverTest.java => MongoDb4ResolverIT.java} (86%) create mode 100644 log4j-mongodb4/src/test/resources/MongoDb4AdditionalFields.xml rename log4j-mongodb4/src/test/resources/{log4j2-mongodb-auth-failure.xml => MongoDb4AuthFailureIT.xml} (67%) rename log4j-mongodb4/src/test/resources/{log4j2-mongodb-capped-int.xml => MongoDb4CappedIntIT.xml} (70%) rename log4j-mongodb4/src/test/resources/{log4j2-mongodb-capped-long.xml => MongoDb4CappedLongIT.xml} (71%) rename log4j-mongodb/src/test/resources/log4j2-mongodb-auth-failure.xml => log4j-mongodb4/src/test/resources/MongoDb4IT.xml (68%) rename log4j-mongodb4/src/test/resources/{log4j2-mongodb-map-message.xml => MongoDb4MapMessageIT.xml} (68%) delete mode 100644 log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml delete mode 100644 log4j-mongodb4/src/test/resources/log4j2-mongodb.xml create mode 100644 src/changelog/.2.x.x/2229_mongodb_docker.xml diff --git a/BUILDING.adoc b/BUILDING.adoc index 27d76641b68..98166cae282 100644 --- a/BUILDING.adoc +++ b/BUILDING.adoc @@ -85,6 +85,13 @@ You either need to have a user-level configuration in `~/.m2/toolchains.xml` or ./mvnw verify -Pjava8-tests,!java8-incompat-fixes ---- +[#docker] +=== Docker tests + +Certain tests use Docker to spawn necessary external services. +Docker tests are configured using the `docker` Maven profile, which is activated by default for the CI environment. +You can locally enable this profile by passing a `-P docker` argument to your `./mvnw` commands. + [#website] == Building the website diff --git a/log4j-mongodb/pom.xml b/log4j-mongodb/pom.xml index d1a37d71930..58d21508fb7 100644 --- a/log4j-mongodb/pom.xml +++ b/log4j-mongodb/pom.xml @@ -30,56 +30,59 @@ org.apache.logging.log4j.core - 5.1.3 - - false - true + 5.1.4 + 2.0.15 + org.mongodb bson - ${mongodb5.version} + ${mongodb.version} + org.mongodb mongodb-driver-core - ${mongodb5.version} - - - org.mongodb - mongodb-driver-legacy - ${mongodb5.version} + ${mongodb.version} + org.mongodb mongodb-driver-sync - ${mongodb5.version} + ${mongodb.version} + + org.apache.logging.log4j log4j-api + org.apache.logging.log4j log4j-mongodb4 + org.mongodb bson + org.mongodb mongodb-driver-core + org.mongodb mongodb-driver-sync + org.apache.logging.log4j log4j-api-test @@ -91,6 +94,7 @@ + org.apache.logging.log4j log4j-core-test @@ -102,31 +106,19 @@ + org.apache.commons commons-lang3 test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - - - de.flapdoodle.embed - de.flapdoodle.embed.process - test - - - de.flapdoodle.reverse - de.flapdoodle.reverse - test - + org.junit.jupiter junit-jupiter-api test + @@ -134,8 +126,7 @@ org.apache.maven.plugins maven-surefire-plugin - - 1 + true @@ -145,6 +136,114 @@ + + + + + + docker + + + + + linux + + + env.CI + true + + + + + + + + io.fabric8 + docker-maven-plugin + + all + true + true + + + mongo + mongo:latest + + + + localhost:mongo.port:27017 + + + + + + + + start-mongo + + start + + + + stop-mongo + + stop + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + org.slf4j + slf4j-nop + ${slf4j2.version} + + + + + + integration-test + verify + + + true + + **/*IT.java + + + + OFF + + ${mongo.port} + + + + + + + + + + + + diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/AbstractMongoDbCappedTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/AbstractMongoDbCappedIT.java similarity index 84% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/AbstractMongoDbCappedTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/AbstractMongoDbCappedIT.java index 07e39339941..56cf3a89bee 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/AbstractMongoDbCappedTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/AbstractMongoDbCappedIT.java @@ -23,17 +23,16 @@ import org.apache.logging.log4j.core.LoggerContext; import org.bson.Document; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -public abstract class AbstractMongoDbCappedTest { +abstract class AbstractMongoDbCappedIT { - @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(AbstractMongoDbCappedTest.class); + protected void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(AbstractMongoDbCappedIT.class); logger.info("Hello log"); final MongoDatabase database = mongoClient.getDatabase(MongoDbTestConstants.DATABASE_NAME); Assertions.assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDbTestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); Assertions.assertNotNull(collection); final Document first = collection.find().first(); Assertions.assertNotNull(first); diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAdditionalFieldsTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAdditionalFieldsIT.java similarity index 88% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAdditionalFieldsTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAdditionalFieldsIT.java index c74172925f5..f63ecf4ecfd 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAdditionalFieldsTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAdditionalFieldsIT.java @@ -26,21 +26,25 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Test; @UsingMongoDb -@LoggerContextSource("log4j2-mongodb-additional-fields.xml") -public class MongoDbAdditionalFieldsTest { +@LoggerContextSource("MongoDbAdditionalFields.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbAdditionalFieldsIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDbAdditionalFieldsTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDbAdditionalFieldsIT.class); logger.info("Hello log 1"); logger.info("Hello log 2", new RuntimeException("Hello ex 2")); final MongoDatabase database = mongoClient.getDatabase(MongoDbTestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDbTestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); assertNotNull(collection); final FindIterable found = collection.find(); final Document first = found.first(); diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAuthFailureTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAuthFailureIT.java similarity index 77% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAuthFailureTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAuthFailureIT.java index 3247dc28c06..5a44aa5456e 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAuthFailureTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbAuthFailureIT.java @@ -25,20 +25,25 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Test; @UsingMongoDb -@LoggerContextSource("log4j2-mongodb-auth-failure.xml") -public class MongoDbAuthFailureTest { +@LoggerContextSource("MongoDbAuthFailureIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbAuthFailureIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDbAuthFailureTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDbAuthFailureIT.class); logger.info("Hello log"); final MongoDatabase database = mongoClient.getDatabase(MongoDbTestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDbTestConstants.DATABASE_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); + ; assertNotNull(collection); final Document first = collection.find().first(); assertNull(first); diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntIT.java similarity index 64% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntIT.java index 34f921fe7bf..887132ded40 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntIT.java @@ -16,11 +16,21 @@ */ package org.apache.logging.log4j.mongodb; +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; @UsingMongoDb -@LoggerContextSource("log4j2-mongodb-capped-long.xml") -public class MongoDbCappedLongTest extends AbstractMongoDbCappedTest { +@LoggerContextSource("MongoDbCappedIntIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbCappedIntIT extends AbstractMongoDbCappedIT { - // test is in superclass + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } } diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongIT.java similarity index 64% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongIT.java index 500964aff08..bb6b4e65722 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongIT.java @@ -16,11 +16,21 @@ */ package org.apache.logging.log4j.mongodb; +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; @UsingMongoDb -@LoggerContextSource("log4j2-mongodb-capped-int.xml") -public class MongoDbCappedIntTest extends AbstractMongoDbCappedTest { +@LoggerContextSource("MongoDbCappedLongIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbCappedLongIT extends AbstractMongoDbCappedIT { - // test is in superclass + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } } diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbIT.java similarity index 84% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbIT.java index 5c4bbb750c1..d0f43cc70fd 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbIT.java @@ -26,21 +26,25 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Test; @UsingMongoDb -@LoggerContextSource("log4j2-mongodb.xml") -public class MongoDbTest { +@LoggerContextSource("MongoDbIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDbTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDbIT.class); logger.info("Hello log 1"); logger.info("Hello log 2", new RuntimeException("Hello ex 2")); final MongoDatabase database = mongoClient.getDatabase(MongoDbTestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDbTestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); assertNotNull(collection); final FindIterable found = collection.find(); final Document first = found.first(); diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageIT.java similarity index 81% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageIT.java index 2733554dbd3..b285529c0ce 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbMapMessageIT.java @@ -23,24 +23,28 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @UsingMongoDb -@LoggerContextSource("log4j2-mongodb-map-message.xml") -public class MongoDbMapMessageTest { +@LoggerContextSource("MongoDbMapMessageIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbMapMessageIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDbMapMessageTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDbMapMessageIT.class); final MapMessage mapMessage = new MapMessage<>(); mapMessage.with("SomeName", "SomeValue"); mapMessage.with("SomeInt", 1); logger.info(mapMessage); final MongoDatabase database = mongoClient.getDatabase(MongoDbTestConstants.DATABASE_NAME); Assertions.assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDbTestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); Assertions.assertNotNull(collection); final Document first = collection.find().first(); Assertions.assertNotNull(first); diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolver.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolver.java index 4b1f394b765..1246d48e496 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolver.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolver.java @@ -18,67 +18,19 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; -import de.flapdoodle.embed.mongo.commands.ServerAddress; -import de.flapdoodle.embed.mongo.distribution.Version; -import de.flapdoodle.embed.mongo.packageresolver.Command; -import de.flapdoodle.embed.mongo.transitions.Mongod; -import de.flapdoodle.embed.mongo.transitions.PackageOfCommandDistribution; -import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess; -import de.flapdoodle.embed.mongo.types.DistributionBaseUrl; -import de.flapdoodle.embed.process.config.store.FileSet; -import de.flapdoodle.embed.process.config.store.FileType; -import de.flapdoodle.embed.process.config.store.Package; -import de.flapdoodle.embed.process.distribution.Distribution; -import de.flapdoodle.embed.process.io.ProcessOutput; -import de.flapdoodle.embed.process.io.Processors; -import de.flapdoodle.embed.process.io.StreamProcessor; -import de.flapdoodle.embed.process.types.Name; -import de.flapdoodle.embed.process.types.ProcessConfig; -import de.flapdoodle.os.OSType; -import de.flapdoodle.reverse.TransitionWalker.ReachedState; -import de.flapdoodle.reverse.transitions.Derive; -import de.flapdoodle.reverse.transitions.Start; -import java.util.Objects; import java.util.function.Supplier; -import org.apache.commons.lang3.NotImplementedException; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.test.TestProperties; import org.apache.logging.log4j.test.junit.ExtensionContextAnchor; -import org.apache.logging.log4j.test.junit.TestPropertySource; import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; +import org.apache.logging.log4j.util.PropertiesUtil; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; -public class MongoDbResolver extends TypeBasedParameterResolver implements BeforeAllCallback { +class MongoDbResolver extends TypeBasedParameterResolver implements BeforeAllCallback { - private static final Logger LOGGER = StatusLogger.getLogger(); - private static final String LOGGING_TARGET_PROPERTY = "log4j2.mongoDbLoggingTarget"; - - private static final int BUILDER_TIMEOUT_MILLIS = 30000; - - private static ProcessOutput getProcessOutput(final LoggingTarget loggingTarget, final String label) { - if (loggingTarget != null) { - switch (loggingTarget) { - case STATUS_LOGGER: - return ProcessOutput.builder() - .output(Processors.named( - "[" + label + " output]", new StatusLoggerStreamProcessor(Level.INFO))) - .error(Processors.named( - "[" + label + " error]", new StatusLoggerStreamProcessor(Level.ERROR))) - .commands(new StatusLoggerStreamProcessor(Level.DEBUG)) - .build(); - case CONSOLE: - return ProcessOutput.namedConsole(label); - default: - } - } - throw new NotImplementedException(Objects.toString(loggingTarget)); - } + static final String PORT_PROPERTY = "log4j2.mongo.port"; public MongoDbResolver() { super(MongoClient.class); @@ -86,40 +38,7 @@ public MongoDbResolver() { @Override public void beforeAll(ExtensionContext context) throws Exception { - final TestProperties props = TestPropertySource.createProperties(context); - final Mongod mongod = Mongod.builder() - .processOutput(Derive.given(Name.class) - .state(ProcessOutput.class) - .deriveBy(name -> getProcessOutput( - LoggingTarget.getLoggingTarget(LoggingTarget.STATUS_LOGGER), name.value()))) - .processConfig(Start.to(ProcessConfig.class) - .initializedWith(ProcessConfig.defaults().withStopTimeoutInMillis(BUILDER_TIMEOUT_MILLIS)) - .withTransitionLabel("create default")) - // workaround for https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/issues/309 - .packageOfDistribution(new PackageOfCommandDistribution() { - - @Override - protected Package packageOf( - Command command, Distribution distribution, DistributionBaseUrl baseUrl) { - if (distribution.platform().operatingSystem().type() == OSType.Windows) { - final Package relativePackage = - commandPackageResolver().apply(command).packageFor(distribution); - final FileSet.Builder fileSetBuilder = FileSet.builder() - .addEntry(FileType.Library, "ssleay32.dll") - .addEntry(FileType.Library, "libeay32.dll"); - relativePackage.fileSet().entries().forEach(fileSetBuilder::addEntries); - return Package.builder() - .archiveType(relativePackage.archiveType()) - .fileSet(fileSetBuilder.build()) - .url(baseUrl.value() + relativePackage.url()) - .hint(relativePackage.hint()) - .build(); - } - return super.packageOf(command, distribution, baseUrl); - } - }) - .build(); - ExtensionContextAnchor.setAttribute(MongoClientHolder.class, new MongoClientHolder(mongod, props), context); + ExtensionContextAnchor.setAttribute(MongoClientHolder.class, new MongoClientHolder(), context); } @Override @@ -129,25 +48,13 @@ public MongoClient resolveParameter(ParameterContext parameterContext, Extension .get(); } - public enum LoggingTarget { - CONSOLE, - STATUS_LOGGER; - - public static LoggingTarget getLoggingTarget(final LoggingTarget defaultValue) { - return LoggingTarget.valueOf(System.getProperty(LOGGING_TARGET_PROPERTY, defaultValue.name())); - } - } - private static final class MongoClientHolder implements CloseableResource, Supplier { - private final ReachedState state; private final MongoClient mongoClient; - public MongoClientHolder(final Mongod mongod, final TestProperties props) { - state = mongod.start(Version.Main.V7_0); - final RunningMongodProcess mongodProcess = state.current(); - final ServerAddress addr = mongodProcess.getServerAddress(); - mongoClient = MongoClients.create(String.format("mongodb://%s:%d", addr.getHost(), addr.getPort())); - props.setProperty(MongoDbTestConstants.PROP_NAME_PORT, addr.getPort()); + public MongoClientHolder() { + mongoClient = MongoClients.create(String.format( + "mongodb://localhost:%d", + PropertiesUtil.getProperties().getIntegerProperty(MongoDbTestConstants.PROP_NAME_PORT, 27017))); } @Override @@ -158,32 +65,6 @@ public MongoClient get() { @Override public void close() throws Exception { mongoClient.close(); - state.close(); - } - } - - private static final class StatusLoggerStreamProcessor implements StreamProcessor { - - private final Level level; - - public StatusLoggerStreamProcessor(Level level) { - this.level = level; - } - - @Override - public void process(String line) { - LOGGER.log(level, () -> stripLineEndings(line)); - } - - @Override - public void onProcessed() { - // noop - } - - String stripLineEndings(String line) { - // we still need to remove line endings that are passed on by - // StreamToLineProcessor... - return line.replaceAll("[\n\r]+", ""); } } } diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolverTest.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolverIT.java similarity index 86% rename from log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolverTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolverIT.java index 199cabcb2fe..dd6a544ca95 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolverTest.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbResolverIT.java @@ -20,6 +20,7 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoIterable; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.junit.jupiter.api.Test; /** @@ -29,10 +30,12 @@ *

*/ @UsingMongoDb -public class MongoDbResolverTest { +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDbResolverIT { @Test - public void testAccess(final MongoClient mongoClient) { + void testAccess(final MongoClient mongoClient) { final MongoIterable databaseNames = mongoClient.listDatabaseNames(); assertNotNull(databaseNames); assertNotNull(databaseNames.first()); diff --git a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTestConstants.java b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTestConstants.java index ed5435599b9..95ed9ccbb5d 100644 --- a/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTestConstants.java +++ b/log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbTestConstants.java @@ -18,7 +18,6 @@ public class MongoDbTestConstants { - public static final String PROP_NAME_PORT = "MongoDBTestPort"; - static final String COLLECTION_NAME = "testCollection"; + public static final String PROP_NAME_PORT = "log4j.mongo.port"; static final String DATABASE_NAME = "testDb"; } diff --git a/log4j-mongodb/src/test/resources/MongoDbAdditionalFields.xml b/log4j-mongodb/src/test/resources/MongoDbAdditionalFields.xml new file mode 100644 index 00000000000..96528f13911 --- /dev/null +++ b/log4j-mongodb/src/test/resources/MongoDbAdditionalFields.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-mongodb/src/test/resources/MongoDbAuthFailureIT.xml b/log4j-mongodb/src/test/resources/MongoDbAuthFailureIT.xml new file mode 100644 index 00000000000..23fbbdf3aca --- /dev/null +++ b/log4j-mongodb/src/test/resources/MongoDbAuthFailureIT.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/log4j-mongodb/src/test/resources/log4j2-mongodb-capped-int.xml b/log4j-mongodb/src/test/resources/MongoDbCappedIntIT.xml similarity index 70% rename from log4j-mongodb/src/test/resources/log4j2-mongodb-capped-int.xml rename to log4j-mongodb/src/test/resources/MongoDbCappedIntIT.xml index 746b19914e7..bdaa8e3f40d 100644 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb-capped-int.xml +++ b/log4j-mongodb/src/test/resources/MongoDbCappedIntIT.xml @@ -15,18 +15,22 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - + - + diff --git a/log4j-mongodb/src/test/resources/log4j2-mongodb-capped-long.xml b/log4j-mongodb/src/test/resources/MongoDbCappedLongIT.xml similarity index 71% rename from log4j-mongodb/src/test/resources/log4j2-mongodb-capped-long.xml rename to log4j-mongodb/src/test/resources/MongoDbCappedLongIT.xml index 941ea71c4e2..6e3b8d6aad8 100644 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb-capped-long.xml +++ b/log4j-mongodb/src/test/resources/MongoDbCappedLongIT.xml @@ -15,19 +15,23 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - + - + diff --git a/log4j-mongodb/src/test/resources/log4j2-mongodb-additional-fields.xml b/log4j-mongodb/src/test/resources/MongoDbIT.xml similarity index 68% rename from log4j-mongodb/src/test/resources/log4j2-mongodb-additional-fields.xml rename to log4j-mongodb/src/test/resources/MongoDbIT.xml index d9d27461a00..9c9ab6037ee 100644 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb-additional-fields.xml +++ b/log4j-mongodb/src/test/resources/MongoDbIT.xml @@ -15,19 +15,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - - - - - - + + - + diff --git a/log4j-mongodb/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-mongodb/src/test/resources/MongoDbMapMessageIT.xml similarity index 68% rename from log4j-mongodb/src/test/resources/log4j2-mongodb-map-message.xml rename to log4j-mongodb/src/test/resources/MongoDbMapMessageIT.xml index 4b740bc947e..dc6aa6846bd 100644 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb-map-message.xml +++ b/log4j-mongodb/src/test/resources/MongoDbMapMessageIT.xml @@ -15,16 +15,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - - + + - + diff --git a/log4j-mongodb/src/test/resources/log4j2-mongodb.xml b/log4j-mongodb/src/test/resources/log4j2-mongodb.xml deleted file mode 100644 index 4f0865b2c46..00000000000 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - diff --git a/log4j-mongodb4/pom.xml b/log4j-mongodb4/pom.xml index e4a9eefaeff..04639d0b535 100644 --- a/log4j-mongodb4/pom.xml +++ b/log4j-mongodb4/pom.xml @@ -35,55 +35,61 @@ org.apache.logging.log4j.core - 4.11.3 + 4.11.4 + 1.7.36 + org.mongodb bson ${mongodb4.version} + org.mongodb mongodb-driver-core ${mongodb4.version} - - org.mongodb - mongodb-driver-legacy - ${mongodb4.version} - + org.mongodb mongodb-driver-sync ${mongodb4.version} + + org.apache.logging.log4j log4j-api + org.apache.logging.log4j log4j-core + org.mongodb bson + org.mongodb mongodb-driver-core + org.mongodb mongodb-driver-sync + org.apache.logging.log4j log4j-api-test @@ -95,6 +101,7 @@ + org.apache.logging.log4j log4j-core-test @@ -106,31 +113,19 @@ + org.apache.commons commons-lang3 test - - de.flapdoodle.embed - de.flapdoodle.embed.mongo - test - - - de.flapdoodle.embed - de.flapdoodle.embed.process - test - - - de.flapdoodle.reverse - de.flapdoodle.reverse - test - + org.junit.jupiter junit-jupiter-api test + @@ -140,8 +135,7 @@ org.apache.maven.plugins maven-surefire-plugin - - 1 + true @@ -155,4 +149,110 @@ + + + + docker + + + + + linux + + + env.CI + true + + + + + + + + io.fabric8 + docker-maven-plugin + + all + true + true + + + mongo + mongo:latest + + + + localhost:mongo.port:27017 + + + + + + + + start-mongo + + start + + + + stop-mongo + + stop + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + + + org.slf4j + slf4j-nop + ${slf4j.version} + + + + + + integration-test + verify + + + true + + **/*IT.java + + + + OFF + + ${mongo.port} + + + + + + + + + + + + diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java similarity index 84% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java index 6f54e77c7e3..6be4f7f6852 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/AbstractMongoDb4CappedIT.java @@ -23,17 +23,16 @@ import org.apache.logging.log4j.core.LoggerContext; import org.bson.Document; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -public abstract class AbstractMongoDb4CappedTest { +abstract class AbstractMongoDb4CappedIT { - @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(AbstractMongoDb4CappedTest.class); + protected void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(AbstractMongoDb4CappedIT.class); logger.info("Hello log"); final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME); Assertions.assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDb4TestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); Assertions.assertNotNull(collection); final Document first = collection.find().first(); Assertions.assertNotNull(first); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsIT.java similarity index 88% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsIT.java index 27062452bf6..b5ccc77e735 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AdditionalFieldsIT.java @@ -26,21 +26,25 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Test; @UsingMongoDb4 -@LoggerContextSource("log4j2-mongodb-additional-fields.xml") -public class MongoDb4AdditionalFieldsTest { +@LoggerContextSource("MongoDb4AdditionalFields.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4AdditionalFieldsIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDb4AdditionalFieldsTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDb4AdditionalFieldsIT.class); logger.info("Hello log 1"); logger.info("Hello log 2", new RuntimeException("Hello ex 2")); final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDb4TestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); assertNotNull(collection); final FindIterable found = collection.find(); final Document first = found.first(); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureIT.java similarity index 76% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureIT.java index 641305256c4..c5b5b15c6db 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureIT.java @@ -25,20 +25,25 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Test; @UsingMongoDb4 -@LoggerContextSource("log4j2-mongodb-auth-failure.xml") -public class MongoDb4AuthFailureTest { +@LoggerContextSource("MongoDb4AuthFailureIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4AuthFailureIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDb4AuthFailureTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDb4AuthFailureIT.class); logger.info("Hello log"); final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDb4TestConstants.DATABASE_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); + ; assertNotNull(collection); final Document first = collection.find().first(); assertNull(first); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntIT.java similarity index 64% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntIT.java index 0a99dbf5b87..24add71aa5d 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntIT.java @@ -16,11 +16,21 @@ */ package org.apache.logging.log4j.mongodb4; +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; @UsingMongoDb4 -@LoggerContextSource("log4j2-mongodb-capped-long.xml") -public class MongoDb4CappedLongTest extends AbstractMongoDb4CappedTest { +@LoggerContextSource("MongoDb4CappedIntIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4CappedIntIT extends AbstractMongoDb4CappedIT { - // test is in superclass + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } } diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongIT.java similarity index 64% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongIT.java index e01e0c0d63b..580c6c60fa6 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongIT.java @@ -16,11 +16,21 @@ */ package org.apache.logging.log4j.mongodb4; +import com.mongodb.client.MongoClient; +import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; +import org.junit.jupiter.api.Test; @UsingMongoDb4 -@LoggerContextSource("log4j2-mongodb-capped-int.xml") -public class MongoDb4CappedIntTest extends AbstractMongoDb4CappedTest { +@LoggerContextSource("MongoDb4CappedLongIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4CappedLongIT extends AbstractMongoDb4CappedIT { - // test is in superclass + @Test + @Override + protected void test(LoggerContext ctx, MongoClient mongoClient) { + super.test(ctx, mongoClient); + } } diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4IT.java similarity index 84% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4IT.java index 140a9597505..ac0c550efca 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4IT.java @@ -26,21 +26,25 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Test; @UsingMongoDb4 -@LoggerContextSource("log4j2-mongodb.xml") -public class MongoDb4Test { +@LoggerContextSource("MongoDb4IT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4IT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDb4Test.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDb4IT.class); logger.info("Hello log 1"); logger.info("Hello log 2", new RuntimeException("Hello ex 2")); final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME); assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDb4TestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); assertNotNull(collection); final FindIterable found = collection.find(); final Document first = found.first(); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageIT.java similarity index 80% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageIT.java index d8877392448..b7a384c23b1 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageIT.java @@ -23,24 +23,28 @@ import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.test.junit.LoggerContextSource; import org.apache.logging.log4j.message.MapMessage; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.bson.Document; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @UsingMongoDb4 -@LoggerContextSource("log4j2-mongodb-map-message.xml") -public class MongoDb4MapMessageTest { +@LoggerContextSource("MongoDb4MapMessageIT.xml") +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4MapMessageIT { @Test - public void test(final LoggerContext ctx, final MongoClient mongoClient) { - final Logger logger = ctx.getLogger(MongoDb4MapMessageTest.class); + void test(final LoggerContext ctx, final MongoClient mongoClient) { + final Logger logger = ctx.getLogger(MongoDb4MapMessageIT.class); final MapMessage mapMessage = new MapMessage<>(); mapMessage.with("SomeName", "SomeValue"); mapMessage.with("SomeInt", 1); logger.info(mapMessage); final MongoDatabase database = mongoClient.getDatabase(MongoDb4TestConstants.DATABASE_NAME); Assertions.assertNotNull(database); - final MongoCollection collection = database.getCollection(MongoDb4TestConstants.COLLECTION_NAME); + final MongoCollection collection = + database.getCollection(getClass().getSimpleName()); Assertions.assertNotNull(collection); final Document first = collection.find().first(); Assertions.assertNotNull(first); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java index 90c71606cfc..e7994f10297 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Resolver.java @@ -18,69 +18,19 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; -import de.flapdoodle.embed.mongo.commands.ServerAddress; -import de.flapdoodle.embed.mongo.distribution.Version; -import de.flapdoodle.embed.mongo.packageresolver.Command; -import de.flapdoodle.embed.mongo.transitions.Mongod; -import de.flapdoodle.embed.mongo.transitions.PackageOfCommandDistribution; -import de.flapdoodle.embed.mongo.transitions.RunningMongodProcess; -import de.flapdoodle.embed.mongo.types.DistributionBaseUrl; -import de.flapdoodle.embed.process.config.store.FileSet; -import de.flapdoodle.embed.process.config.store.FileType; -import de.flapdoodle.embed.process.config.store.Package; -import de.flapdoodle.embed.process.distribution.Distribution; -import de.flapdoodle.embed.process.io.ProcessOutput; -import de.flapdoodle.embed.process.io.Processors; -import de.flapdoodle.embed.process.io.StreamProcessor; -import de.flapdoodle.embed.process.types.Name; -import de.flapdoodle.embed.process.types.ProcessConfig; -import de.flapdoodle.os.OSType; -import de.flapdoodle.reverse.TransitionWalker.ReachedState; -import de.flapdoodle.reverse.transitions.Derive; -import de.flapdoodle.reverse.transitions.Start; -import java.util.Objects; import java.util.function.Supplier; -import org.apache.commons.lang3.NotImplementedException; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.status.StatusLogger; -import org.apache.logging.log4j.test.TestProperties; import org.apache.logging.log4j.test.junit.ExtensionContextAnchor; -import org.apache.logging.log4j.test.junit.TestPropertySource; import org.apache.logging.log4j.test.junit.TypeBasedParameterResolver; +import org.apache.logging.log4j.util.PropertiesUtil; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; -public class MongoDb4Resolver extends TypeBasedParameterResolver implements BeforeAllCallback { +class MongoDb4Resolver extends TypeBasedParameterResolver implements BeforeAllCallback { - private static final Logger LOGGER = StatusLogger.getLogger(); - private static final String LOGGING_TARGET_PROPERTY = "log4j2.mongoDbLoggingTarget"; - - private static final int BUILDER_TIMEOUT_MILLIS = 30000; - - private static ProcessOutput getProcessOutput(final LoggingTarget loggingTarget, final String label) { - if (loggingTarget != null) { - switch (loggingTarget) { - case STATUS_LOGGER: - // @formatter:off - return ProcessOutput.builder() - .output(Processors.named( - "[" + label + " output]", new StatusLoggerStreamProcessor(Level.INFO))) - .error(Processors.named( - "[" + label + " error]", new StatusLoggerStreamProcessor(Level.ERROR))) - .commands(new StatusLoggerStreamProcessor(Level.DEBUG)) - .build(); - // @formatter:on - case CONSOLE: - return ProcessOutput.namedConsole(label); - default: - } - } - throw new NotImplementedException(Objects.toString(loggingTarget)); - } + static final String PORT_PROPERTY = "log4j2.mongo.port"; public MongoDb4Resolver() { super(MongoClient.class); @@ -88,40 +38,7 @@ public MongoDb4Resolver() { @Override public void beforeAll(ExtensionContext context) throws Exception { - final TestProperties props = TestPropertySource.createProperties(context); - final Mongod mongod = Mongod.builder() - .processOutput(Derive.given(Name.class) - .state(ProcessOutput.class) - .deriveBy(name -> getProcessOutput( - LoggingTarget.getLoggingTarget(LoggingTarget.STATUS_LOGGER), name.value()))) - .processConfig(Start.to(ProcessConfig.class) - .initializedWith(ProcessConfig.defaults().withStopTimeoutInMillis(BUILDER_TIMEOUT_MILLIS)) - .withTransitionLabel("create default")) - // workaround for https://github.com/flapdoodle-oss/de.flapdoodle.embed.mongo/issues/309 - .packageOfDistribution(new PackageOfCommandDistribution() { - - @Override - protected Package packageOf( - Command command, Distribution distribution, DistributionBaseUrl baseUrl) { - if (distribution.platform().operatingSystem().type() == OSType.Windows) { - final Package relativePackage = - commandPackageResolver().apply(command).packageFor(distribution); - final FileSet.Builder fileSetBuilder = FileSet.builder() - .addEntry(FileType.Library, "ssleay32.dll") - .addEntry(FileType.Library, "libeay32.dll"); - relativePackage.fileSet().entries().forEach(fileSetBuilder::addEntries); - return Package.builder() - .archiveType(relativePackage.archiveType()) - .fileSet(fileSetBuilder.build()) - .url(baseUrl.value() + relativePackage.url()) - .hint(relativePackage.hint()) - .build(); - } - return super.packageOf(command, distribution, baseUrl); - } - }) - .build(); - ExtensionContextAnchor.setAttribute(MongoClientHolder.class, new MongoClientHolder(mongod, props), context); + ExtensionContextAnchor.setAttribute(MongoClientHolder.class, new MongoClientHolder(), context); } @Override @@ -131,25 +48,13 @@ public MongoClient resolveParameter(ParameterContext parameterContext, Extension .get(); } - public enum LoggingTarget { - CONSOLE, - STATUS_LOGGER; - - public static LoggingTarget getLoggingTarget(final LoggingTarget defaultValue) { - return LoggingTarget.valueOf(System.getProperty(LOGGING_TARGET_PROPERTY, defaultValue.name())); - } - } - private static final class MongoClientHolder implements CloseableResource, Supplier { - private final ReachedState state; private final MongoClient mongoClient; - public MongoClientHolder(final Mongod mongod, final TestProperties props) { - state = mongod.start(Version.Main.V7_0); - final RunningMongodProcess mongodProcess = state.current(); - final ServerAddress addr = mongodProcess.getServerAddress(); - mongoClient = MongoClients.create(String.format("mongodb://%s:%d", addr.getHost(), addr.getPort())); - props.setProperty(MongoDb4TestConstants.PROP_NAME_PORT, addr.getPort()); + public MongoClientHolder() { + mongoClient = MongoClients.create(String.format( + "mongodb://localhost:%d", + PropertiesUtil.getProperties().getIntegerProperty(MongoDb4TestConstants.PROP_NAME_PORT, 27017))); } @Override @@ -160,32 +65,6 @@ public MongoClient get() { @Override public void close() throws Exception { mongoClient.close(); - state.close(); - } - } - - private static final class StatusLoggerStreamProcessor implements StreamProcessor { - - private final Level level; - - public StatusLoggerStreamProcessor(Level level) { - this.level = level; - } - - @Override - public void process(String line) { - LOGGER.log(level, () -> stripLineEndings(line)); - } - - @Override - public void onProcessed() { - // noop - } - - String stripLineEndings(String line) { - // we still need to remove line endings that are passed on by - // StreamToLineProcessor... - return line.replaceAll("[\n\r]+", ""); } } } diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ResolverTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ResolverIT.java similarity index 86% rename from log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ResolverTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ResolverIT.java index 9d0bebe5e0b..d63b07072bf 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ResolverTest.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4ResolverIT.java @@ -20,6 +20,7 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoIterable; +import org.apache.logging.log4j.test.junit.UsingStatusListener; import org.junit.jupiter.api.Test; /** @@ -29,10 +30,12 @@ *

*/ @UsingMongoDb4 -public class MongoDb4ResolverTest { +// Print debug status logger output upon failure +@UsingStatusListener +class MongoDb4ResolverIT { @Test - public void testAccess(final MongoClient mongoClient) { + void testAccess(final MongoClient mongoClient) { final MongoIterable databaseNames = mongoClient.listDatabaseNames(); assertNotNull(databaseNames); assertNotNull(databaseNames.first()); diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java index 6f43c72527e..5245c686700 100644 --- a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java +++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java @@ -18,7 +18,6 @@ public class MongoDb4TestConstants { - public static final String PROP_NAME_PORT = "MongoDBTestPort"; - static final String COLLECTION_NAME = "testCollection"; + public static final String PROP_NAME_PORT = "log4j.mongo.port"; static final String DATABASE_NAME = "testDb"; } diff --git a/log4j-mongodb4/src/test/resources/MongoDb4AdditionalFields.xml b/log4j-mongodb4/src/test/resources/MongoDb4AdditionalFields.xml new file mode 100644 index 00000000000..6892bcac508 --- /dev/null +++ b/log4j-mongodb4/src/test/resources/MongoDb4AdditionalFields.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml b/log4j-mongodb4/src/test/resources/MongoDb4AuthFailureIT.xml similarity index 67% rename from log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml rename to log4j-mongodb4/src/test/resources/MongoDb4AuthFailureIT.xml index 0674de4fa0c..fa8a46bbebe 100644 --- a/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml +++ b/log4j-mongodb4/src/test/resources/MongoDb4AuthFailureIT.xml @@ -15,16 +15,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - + + connection="mongodb://log4jUser:12345678@localhost:${sys:log4j.mongo.port:-27017}/testDb.MongoDb4AuthFailureIT" /> - + diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped-int.xml b/log4j-mongodb4/src/test/resources/MongoDb4CappedIntIT.xml similarity index 70% rename from log4j-mongodb4/src/test/resources/log4j2-mongodb-capped-int.xml rename to log4j-mongodb4/src/test/resources/MongoDb4CappedIntIT.xml index 03aca2ebbea..2e9a11cb68a 100644 --- a/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped-int.xml +++ b/log4j-mongodb4/src/test/resources/MongoDb4CappedIntIT.xml @@ -15,18 +15,22 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - + - + diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped-long.xml b/log4j-mongodb4/src/test/resources/MongoDb4CappedLongIT.xml similarity index 71% rename from log4j-mongodb4/src/test/resources/log4j2-mongodb-capped-long.xml rename to log4j-mongodb4/src/test/resources/MongoDb4CappedLongIT.xml index ddde9bcb05e..b79a4dc59e8 100644 --- a/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped-long.xml +++ b/log4j-mongodb4/src/test/resources/MongoDb4CappedLongIT.xml @@ -15,19 +15,23 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - + - + diff --git a/log4j-mongodb/src/test/resources/log4j2-mongodb-auth-failure.xml b/log4j-mongodb4/src/test/resources/MongoDb4IT.xml similarity index 68% rename from log4j-mongodb/src/test/resources/log4j2-mongodb-auth-failure.xml rename to log4j-mongodb4/src/test/resources/MongoDb4IT.xml index e84603a0a52..6ab35f8765a 100644 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb-auth-failure.xml +++ b/log4j-mongodb4/src/test/resources/MongoDb4IT.xml @@ -15,16 +15,19 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - - + + - + diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-mongodb4/src/test/resources/MongoDb4MapMessageIT.xml similarity index 68% rename from log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml rename to log4j-mongodb4/src/test/resources/MongoDb4MapMessageIT.xml index a84154e78da..8990a64afcc 100644 --- a/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml +++ b/log4j-mongodb4/src/test/resources/MongoDb4MapMessageIT.xml @@ -15,16 +15,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - - + + - + diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml deleted file mode 100644 index eec98585cd9..00000000000 --- a/log4j-mongodb4/src/test/resources/log4j2-mongodb-additional-fields.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml deleted file mode 100644 index b36a58affc9..00000000000 --- a/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index e87e3e7309a..566ff065a70 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -81,8 +81,6 @@ 8.15.0 0.9.0 7.0.5 - 4.13.1 - 1.8.0 4.0.22 33.3.0-jre 2.2.224 @@ -361,24 +359,6 @@ ${commons-pool2.version}
- - de.flapdoodle.embed - de.flapdoodle.embed.mongo - ${flapdoodle-embed.version} - - - - de.flapdoodle.embed - de.flapdoodle.embed.process - ${flapdoodle-embed.version} - - - - de.flapdoodle.reverse - de.flapdoodle.reverse - ${flapdoodle-reverse.version} - - com.conversantmedia disruptor @@ -883,7 +863,7 @@ - + io.fabric8 docker-maven-plugin diff --git a/src/changelog/.2.x.x/2229_mongodb_docker.xml b/src/changelog/.2.x.x/2229_mongodb_docker.xml new file mode 100644 index 00000000000..1d5b36ed1ca --- /dev/null +++ b/src/changelog/.2.x.x/2229_mongodb_docker.xml @@ -0,0 +1,8 @@ + + + + Switch MongoDB tests to use Docker. + From 428d1126d72ed49eb39ee0e1f353063658db2a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Volkan=20Yaz=C4=B1c=C4=B1?= Date: Tue, 17 Sep 2024 13:21:00 +0200 Subject: [PATCH 07/11] Enable `docker` profile in CI for `log4j-layout-template-json-test` (#2953) --- log4j-layout-template-json-test/pom.xml | 140 ++++++++++------ .../layout/template/json/LogstashIT.java | 152 ++++++++++++------ log4j-parent/pom.xml | 7 - 3 files changed, 192 insertions(+), 107 deletions(-) diff --git a/log4j-layout-template-json-test/pom.xml b/log4j-layout-template-json-test/pom.xml index 7031fd4d80b..51bc8a9660b 100644 --- a/log4j-layout-template-json-test/pom.xml +++ b/log4j-layout-template-json-test/pom.xml @@ -43,6 +43,14 @@ org.apache.logging.log4j.layout.template.json.test org.apache.logging.log4j.core + + 8.15.1 + @@ -85,6 +93,7 @@ co.elastic.clients elasticsearch-java + ${elastic.version} test @@ -129,23 +138,6 @@ - - - org.apache.maven.plugins - maven-failsafe-plugin - - true - - - - - integration-test - verify - - - - - org.apache.maven.plugins maven-surefire-plugin @@ -170,15 +162,29 @@ docker + - false + + linux + + + env.CI + true + - 8.10.2 + + + false + false + - -Xms750m -Xmx750m + -Xms750m -Xmx750m + @@ -188,7 +194,6 @@ io.fabric8 docker-maven-plugin - all true true @@ -199,10 +204,11 @@ single-node false - ${elastic.java-opts} + ${elastic.javaOpts} - 9200:9200 + + localhost:elasticsearch.port:9200 custom @@ -214,7 +220,11 @@ cyan - recovered \[0\] indices into cluster_state + + + 9200 + + @@ -232,11 +242,13 @@ logstash - ${elastic.java-opts} + ${elastic.javaOpts} - 12222:12222 - 12345:12345 + + localhost:logstash.gelf.port:12222 + + localhost:logstash.tcp.port:12345 [LS] @@ -248,54 +260,71 @@ --pipeline.batch.size 1 -e - input { + "logstash" + use_tcp => true + use_udp => false + port => 12222 + type => "gelf" } + + # Documentation: https://www.elastic.co/guide/en/logstash/current/plugins-inputs-tcp.html tcp { - port => 12345 - codec => json - type => "tcp" + port => 12345 + codec => json + type => "tcp" } + } filter { if [type] == "gelf" { # These are GELF/Syslog logging levels as defined in RFC 3164. - # Map the integer level to its human readable format. + # Map the integer level to its human-readable format. + # Documentation: https://www.elastic.co/guide/en/logstash/current/plugins-filters-translate.html translate { - field => "[level]" - destination => "[levelName]" - dictionary => { - "0" => "EMERG" - "1" => "ALERT" - "2" => "CRITICAL" - "3" => "ERROR" - "4" => "WARN" - "5" => "NOTICE" - "6" => "INFO" - "7" => "DEBUG" + source => "[level]" + target => "[levelName]" + dictionary => { + "0" => "EMERG" + "1" => "ALERT" + "2" => "CRITICAL" + "3" => "ERROR" + "4" => "WARN" + "5" => "NOTICE" + "6" => "INFO" + "7" => "DEBUG" } } } } + # Documentation: https://www.elastic.co/guide/en/logstash/current/plugins-filters-elasticsearch.html output { # (Un)comment for debugging purposes - # stdout { codec => rubydebug } + # stdout { codec => rubydebug } elasticsearch { - hosts => ["http://elasticsearch:9200"] - index => "log4j" + hosts => ["http://elasticsearch:9200"] + index => "log4j" } - } + } + + ]]> - Successfully started Logstash API endpoint + + localhost + + 12222 + 12345 + + @@ -327,6 +356,11 @@ **/*IT.java + + ${elasticsearch.port} + ${logstash.gelf.port} + ${logstash.tcp.port} + diff --git a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java index abb86f8ebc0..17b83629755 100644 --- a/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java +++ b/log4j-layout-template-json-test/src/test/java/org/apache/logging/log4j/layout/template/json/LogstashIT.java @@ -16,6 +16,9 @@ */ package org.apache.logging.log4j.layout.template.json; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch._types.ElasticsearchException; import co.elastic.clients.elasticsearch._types.HealthStatus; @@ -32,6 +35,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.net.Socket; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -41,6 +45,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -53,13 +58,11 @@ import org.apache.logging.log4j.core.config.DefaultConfiguration; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.GelfLayout; -import org.apache.logging.log4j.core.util.NetUtils; import org.apache.logging.log4j.layout.template.json.JsonTemplateLayout.EventTemplateAdditionalField; import org.apache.logging.log4j.layout.template.json.util.ThreadLocalRecyclerFactory; import org.apache.logging.log4j.message.SimpleMessage; import org.apache.logging.log4j.status.StatusLogger; -import org.assertj.core.api.Assertions; -import org.awaitility.Awaitility; +import org.apache.logging.log4j.util.Strings; import org.elasticsearch.client.RestClient; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -71,14 +74,14 @@ @Execution(ExecutionMode.SAME_THREAD) class LogstashIT { + private static final String LOG_PREFIX = LogstashIT.class.getSimpleName() + ' '; + private static final StatusLogger LOGGER = StatusLogger.getLogger(); private static final DefaultConfiguration CONFIGURATION = new DefaultConfiguration(); private static final Charset CHARSET = StandardCharsets.UTF_8; - private static final String HOST_NAME = NetUtils.getLocalHostname(); - private static final String SERVICE_NAME = "LogstashIT"; private static final String EVENT_DATASET = SERVICE_NAME + ".log"; @@ -88,7 +91,7 @@ class LogstashIT { .setCharset(CHARSET) .setCompressionType(GelfLayout.CompressionType.OFF) .setIncludeNullDelimiter(true) - .setHost(HOST_NAME) + .setHost(MavenHardcodedConstants.HOST_NAME) .build(); private static final JsonTemplateLayout JSON_TEMPLATE_GELF_LAYOUT = JsonTemplateLayout.newBuilder() @@ -99,13 +102,12 @@ class LogstashIT { .setEventTemplateAdditionalFields(new EventTemplateAdditionalField[] { EventTemplateAdditionalField.newBuilder() .setKey("host") - .setValue(HOST_NAME) + .setValue(MavenHardcodedConstants.HOST_NAME) .build() }) .build(); - // Note that EcsLayout doesn't support charset configuration, though it uses - // UTF-8 internally. + // Note that `EcsLayout` doesn't support charset configuration, though it uses UTF-8 internally. private static final EcsLayout ECS_LAYOUT = EcsLayout.newBuilder() .setConfiguration(CONFIGURATION) .setServiceName(SERVICE_NAME) @@ -140,44 +142,85 @@ class LogstashIT { private static ElasticsearchClient ES_CLIENT; /** - * Constants hardcoded in docker-maven-plugin configuration, do not change! + * Constants hardcoded in `docker-maven-plugin` configuration, do not change! */ private static final class MavenHardcodedConstants { private MavenHardcodedConstants() {} - private static final int LS_GELF_INPUT_PORT = 12222; + private static final String HOST_NAME = "localhost"; - private static final int LS_TCP_INPUT_PORT = 12345; + private static final int LS_GELF_INPUT_PORT = readPort("log4j.logstash.gelf.port"); - private static final int ES_PORT = 9200; + private static final int LS_TCP_INPUT_PORT = readPort("log4j.logstash.tcp.port"); + + private static final int ES_PORT = readPort("log4j.elasticsearch.port"); private static final String ES_INDEX_NAME = "log4j"; + + private static int readPort(final String propertyName) { + final String propertyValue = System.getProperty(propertyName); + final int port; + final String errorMessage = String.format( + "was expecting a valid port number in the system property `%s`, found: `%s`", + propertyName, propertyValue); + try { + if (Strings.isBlank(propertyValue) || (port = Integer.parseInt(propertyValue)) < 0 || port >= 0xFFFF) { + throw new IllegalArgumentException(errorMessage); + } + } catch (final NumberFormatException error) { + throw new IllegalArgumentException(errorMessage, error); + } + return port; + } } @BeforeAll - public static void initClient() throws IOException { + public static void initEsClient() { - LOGGER.info("instantiating the ES client"); - REST_CLIENT = RestClient.builder( - HttpHost.create(String.format("http://%s:%d", HOST_NAME, MavenHardcodedConstants.ES_PORT))) - .build(); + LOGGER.info(LOG_PREFIX + "instantiating the ES client"); + final String hostUri = + String.format("http://%s:%d", MavenHardcodedConstants.HOST_NAME, MavenHardcodedConstants.ES_PORT); + REST_CLIENT = RestClient.builder(HttpHost.create(hostUri)).build(); ES_TRANSPORT = new RestClientTransport(REST_CLIENT, new JacksonJsonpMapper()); ES_CLIENT = new ElasticsearchClient(ES_TRANSPORT); - LOGGER.info("verifying the ES connection"); - HealthResponse healthResponse = ES_CLIENT.cluster().health(); - Assertions.assertThat(healthResponse.status()).isNotEqualTo(HealthStatus.Red); + LOGGER.info(LOG_PREFIX + "verifying the ES connection to `{}`", hostUri); + await("ES cluster health") + .pollDelay(100, TimeUnit.MILLISECONDS) + .atMost(1, TimeUnit.MINUTES) + .untilAsserted(() -> { + final HealthResponse healthResponse = ES_CLIENT.cluster().health(); + assertThat(healthResponse.status()).isNotEqualTo(HealthStatus.Red); + }); + } + + @BeforeAll + public static void waitForLsInputSockets() { + waitForSocketBinding(MavenHardcodedConstants.LS_GELF_INPUT_PORT, "Logstash GELF input"); + waitForSocketBinding(MavenHardcodedConstants.LS_TCP_INPUT_PORT, "Logstash TCP input"); + } + + private static void waitForSocketBinding(final int port, final String name) { + LOGGER.info(LOG_PREFIX + "verifying socket binding at port {} for {}", port, name); + await("socket binding at port " + port) + .pollDelay(100, TimeUnit.MILLISECONDS) + .atMost(1, TimeUnit.MINUTES) + .untilAsserted(() -> { + try (final Socket socket = new Socket(MavenHardcodedConstants.HOST_NAME, port)) { + assertThat(socket.isConnected()).isTrue(); + } + }); } @BeforeEach void deleteIndex() throws IOException { - LOGGER.info("deleting the ES index"); + LOGGER.info(LOG_PREFIX + "deleting the ES index"); try { DeleteIndexResponse deleteIndexResponse = ES_CLIENT .indices() .delete(DeleteIndexRequest.of(builder -> builder.index(MavenHardcodedConstants.ES_INDEX_NAME))); - Assertions.assertThat(deleteIndexResponse.acknowledged()).isTrue(); + assertThat(deleteIndexResponse.acknowledged()).isTrue(); } catch (ElasticsearchException error) { if (!error.getMessage().contains("index_not_found_exception")) { throw new RuntimeException(error); @@ -209,15 +252,15 @@ private static void testEvents(final List logEvents) throws IOExceptio try { // Append events. - LOGGER.info("appending events"); + LOGGER.info(LOG_PREFIX + "appending events"); logEvents.forEach(appender::append); - LOGGER.info("completed appending events"); + LOGGER.info(LOG_PREFIX + "completed appending events"); // Wait all messages to arrive. - Awaitility.await() + await("message delivery") .atMost(Duration.ofSeconds(60)) .pollDelay(Duration.ofSeconds(2)) - .until(() -> checkDocumentCount(LOG_EVENT_COUNT)); + .untilAsserted(() -> assertDocumentCount(LOG_EVENT_COUNT)); // Verify indexed messages. final Set expectedMessages = logEvents.stream() @@ -227,7 +270,7 @@ private static void testEvents(final List logEvents) throws IOExceptio .map(source -> (String) source.get(ES_INDEX_MESSAGE_FIELD_NAME)) .filter(Objects::nonNull) .collect(Collectors.toSet()); - Assertions.assertThat(actualMessages).isEqualTo(expectedMessages); + assertThat(actualMessages).isEqualTo(expectedMessages); } finally { appender.stop(); @@ -280,16 +323,16 @@ void test_newlines() throws IOException { try { // Append the event. - LOGGER.info("appending events"); + LOGGER.info(LOG_PREFIX + "appending events"); appender.append(logEvent1); appender.append(logEvent2); - LOGGER.info("completed appending events"); + LOGGER.info(LOG_PREFIX + "completed appending events"); // Wait the message to arrive. - Awaitility.await() + await("message delivery") .atMost(Duration.ofSeconds(60)) .pollDelay(Duration.ofSeconds(2)) - .until(() -> checkDocumentCount(2)); + .untilAsserted(() -> assertDocumentCount(2)); // Verify indexed messages. final Set expectedMessages = Stream.of(logEvent1, logEvent2) @@ -299,7 +342,7 @@ void test_newlines() throws IOException { .map(source -> (String) source.get(ES_INDEX_MESSAGE_FIELD_NAME)) .filter(Objects::nonNull) .collect(Collectors.toSet()); - Assertions.assertThat(actualMessages).isEqualTo(expectedMessages); + assertThat(actualMessages).isEqualTo(expectedMessages); } finally { appender.stop(); @@ -336,7 +379,7 @@ void test_GelfLayout() throws IOException { Collections.emptySet()); // Compare persisted sources. - Assertions.assertThat(actualSourceByKey).isEqualTo(expectedSourceByKey); + assertThat(actualSourceByKey).isEqualTo(expectedSourceByKey); } @Test @@ -370,7 +413,7 @@ void test_EcsLayout() throws IOException { excludedKeys); // Compare persisted sources. - Assertions.assertThat(actualSourceByKey).isEqualTo(expectedSourceByKey); + assertThat(actualSourceByKey).isEqualTo(expectedSourceByKey); } private static Map appendAndCollect( @@ -384,15 +427,15 @@ private static Map appendAndCollect( try { // Append the event. - LOGGER.info("appending events"); + LOGGER.info(LOG_PREFIX + "appending events"); logEvents.forEach(appender::append); - LOGGER.info("completed appending events"); + LOGGER.info(LOG_PREFIX + "completed appending events"); // Wait the message to arrive. - Awaitility.await() + await("message delivery") .atMost(Duration.ofSeconds(60)) .pollDelay(Duration.ofSeconds(2)) - .until(() -> checkDocumentCount(LOG_EVENT_COUNT)); + .untilAsserted(() -> assertDocumentCount(LOG_EVENT_COUNT)); // Retrieve the persisted messages. return queryDocuments().stream().collect(Collectors.toMap(keyMapper, (final Map source) -> { @@ -406,10 +449,10 @@ private static Map appendAndCollect( } private static SocketAppender createStartedAppender(final Layout layout, final int port) { - LOGGER.info("creating the appender"); + LOGGER.info(LOG_PREFIX + "creating the appender"); final SocketAppender appender = SocketAppender.newBuilder() .setConfiguration(CONFIGURATION) - .setHost(HOST_NAME) + .setHost(MavenHardcodedConstants.HOST_NAME) .setPort(port) .setReconnectDelayMillis(100) .setName("LogstashItAppender") @@ -422,12 +465,27 @@ private static SocketAppender createStartedAppender(final Layout layout, fina return appender; } - private static boolean checkDocumentCount(int expectedCount) throws IOException { - final CountResponse countResponse = - ES_CLIENT.count(builder -> builder.index(MavenHardcodedConstants.ES_INDEX_NAME)); + private static void assertDocumentCount(final int expectedCount) throws IOException { + final CountResponse countResponse; + try { + countResponse = ES_CLIENT.count(builder -> builder.index(MavenHardcodedConstants.ES_INDEX_NAME)); + } + // Try to enrich the failure with the available list of indices + catch (final ElasticsearchException error) { + try { + if (error.getMessage().contains("index_not_found_exception")) { + final Set indexNames = + ES_CLIENT.cluster().health().indices().keySet(); + final String message = String.format("Could not find index! Available index names: %s", indexNames); + throw new AssertionError(message, error); + } + } catch (final Exception suppressed) { + error.addSuppressed(suppressed); + } + throw error; + } final long actualCount = countResponse.count(); - Assertions.assertThat(actualCount).isLessThanOrEqualTo(expectedCount); - return actualCount == expectedCount; + assertThat(actualCount).isEqualTo(expectedCount); } private static List> queryDocuments() throws IOException { @@ -441,7 +499,7 @@ private static List> queryDocuments() throws IOException { return searchResponse.hits().hits().stream() .map(hit -> { @SuppressWarnings("unchecked") - Map source = hit.source(); + final Map source = hit.source(); return source; }) .collect(Collectors.toList()); diff --git a/log4j-parent/pom.xml b/log4j-parent/pom.xml index 566ff065a70..1faa00a7101 100644 --- a/log4j-parent/pom.xml +++ b/log4j-parent/pom.xml @@ -78,7 +78,6 @@ 1.2.15 3.4.4 - 8.15.0 0.9.0 7.0.5 4.0.22 @@ -371,12 +370,6 @@ ${disruptor.version} - - co.elastic.clients - elasticsearch-java - ${elasticsearch-java.version} - - org.zapodot embedded-ldap-junit From ea1fc0814b86aca9a2d6d185f4a499a5c9216c3a Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 18 Sep 2024 10:47:09 +0200 Subject: [PATCH 08/11] Add changelog for `logging-parent` --- .../.2.x.x/update_org_apache_logging_logging_parent.xml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml diff --git a/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml b/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml new file mode 100644 index 00000000000..5b17082960b --- /dev/null +++ b/src/changelog/.2.x.x/update_org_apache_logging_logging_parent.xml @@ -0,0 +1,7 @@ + + + Update `org.apache.logging:logging-parent` to version `11.3.0` + From f798774fc076206bf5e58b55ed9b332bf27053cb Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 18 Sep 2024 11:06:49 +0200 Subject: [PATCH 09/11] Upgrade actions to `logging-parent/11.3.0` --- .github/workflows/build.yaml | 6 +++--- .github/workflows/codeql-analysis.yaml | 2 +- .github/workflows/deploy-site.yaml | 6 +++--- .github/workflows/merge-dependabot.yaml | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a64a794acf7..0e82c8e6b87 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -30,7 +30,7 @@ jobs: build: if: github.actor != 'dependabot[bot]' - uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@release/11.3.0 secrets: DV_ACCESS_TOKEN: ${{ startsWith(github.ref_name, 'release/') && '' || secrets.GE_ACCESS_TOKEN }} with: @@ -44,7 +44,7 @@ jobs: deploy-snapshot: needs: build if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.24.x' - uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@release/11.3.0 # Secrets for deployments secrets: NEXUS_USERNAME: ${{ secrets.NEXUS_USER }} @@ -57,7 +57,7 @@ jobs: deploy-release: needs: build if: github.repository == 'apache/logging-log4j2' && startsWith(github.ref_name, 'release/') - uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@release/11.3.0 # Secrets for deployments secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml index 8d3643c10e3..6721abecf07 100644 --- a/.github/workflows/codeql-analysis.yaml +++ b/.github/workflows/codeql-analysis.yaml @@ -30,7 +30,7 @@ permissions: read-all jobs: analyze: - uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@release/11.3.0 with: java-version: | 8 diff --git a/.github/workflows/deploy-site.yaml b/.github/workflows/deploy-site.yaml index 543e50e4436..bf39a6185f2 100644 --- a/.github/workflows/deploy-site.yaml +++ b/.github/workflows/deploy-site.yaml @@ -33,7 +33,7 @@ jobs: deploy-site-stg: if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x' - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@release/11.3.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} @@ -53,7 +53,7 @@ jobs: deploy-site-pro: if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x-site-pro' - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@release/11.3.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} @@ -83,7 +83,7 @@ jobs: deploy-site-rel: needs: export-version - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@release/11.3.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} diff --git a/.github/workflows/merge-dependabot.yaml b/.github/workflows/merge-dependabot.yaml index 7fd1f9572a2..0655edad2f3 100644 --- a/.github/workflows/merge-dependabot.yaml +++ b/.github/workflows/merge-dependabot.yaml @@ -31,7 +31,7 @@ jobs: build: if: github.repository == 'apache/logging-log4j2' && github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' # Temporary use `develocity` branch - uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@release/11.3.0 secrets: DV_ACCESS_TOKEN: ${{ secrets.GE_ACCESS_TOKEN }} with: @@ -43,7 +43,7 @@ jobs: merge-dependabot: needs: build - uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@release/11.3.0 with: java-version: 17 permissions: From 92df5b51afe28045ffdd434f1c120ec184dec86b Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Wed, 18 Sep 2024 11:39:28 +0200 Subject: [PATCH 10/11] Update staging repo address --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6697e584cb0..a27008fa54b 100644 --- a/pom.xml +++ b/pom.xml @@ -547,7 +547,7 @@ false apache.staging - https://repository.apache.org/content/repositories/orgapachelogging-1296 + https://repository.apache.org/content/repositories/orgapachelogging-1297 From 149dbe70e8689d0b64cd058ba56638b53a4ca4e4 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sun, 22 Sep 2024 10:06:47 +0200 Subject: [PATCH 11/11] Cleanup after `11.3.0` official release --- .github/workflows/build.yaml | 6 +++--- .github/workflows/codeql-analysis.yaml | 2 +- .github/workflows/deploy-site.yaml | 6 +++--- .github/workflows/merge-dependabot.yaml | 4 ++-- pom.xml | 10 ---------- 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0e82c8e6b87..8f1498f3f5b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -30,7 +30,7 @@ jobs: build: if: github.actor != 'dependabot[bot]' - uses: apache/logging-parent/.github/workflows/build-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.3.0 secrets: DV_ACCESS_TOKEN: ${{ startsWith(github.ref_name, 'release/') && '' || secrets.GE_ACCESS_TOKEN }} with: @@ -44,7 +44,7 @@ jobs: deploy-snapshot: needs: build if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.24.x' - uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-snapshot-reusable.yaml@rel/11.3.0 # Secrets for deployments secrets: NEXUS_USERNAME: ${{ secrets.NEXUS_USER }} @@ -57,7 +57,7 @@ jobs: deploy-release: needs: build if: github.repository == 'apache/logging-log4j2' && startsWith(github.ref_name, 'release/') - uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-release-reusable.yaml@rel/11.3.0 # Secrets for deployments secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} diff --git a/.github/workflows/codeql-analysis.yaml b/.github/workflows/codeql-analysis.yaml index 6721abecf07..cc2dbcaaf85 100644 --- a/.github/workflows/codeql-analysis.yaml +++ b/.github/workflows/codeql-analysis.yaml @@ -30,7 +30,7 @@ permissions: read-all jobs: analyze: - uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/codeql-analysis-reusable.yaml@rel/11.3.0 with: java-version: | 8 diff --git a/.github/workflows/deploy-site.yaml b/.github/workflows/deploy-site.yaml index bf39a6185f2..580166e9250 100644 --- a/.github/workflows/deploy-site.yaml +++ b/.github/workflows/deploy-site.yaml @@ -33,7 +33,7 @@ jobs: deploy-site-stg: if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x' - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.3.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} @@ -53,7 +53,7 @@ jobs: deploy-site-pro: if: github.repository == 'apache/logging-log4j2' && github.ref_name == '2.x-site-pro' - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.3.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} @@ -83,7 +83,7 @@ jobs: deploy-site-rel: needs: export-version - uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/deploy-site-reusable.yaml@rel/11.3.0 # Secrets for committing the generated site secrets: GPG_SECRET_KEY: ${{ secrets.LOGGING_GPG_SECRET_KEY }} diff --git a/.github/workflows/merge-dependabot.yaml b/.github/workflows/merge-dependabot.yaml index 0655edad2f3..c0bda2b032f 100644 --- a/.github/workflows/merge-dependabot.yaml +++ b/.github/workflows/merge-dependabot.yaml @@ -31,7 +31,7 @@ jobs: build: if: github.repository == 'apache/logging-log4j2' && github.event_name == 'pull_request_target' && github.actor == 'dependabot[bot]' # Temporary use `develocity` branch - uses: apache/logging-parent/.github/workflows/build-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/build-reusable.yaml@rel/11.3.0 secrets: DV_ACCESS_TOKEN: ${{ secrets.GE_ACCESS_TOKEN }} with: @@ -43,7 +43,7 @@ jobs: merge-dependabot: needs: build - uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@release/11.3.0 + uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@rel/11.3.0 with: java-version: 17 permissions: diff --git a/pom.xml b/pom.xml index a27008fa54b..dcd499d2d38 100644 --- a/pom.xml +++ b/pom.xml @@ -541,16 +541,6 @@ - - - - false - - apache.staging - https://repository.apache.org/content/repositories/orgapachelogging-1297 - - -