You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2022/03/31 19:43:35 UTC
[logging-log4j2] 06/15: [LOG4J2-3423] Refactoring of URLConnection code
This is an automated email from the ASF dual-hosted git repository.
pkarwasz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 9fcb3c0153b4e4517eb7ee797234d3dfdee04ba1
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Wed Mar 9 22:36:33 2022 +0100
[LOG4J2-3423] Refactoring of URLConnection code
Refactors the code to minimize the number of calls to `URLConnection`
methods.
Conflicts:
log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java
log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java
log4j-core/src/test/java/org/apache/logging/log4j/core/net/UrlConnectionFactoryTest.java
log4j-core/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
---
.../org/apache/log4j/PropertyConfigurator.java | 5 +-
.../java/org/apache/log4j/xml/DOMConfigurator.java | 5 +-
.../log4j/core/config/ConfigurationSourceTest.java | 48 +++++++++++-
.../log4j/core/net/UrlConnectionFactoryTest.java | 83 +++++++++++++++------
.../logging/log4j/core/util/NetUtilsTest.java | 18 ++++-
.../log4j/core/config/ConfigurationFactory.java | 44 +++--------
.../log4j/core/config/ConfigurationSource.java | 10 +--
.../logging/log4j/core/config/HttpWatcher.java | 2 +
.../log4j/core/net/UrlConnectionFactory.java | 10 +--
log4j-core/src/test/resources/jarfile.jar | Bin 0 -> 1122 bytes
.../src/test/resources/jarfile/config/console.xml | 28 +++++++
11 files changed, 175 insertions(+), 78 deletions(-)
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
index 348646a..7f82b47 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
@@ -47,6 +47,7 @@ import org.apache.log4j.spi.RendererSupport;
import org.apache.log4j.spi.ThrowableRenderer;
import org.apache.log4j.spi.ThrowableRendererSupport;
import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.apache.logging.log4j.util.StackLocatorUtil;
/**
@@ -381,9 +382,7 @@ public class PropertyConfigurator implements Configurator {
Configuration doConfigure(final URL url, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
LogLog.debug("Reading configuration from URL " + url);
try {
- final URLConnection urlConnection = url.openConnection();
- // A "jar:" URL file remains open after the stream is closed, so do not cache it.
- urlConnection.setUseCaches(false);
+ final URLConnection urlConnection = UrlConnectionFactory.createConnection(url);
try (InputStream inputStream = urlConnection.getInputStream()) {
return doConfigure(inputStream, loggerRepository, classLoader);
}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java
index e5adf60..b0cbb58 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/DOMConfigurator.java
@@ -39,6 +39,7 @@ import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.apache.logging.log4j.core.util.IOUtils;
import org.w3c.dom.Element;
@@ -144,9 +145,7 @@ public class DOMConfigurator {
public void doConfigure(final URL url, final LoggerRepository repository) {
try {
- final URLConnection connection = url.openConnection();
- // A "jar:" URL file remains open after the stream is closed, so do not cache it.
- connection.setUseCaches(false);
+ final URLConnection connection = UrlConnectionFactory.createConnection(url);
try (InputStream inputStream = connection.getInputStream()) {
doConfigure(new ConfigurationSource(inputStream, url));
}
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 c067b71..9182e01 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
@@ -17,11 +17,23 @@
package org.apache.logging.log4j.core.config;
-import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
+import org.junit.jupiter.api.Test;
+
+import com.sun.management.UnixOperatingSystemMXBean;
public class ConfigurationSourceTest {
@@ -30,4 +42,36 @@ public class ConfigurationSourceTest {
ConfigurationSource configurationSource = new ConfigurationSource(new ByteArrayInputStream(new byte[] { 'a', 'b' }));
assertNotNull(configurationSource.resetInputStream());
}
+
+ /**
+ * Checks if the usage of 'jar:' URLs does not increase the file descriptor
+ * count and the jar file can be deleted.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testNoJarFileLeak() throws Exception {
+ final Path original = Paths.get("target", "test-classes", "jarfile.jar");
+ final Path copy = Paths.get("target", "test-classes", "jarfile-copy.jar");
+ Files.copy(original, copy);
+ final URL jarUrl = new URL("jar:" + copy.toUri().toURL() + "!/config/console.xml");
+ final long expected = getOpenFileDescriptorCount();
+ UrlConnectionFactory.createConnection(jarUrl).getInputStream().close();
+ // This can only fail on UNIX
+ assertEquals(expected, getOpenFileDescriptorCount());
+ // This can only fail on Windows
+ try {
+ Files.delete(copy);
+ } catch (IOException e) {
+ fail(e);
+ }
+ }
+
+ private long getOpenFileDescriptorCount() {
+ final OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
+ if (os instanceof UnixOperatingSystemMXBean) {
+ return ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();
+ }
+ return 0L;
+ }
}
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 07cd754..16f948e 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,18 +16,33 @@
*/
package org.apache.logging.log4j.core.net;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
import java.net.HttpURLConnection;
import java.net.URI;
+import java.net.URL;
import java.nio.file.Files;
import java.util.Base64;
import java.util.Enumeration;
-import java.util.Properties;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -38,16 +53,11 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
-
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import 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.sun.management.UnixOperatingSystemMXBean;
/**
* Tests the UrlConnectionFactory
@@ -60,9 +70,6 @@ public class UrlConnectionFactoryTest {
private static Server server;
private static Base64.Decoder decoder = Base64.getDecoder();
private static int port;
- private static final int NOT_MODIFIED = 304;
- private static final int NOT_AUTHORIZED = 401;
- private static final int OK = 200;
private static final int BUF_SIZE = 1024;
@BeforeAll
@@ -110,15 +117,16 @@ public class UrlConnectionFactoryTest {
assertNotNull(source, "No ConfigurationSource returned");
InputStream is = source.getInputStream();
assertNotNull(is, "No data returned");
+ is.close();
long lastModified = source.getLastModified();
int result = verifyNotModified(uri, lastModified);
- assertEquals(NOT_MODIFIED, result,"File was modified");
+ assertEquals(SC_NOT_MODIFIED, result,"File was modified");
File file = new File("target/test-classes/log4j2-config.xml");
if (!file.setLastModified(System.currentTimeMillis())) {
fail("Unable to set last modified time");
}
result = verifyNotModified(uri, lastModified);
- assertEquals(OK, result,"File was not modified");
+ assertEquals(SC_OK, result,"File was not modified");
}
private int verifyNotModified(URI uri, long lastModifiedMillis) throws Exception {
@@ -130,10 +138,34 @@ public class UrlConnectionFactoryTest {
return urlConnection.getResponseCode();
} catch (final IOException ioe) {
LOGGER.error("Error accessing configuration at {}: {}", uri, ioe.getMessage());
- return 500;
+ return SC_INTERNAL_SERVER_ERROR;
}
}
+ @Test
+ public void testNoJarFileLeak() throws Exception {
+ final URL url = new File("target/test-classes/jarfile.jar").toURI().toURL();
+ // Retrieve using 'file:'
+ URL jarUrl = new URL("jar:" + url.toString() + "!/config/console.xml");
+ long expected = getOpenFileDescriptorCount();
+ UrlConnectionFactory.createConnection(jarUrl).getInputStream().close();
+ assertEquals(expected, getOpenFileDescriptorCount());
+ // 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;
+ UrlConnectionFactory.createConnection(jarUrl).getInputStream().close();
+ assertEquals(expected, tmpDir.list().length, "File descriptor leak");
+ }
+
+ private long getOpenFileDescriptorCount() {
+ final OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
+ if (os instanceof UnixOperatingSystemMXBean) {
+ return ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();
+ }
+ return 0L;
+ }
+
public static class TestServlet extends DefaultServlet {
private static final long serialVersionUID = -2885158530511450659L;
@@ -143,7 +175,7 @@ public class UrlConnectionFactoryTest {
HttpServletResponse response) throws ServletException, IOException {
Enumeration<String> headers = request.getHeaders(HttpHeader.AUTHORIZATION.toString());
if (headers == null) {
- response.sendError(401, "No Auth header");
+ response.sendError(SC_UNAUTHORIZED, "No Auth header");
return;
}
while (headers.hasMoreElements()) {
@@ -151,26 +183,31 @@ public class UrlConnectionFactoryTest {
assertTrue(authData.startsWith(BASIC), "Not a Basic auth header");
String credentials = new String(decoder.decode(authData.substring(BASIC.length())));
if (!expectedCreds.equals(credentials)) {
- response.sendError(401, "Invalid credentials");
+ response.sendError(SC_UNAUTHORIZED, "Invalid credentials");
return;
}
}
- if (request.getServletPath().equals("/log4j2-config.xml")) {
- File file = new File("target/test-classes/log4j2-config.xml");
+ final String servletPath = request.getServletPath();
+ if (servletPath != null) {
+ final File file = new File("target/test-classes" + servletPath);
+ if (!file.exists()) {
+ response.sendError(SC_NOT_FOUND);
+ return;
+ }
long modifiedSince = request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.toString());
long lastModified = (file.lastModified() / 1000) * 1000;
LOGGER.debug("LastModified: {}, modifiedSince: {}", lastModified, modifiedSince);
if (modifiedSince > 0 && lastModified <= modifiedSince) {
- response.setStatus(304);
+ 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(200);
+ response.setStatus(SC_OK);
} else {
- response.sendError(400, "Unsupported request");
+ response.sendError(SC_BAD_REQUEST, "Unsupported request");
}
}
}
diff --git a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
index c7cddc6..c0f7ffa 100644
--- a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
+++ b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
@@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
+import java.io.File;
import java.net.URI;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -31,10 +32,25 @@ public class NetUtilsTest {
@Test
public void testToUriWithoutBackslashes() {
final String config = "file:///path/to/something/on/unix";
- final URI uri = NetUtils.toURI(config);
+ URI uri = NetUtils.toURI(config);
assertNotNull(uri, "The URI should not be null.");
assertEquals("file:///path/to/something/on/unix", uri.toString(), "The URI is not correct.");
+
+ final String properUriPath = "/path/without/spaces";
+ uri = NetUtils.toURI(properUriPath);
+
+ assertNotNull(uri, "The URI should not be null.");
+ assertEquals(properUriPath, uri.toString(), "The URI is not correct.");
+ }
+
+ @Test
+ public void testToUriUnixWithSpaces() {
+ final String pathWithSpaces = "/ path / with / spaces";
+ final URI uri = NetUtils.toURI(pathWithSpaces);
+
+ assertNotNull(uri, "The URI should not be null.");
+ assertEquals(new File(pathWithSpaces).toURI().toString(), uri.toString(), "The URI is not correct.");
}
@Test
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
index fac845f..8473e25 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFactory.java
@@ -16,12 +16,23 @@
*/
package org.apache.logging.log4j.core.config;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.core.net.UrlConnectionFactory;
import org.apache.logging.log4j.core.util.AuthorizationProvider;
import org.apache.logging.log4j.core.util.BasicAuthorizationProvider;
import org.apache.logging.log4j.core.util.FileUtils;
@@ -235,35 +246,4 @@ public abstract class ConfigurationFactory extends ConfigurationBuilderFactory {
return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
}
- /**
- * Loads the configuration from the location represented by the String.
- * @param config The configuration location.
- * @param loader The default ClassLoader to use.
- * @return The InputSource to use to read the configuration.
- */
- protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
- try {
- final URL url = new URL(config);
- final URLConnection urlConnection = UrlConnectionFactory.createConnection(url);
- final File file = FileUtils.fileFromUri(url.toURI());
- if (file != null) {
- return new ConfigurationSource(urlConnection.getInputStream(), FileUtils.fileFromUri(url.toURI()));
- } else {
- return new ConfigurationSource(urlConnection.getInputStream(), url, urlConnection.getLastModified());
- }
- } catch (final Exception ex) {
- final ConfigurationSource source = ConfigurationSource.fromResource(config, loader);
- if (source == null) {
- try {
- final File file = new File(config);
- return new ConfigurationSource(new FileInputStream(file), file);
- } catch (final FileNotFoundException fnfe) {
- // Ignore the exception
- LOGGER.catching(Level.DEBUG, fnfe);
- }
- }
- return source;
- }
- }
-
}
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 bb37919..24f7961 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
@@ -332,17 +332,9 @@ public class ConfigurationSource {
return null;
}
try {
- final URL url = configLocation.toURL();
- final URLConnection urlConnection = UrlConnectionFactory.createConnection(url);
- final InputStream is = urlConnection.getInputStream();
- final long lastModified = urlConnection.getLastModified();
- return new ConfigurationSource(is, configLocation.toURL(), lastModified);
- } catch (final FileNotFoundException ex) {
- ConfigurationFactory.LOGGER.warn("Could not locate file {}", configLocation.toString());
+ return getConfigurationSource(configLocation.toURL());
} catch (final MalformedURLException ex) {
ConfigurationFactory.LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
- } catch (final Exception ex) {
- ConfigurationFactory.LOGGER.error("Unable to access {}", configLocation.toString(), ex);
}
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 b781a4c..f6eb7e2 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
@@ -135,6 +135,8 @@ public class HttpWatcher extends AbstractWatcher {
}
} catch (final IOException ioe) {
LOGGER.error("Error accessing configuration at {}: {}", url, ioe.getMessage());
+ } finally {
+ urlConnection.disconnect();
}
} catch (final IOException ioe) {
LOGGER.error("Error connecting to configuration at {}: {}", url, ioe.getMessage());
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
index 65d4a13..fc72141 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
@@ -18,6 +18,7 @@ package org.apache.logging.log4j.core.net;
import java.io.IOException;
import java.net.HttpURLConnection;
+import java.net.JarURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;
@@ -77,8 +78,6 @@ public class UrlConnectionFactory {
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
urlConnection.setRequestMethod("GET");
- // A "jar:" URL file remains open after the stream is closed, so do not cache it.
- urlConnection.setUseCaches(false);
if (connectTimeoutMillis > 0) {
urlConnection.setConnectTimeout(connectTimeoutMillis);
}
@@ -107,13 +106,14 @@ public class UrlConnectionFactory {
urlConnection = createConnection(url, 0, SslConfigurationFactory.getSslConfiguration());
} else {
urlConnection = url.openConnection();
- // A "jar:" URL file remains open after the stream is closed, so do not cache it.
- urlConnection.setUseCaches(false);
+ if (urlConnection instanceof JarURLConnection) {
+ // A "jar:" URL file remains open after the stream is closed, so do not cache it.
+ urlConnection.setUseCaches(false);
+ }
}
return urlConnection;
}
-
private static boolean isXml(final String type) {
return type.equalsIgnoreCase("xml");
}
diff --git a/log4j-core/src/test/resources/jarfile.jar b/log4j-core/src/test/resources/jarfile.jar
new file mode 100644
index 0000000..bf34b8c
Binary files /dev/null and b/log4j-core/src/test/resources/jarfile.jar differ
diff --git a/log4j-core/src/test/resources/jarfile/config/console.xml b/log4j-core/src/test/resources/jarfile/config/console.xml
new file mode 100644
index 0000000..015ac0b
--- /dev/null
+++ b/log4j-core/src/test/resources/jarfile/config/console.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" />
+ </Appenders>
+ <Loggers>
+ <Root level="TRACE">
+ <AppenderRef ref="Console" />
+ </Root>
+ </Loggers>
+</Configuration>