diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a64a794acf7..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@rel/11.2.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@rel/11.2.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@rel/11.2.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 8d3643c10e3..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@rel/11.2.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 543e50e4436..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@rel/11.2.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@rel/11.2.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@rel/11.2.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/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 }} diff --git a/.github/workflows/merge-dependabot.yaml b/.github/workflows/merge-dependabot.yaml index 7fd1f9572a2..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@rel/11.2.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@rel/11.2.0 + uses: apache/logging-parent/.github/workflows/merge-dependabot-reusable.yaml@rel/11.3.0 with: java-version: 17 permissions: 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-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/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-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-mongodb/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml similarity index 70% rename from log4j-mongodb/src/test/resources/log4j2-mongodb-map-message.xml rename to log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml index 4b740bc947e..54c7f846f3f 100644 --- a/log4j-mongodb/src/test/resources/log4j2-mongodb-map-message.xml +++ b/log4j-core-test/src/test/resources/config/ConfigurationSourceTest.xml @@ -15,16 +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/log4j2-mutableFilter.xml b/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml similarity index 61% rename from log4j-core-test/src/test/resources/log4j2-mutableFilter.xml rename to log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml index 2f4e391d139..34eb059c4e0 100644 --- a/log4j-core-test/src/test/resources/log4j2-mutableFilter.xml +++ b/log4j-core-test/src/test/resources/filter/MutableThreadContextMapFilterTest.xml @@ -15,21 +15,21 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - + + - - - - - + - - - - - + + 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-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-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/MongoDbCappedIntTest.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/MongoDbCappedIntTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedIntIT.java index 500964aff08..887132ded40 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/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-int.xml") -public class MongoDbCappedIntTest 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/MongoDbCappedLongTest.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/MongoDbCappedLongTest.java rename to log4j-mongodb/src/test/java/org/apache/logging/log4j/mongodb/MongoDbCappedLongIT.java index 34f921fe7bf..bb6b4e65722 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/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-long.xml") -public class MongoDbCappedLongTest 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-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-mongodb/src/test/resources/MongoDbMapMessageIT.xml similarity index 68% rename from log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml rename to log4j-mongodb/src/test/resources/MongoDbMapMessageIT.xml index a84154e78da..dc6aa6846bd 100644 --- a/log4j-mongodb4/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/MongoDb4CappedIntTest.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/MongoDb4CappedIntTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedIntIT.java index e01e0c0d63b..24add71aa5d 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/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-int.xml") -public class MongoDb4CappedIntTest 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/MongoDb4CappedLongTest.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/MongoDb4CappedLongTest.java rename to log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedLongIT.java index 0a99dbf5b87..580c6c60fa6 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/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-long.xml") -public class MongoDb4CappedLongTest 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/MongoDb4MapMessageIT.xml b/log4j-mongodb4/src/test/resources/MongoDb4MapMessageIT.xml new file mode 100644 index 00000000000..8990a64afcc --- /dev/null +++ b/log4j-mongodb4/src/test/resources/MongoDb4MapMessageIT.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + 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 0bfb616f0f4..1faa00a7101 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 @@ -79,12 +78,9 @@ 1.2.15 3.4.4 - 8.15.0 0.9.0 7.0.5 - 4.13.1 - 1.8.0 - 3.0.22 + 4.0.22 33.3.0-jre 2.2.224 3.0 @@ -109,7 +105,6 @@ 1.7.0 4.0.5 0.6.0 - 9.4.55.v20240627 3.5.12 1.37 2.40.1 @@ -135,7 +130,6 @@ 2.7.18 5.3.39 2.0.3 - 10.0.27 1.7 2.35.2 2.10.0 @@ -178,7 +172,7 @@ - org.codehaus.groovy + org.apache.groovy groovy-bom ${groovy.version} pom @@ -201,14 +195,6 @@ import - - org.eclipse.jetty - jetty-bom - ${jetty.version} - pom - import - - org.junit junit-bom @@ -372,24 +358,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 @@ -402,12 +370,6 @@ ${disruptor.version} - - co.elastic.clients - elasticsearch-java - ${elasticsearch-java.version} - - org.zapodot embedded-ldap-junit @@ -814,12 +776,6 @@ ${system-stubs.version} - - org.apache.tomcat - tomcat-juli - ${tomcat-juli.version} - - org.apache.velocity velocity @@ -900,7 +856,7 @@ - + io.fabric8 docker-maven-plugin @@ -999,21 +955,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 diff --git a/pom.xml b/pom.xml index ca216b5a2ed..dcd499d2d38 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.apache.logging logging-parent - 11.2.0 + 11.3.0 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. + 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 + 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 + 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` +