You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by ck...@apache.org on 2022/01/07 22:02:59 UTC

[logging-log4j2] branch master updated (c1e006c -> ffdfc53)

This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git.


    from c1e006c  Document changes to lookups in 2.17.0
     new 05fc466  LOG4J2-3282: log4j-to-jul JDK Logging Bridge (#653)
     new ffdfc53  LOG4J2-3282 changelog

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 {log4j-to-slf4j => log4j-to-jul}/pom.xml           |  41 ++--
 .../org/apache/logging/log4j/tojul/JULLogger.java  | 219 +++++++++++----------
 .../logging/log4j/tojul/JULLoggerContext.java      |  15 +-
 .../log4j/tojul/JULLoggerContextFactory.java       | 141 ++++++-------
 .../apache/logging/log4j/tojul/JULProvider.java    |  12 +-
 .../apache/logging/log4j/tojul}/package-info.java  |   7 +-
 .../services/org.apache.logging.log4j.spi.Provider |  15 +-
 .../src/site/markdown/index.md                     |  20 +-
 {log4j-1.2-api => log4j-to-jul}/src/site/site.xml  |   2 +-
 .../org/apache/logging/log4j/tojul/LoggerTest.java | 204 +++++++++++++++++++
 pom.xml                                            |   8 +
 src/changes/changes.xml                            |   4 +
 12 files changed, 446 insertions(+), 242 deletions(-)
 copy {log4j-to-slf4j => log4j-to-jul}/pom.xml (86%)
 copy log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java => log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLogger.java (50%)
 copy log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContext.java => log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java (78%)
 copy log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java => log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContextFactory.java (68%)
 copy log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jProvider.java => log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java (74%)
 copy {log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api => log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul}/package-info.java (74%)
 copy log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh => log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider (71%)
 mode change 100755 => 100644
 copy {log4j-to-slf4j => log4j-to-jul}/src/site/markdown/index.md (68%)
 copy {log4j-1.2-api => log4j-to-jul}/src/site/site.xml (98%)
 create mode 100644 log4j-to-jul/src/test/java/org/apache/logging/log4j/tojul/LoggerTest.java

[logging-log4j2] 02/02: LOG4J2-3282 changelog

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit ffdfc534e573a660d0fe8ca8c0e9ea84b80dd229
Author: Carter Kozak <ck...@apache.org>
AuthorDate: Fri Jan 7 16:59:44 2022 -0500

    LOG4J2-3282 changelog
---
 src/changes/changes.xml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 033a7a8..cf75c6f 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -251,6 +251,10 @@
       <action dev="ggregory" type="fix">
         Log4j 1.2 bridge adds org.apache.log4j.spi.DefaultRepositorySelector.
       </action>
+      <!-- ADD -->
+      <action issue="LOG4J2-3282" dev="ckozak" type="add" due-to="Michael Vorburger">
+        Add the log4j-to-jul JDK Logging Bridge
+      </action>
     </release>
     <release version="2.17.1" date="2021-MM-DD" description="GA Release 2.17.1">
       <action issue="LOG4J2-3264" dev="ckozak" type="fix" due-to="Yanming Zhou">

[logging-log4j2] 01/02: LOG4J2-3282: log4j-to-jul JDK Logging Bridge (#653)

Posted by ck...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ckozak pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 05fc46646733c6272c355e163e717fec01862749
Author: Michael Vorburger ⛑️ <vo...@apache.org>
AuthorDate: Fri Jan 7 22:55:48 2022 +0100

    LOG4J2-3282: log4j-to-jul JDK Logging Bridge (#653)
---
 log4j-to-jul/pom.xml                               | 175 ++++++++++++++
 .../org/apache/logging/log4j/tojul/JULLogger.java  | 267 +++++++++++++++++++++
 .../logging/log4j/tojul/JULLoggerContext.java      |  72 ++++++
 .../log4j/tojul/JULLoggerContextFactory.java       |  72 ++++++
 .../apache/logging/log4j/tojul/JULProvider.java    |  30 +++
 .../apache/logging/log4j/tojul/package-info.java   |  23 ++
 .../services/org.apache.logging.log4j.spi.Provider |  18 ++
 log4j-to-jul/src/site/markdown/index.md            |  41 ++++
 log4j-to-jul/src/site/site.xml                     |  52 ++++
 .../org/apache/logging/log4j/tojul/LoggerTest.java | 204 ++++++++++++++++
 pom.xml                                            |   8 +
 11 files changed, 962 insertions(+)

diff --git a/log4j-to-jul/pom.xml b/log4j-to-jul/pom.xml
new file mode 100644
index 0000000..95c209e
--- /dev/null
+++ b/log4j-to-jul/pom.xml
@@ -0,0 +1,175 @@
+<?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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.logging.log4j</groupId>
+    <artifactId>log4j</artifactId>
+    <version>2.17.2-SNAPSHOT</version>
+    <relativePath>../</relativePath>
+  </parent>
+  <artifactId>log4j-to-jul</artifactId>
+  <packaging>jar</packaging>
+  <name>Apache Log4j to JUL Bridge</name>
+  <description>The Apache Log4j binding between Log4j 2 API and java.util.logging (JUL).</description>
+  <properties>
+    <log4jParentDir>${basedir}/..</log4jParentDir>
+    <docLabel>Log4j to JUL Documentation</docLabel>
+    <projectDir>/log4j-to-jul</projectDir>
+    <module.name>org.apache.logging.tojul</module.name>
+    <maven.doap.skip>true</maven.doap.skip>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava-testlib</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.vintage</groupId>
+      <artifactId>junit-vintage-engine</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter-engine</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <!-- Include the standard NOTICE and LICENSE -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-remote-resources-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>process</goal>
+            </goals>
+            <configuration>
+              <skip>false</skip>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <configuration>
+          <instructions>
+            <Export-Package>org.apache.logging.tojul</Export-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-changes-plugin</artifactId>
+        <version>${changes.plugin.version}</version>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>changes-report</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+        <configuration>
+          <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+          <useJql>true</useJql>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>${checkstyle.plugin.version}</version>
+        <configuration>
+          <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+          <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+          <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+          <enableRulesSummary>false</enableRulesSummary>
+          <propertyExpansion>basedir=${basedir}</propertyExpansion>
+          <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>${javadoc.plugin.version}</version>
+        <configuration>
+          <bottom><![CDATA[<p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+            Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+            and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+          <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+               project -->
+          <detectOfflineLinks>false</detectOfflineLinks>
+          <linksource>true</linksource>
+        </configuration>
+        <reportSets>
+          <reportSet>
+            <id>non-aggregate</id>
+            <reports>
+              <report>javadoc</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+      <plugin>
+        <groupId>com.github.spotbugs</groupId>
+        <artifactId>spotbugs-maven-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>${jxr.plugin.version}</version>
+        <reportSets>
+          <reportSet>
+            <id>non-aggregate</id>
+            <reports>
+              <report>jxr</report>
+            </reports>
+          </reportSet>
+          <reportSet>
+            <id>aggregate</id>
+            <reports>
+              <report>aggregate</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>${pmd.plugin.version}</version>
+        <configuration>
+          <targetJdk>${maven.compiler.target}</targetJdk>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
diff --git a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLogger.java b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLogger.java
new file mode 100644
index 0000000..52c5299
--- /dev/null
+++ b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLogger.java
@@ -0,0 +1,267 @@
+/*
+ * 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.tojul;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.spi.AbstractLogger;
+
+/**
+ * Implementation of {@link org.apache.logging.log4j.Logger} that's backed by a {@link Logger}.
+ *
+ * This implementation currently ignores {@link Marker}.
+ *
+ * @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a> for Google
+ */
+final class JULLogger extends AbstractLogger {
+    private static final long serialVersionUID = 1L;
+
+    private final Logger logger;
+
+    // This implementation is inspired by org.apache.logging.slf4j.SLF4JLogger
+
+    public JULLogger(final String name, final MessageFactory messageFactory, final Logger logger) {
+        super(name, messageFactory);
+        this.logger = requireNonNull(logger, "logger");
+    }
+
+    public JULLogger(final String name, final Logger logger) {
+        super(name);
+        this.logger = requireNonNull(logger, "logger");
+    }
+
+    public Logger getWrappedLogger() {
+        return logger;
+    }
+
+    @Override
+    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) {
+        java.util.logging.Level julLevel = convertLevel(level);
+        if (!logger.isLoggable(julLevel)) {
+            return;
+        }
+        LogRecord record = new LogRecord(julLevel, message.getFormattedMessage()); // NOT getFormat()
+        // NOT record.setParameters(message.getParameters()); BECAUSE getFormattedMessage() NOT getFormat()
+        record.setLoggerName(getName());
+        record.setThrown(t == null ? message.getThrowable() : t);
+        // Source class/method is not supported (yet)
+        record.setSourceClassName(null);
+        record.setSourceMethodName(null);
+        logger.log(record);
+        // fqcn is un-used
+    }
+
+    // Convert Level in Log4j scale to JUL scale.
+    // See getLevel() for the mapping. Note that JUL's FINEST & CONFIG are never returned because Log4j has no such levels, and
+    // that Log4j's FATAL is simply mapped to JUL's SEVERE as is Log4j's ERROR because JUL does not distinguish between ERROR and FATAL.
+    private java.util.logging.Level convertLevel(final Level level) {
+        switch (level.getStandardLevel()) {
+            // Test in logical order of likely frequency of use
+            // Must be kept in sync with #getLevel()
+            case ALL:
+                return java.util.logging.Level.ALL;
+            case TRACE:
+                return java.util.logging.Level.FINER;
+            case DEBUG:
+                return java.util.logging.Level.FINE;
+            case INFO:
+                return java.util.logging.Level.INFO;
+            case WARN:
+                return java.util.logging.Level.WARNING;
+            case ERROR:
+                return java.util.logging.Level.SEVERE;
+            case FATAL:
+                return java.util.logging.Level.SEVERE;
+            case OFF:
+                return java.util.logging.Level.OFF;
+            default:
+                // This is tempting: throw new IllegalStateException("Impossible Log4j Level encountered: " + level.intLevel());
+                // But it's not a great idea, security wise. If an attacker *SOMEHOW* managed to create a Log4j Level instance
+                // with an unexpected level (through JVM de-serialization, despite readResolve() { return Level.valueOf(this.name); },
+                // or whatever other means), then we would blow up in a very unexpected place and way. Let us therefore instead just
+                // return SEVERE for unexpected values, because that's more likely to be noticed than a FINER.
+                // Greetings, Michael Vorburger.ch <http://www.vorburger.ch>, for Google, on 2021.12.24.
+                return java.util.logging.Level.SEVERE;
+        }
+    }
+
+    /**
+     * Level in Log4j scale.
+     * JUL Levels are mapped as follows:
+     * <ul>
+     * <li>OFF => OFF
+     * <li>SEVERE => ERROR
+     * <li>WARNING => WARN
+     * <li>INFO => INFO
+     * <li>CONFIG => INFO
+     * <li>FINE => DEBUG
+     * <li>FINER => TRACE (as in https://github.com/apache/logging-log4j2/blob/a58a06bf2365165ac5abdde931bb4ecd1adf0b3c/log4j-jul/src/main/java/org/apache/logging/log4j/jul/DefaultLevelConverter.java#L55-L75)
+     * <li>FINEST => TRACE
+     * <li>ALL => ALL
+     * </ul>
+     *
+     * Numeric JUL Levels that don't match the known levels are matched to the closest one.
+     * For example, anything between OFF (Integer.MAX_VALUE) and SEVERE (1000) is returned as a Log4j FATAL.
+     */
+    @Override
+    public Level getLevel() {
+        int julLevel = getEffectiveJULLevel().intValue();
+        // Test in logical order of likely frequency of use
+        // Must be kept in sync with #convertLevel()
+        if (julLevel == java.util.logging.Level.ALL.intValue()) {
+            return Level.ALL;
+        }
+        if (julLevel <= java.util.logging.Level.FINER.intValue()) {
+            return Level.TRACE;
+        }
+        if (julLevel <= java.util.logging.Level.FINE.intValue()) {  // includes FINER
+            return Level.DEBUG;
+        }
+        if (julLevel <= java.util.logging.Level.INFO.intValue()) { // includes CONFIG
+            return Level.INFO;
+        }
+        if (julLevel <= java.util.logging.Level.WARNING.intValue()) {
+            return Level.WARN;
+        }
+        if (julLevel <= java.util.logging.Level.SEVERE.intValue()) {
+            return Level.ERROR;
+        }
+        return Level.OFF;
+    }
+
+    private java.util.logging.Level getEffectiveJULLevel() {
+        Logger current = logger;
+        while (current.getLevel() == null && current.getParent() != null) {
+            current = current.getParent();
+        }
+        if (current.getLevel() != null) {
+            return current.getLevel();
+        }
+        // This is a safety fallback that is typically never reached, because usually the root Logger.getLogger("") has a Level.
+        return Logger.getGlobal().getLevel();
+    }
+
+    private boolean isEnabledFor(final Level level, final Marker marker) {
+        // E.g. we're logging WARN and more, so getLevel() is 300, if we're asked if we're
+        // enabled for level ERROR which is 200, isLessSpecificThan() tests for >= so return true.
+        return getLevel().isLessSpecificThan(level);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final Message data, final Throwable t) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final CharSequence data, final Throwable t) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final Object data, final Throwable t) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String data) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String data, final Object... p1) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3,
+            final Object p4) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3,
+            final Object p4, final Object p5) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3,
+            final Object p4, final Object p5, final Object p6) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3,
+            final Object p4, final Object p5, final Object p6,
+            final Object p7) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3,
+            final Object p4, final Object p5, final Object p6,
+            final Object p7, final Object p8) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object p0,
+            final Object p1, final Object p2, final Object p3,
+            final Object p4, final Object p5, final Object p6,
+            final Object p7, final Object p8, final Object p9) {
+        return isEnabledFor(level, marker);
+    }
+
+    @Override
+    public boolean isEnabled(final Level level, final Marker marker, final String data, final Throwable t) {
+        return isEnabledFor(level, marker);
+    }
+}
diff --git a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java
new file mode 100644
index 0000000..4dca60e
--- /dev/null
+++ b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContext.java
@@ -0,0 +1,72 @@
+/*
+ * 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.tojul;
+
+import java.util.logging.Logger;
+import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.apache.logging.log4j.spi.LoggerRegistry;
+
+/**
+ * Implementation of Log4j {@link LoggerContext} SPI.
+ * This is a factory to produce {@link JULLogger} instances.
+ *
+ * @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a> for Google
+ */
+class JULLoggerContext implements LoggerContext {
+    private final LoggerRegistry<ExtendedLogger> loggerRegistry = new LoggerRegistry<>();
+
+    // This implementation is strongly inspired by org.apache.logging.slf4j.SLF4JLoggerContext
+
+    @Override
+    public Object getExternalContext() {
+        return null;
+    }
+
+    @Override
+    public ExtendedLogger getLogger(final String name) {
+        if (!loggerRegistry.hasLogger(name)) {
+            loggerRegistry.putIfAbsent(name, null, new JULLogger(name, Logger.getLogger(name)));
+        }
+        return loggerRegistry.getLogger(name);
+    }
+
+    @Override
+    public ExtendedLogger getLogger(final String name, final MessageFactory messageFactory) {
+        if (!loggerRegistry.hasLogger(name, messageFactory)) {
+            loggerRegistry.putIfAbsent(name, messageFactory,
+                    new JULLogger(name, messageFactory, Logger.getLogger(name)));
+        }
+        return loggerRegistry.getLogger(name, messageFactory);
+    }
+
+    @Override
+    public boolean hasLogger(final String name) {
+        return loggerRegistry.hasLogger(name);
+    }
+
+    @Override
+    public boolean hasLogger(final String name, final MessageFactory messageFactory) {
+        return loggerRegistry.hasLogger(name, messageFactory);
+    }
+
+    @Override
+    public boolean hasLogger(final String name, final Class<? extends MessageFactory> messageFactoryClass) {
+        return loggerRegistry.hasLogger(name, messageFactoryClass);
+    }
+}
diff --git a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContextFactory.java b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContextFactory.java
new file mode 100644
index 0000000..f44a0d4
--- /dev/null
+++ b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULLoggerContextFactory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.tojul;
+
+import java.net.URI;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+/**
+ * Implementation of Log4j {@link LoggerContextFactory} SPI.
+ * This is a factory to produce the (one and only) {@link JULLoggerContext} instance.
+ *
+ * @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a> for Google
+ */
+public class JULLoggerContextFactory implements LoggerContextFactory {
+    private static final StatusLogger LOGGER = StatusLogger.getLogger();
+    private static final LoggerContext context = new JULLoggerContext();
+
+    // This implementation is strongly inspired by org.apache.logging.slf4j.SLF4JLoggerContextFactory
+
+    public JULLoggerContextFactory() {
+        boolean misconfigured = false;
+        try {
+            LoaderUtil.loadClass("org.apache.logging.log4j.jul.LogManager");
+            misconfigured = true;
+        } catch (final ClassNotFoundException classNotFoundIsGood) {
+            LOGGER.debug("org.apache.logging.log4j.jul.LogManager is not on classpath. Good!");
+        }
+        if (misconfigured) {
+            throw new IllegalStateException("log4j-jul JAR is mutually exclusive with the log4j-to-jul JAR"
+                    + "(the first routes calls from Log4j to JUL, the second from Log4j to JUL)");
+        }
+    }
+
+    @Override
+    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
+                                    final boolean currentContext) {
+        return context;
+    }
+
+    @Override
+    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
+                                    final boolean currentContext, final URI configLocation, final String name) {
+        return context;
+    }
+
+    @Override
+    public void removeContext(final LoggerContext ignored) {
+    }
+
+    @Override
+    public boolean isClassLoaderDependent() {
+        // context is always used
+        return false;
+    }
+}
diff --git a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java
new file mode 100644
index 0000000..2c57ef9
--- /dev/null
+++ b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/JULProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.tojul;
+
+import org.apache.logging.log4j.spi.Provider;
+
+/**
+ * Bind the Log4j API to JUL.
+ *
+ * @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a> for Google
+ */
+public class JULProvider extends Provider {
+    public JULProvider() {
+        super(15, "2.6.0", JULLoggerContextFactory.class, null);
+    }
+}
diff --git a/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java
new file mode 100644
index 0000000..b6cd969
--- /dev/null
+++ b/log4j-to-jul/src/main/java/org/apache/logging/log4j/tojul/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Java JDK java.util.logging (JUL) bridge.
+ * This sends all Log4j logs to JUL (not the other way around, there is another module for the opposite direction).
+ *
+ * @author <a href="http://www.vorburger.ch">Michael Vorburger.ch</a> for Google
+ */
+package org.apache.logging.log4j.tojul;
diff --git a/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider b/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
new file mode 100644
index 0000000..2ac36b5
--- /dev/null
+++ b/log4j-to-jul/src/main/resources/META-INF/services/org.apache.logging.log4j.spi.Provider
@@ -0,0 +1,18 @@
+# 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.
+
+org.apache.logging.log4j.tojul.JULProvider
diff --git a/log4j-to-jul/src/site/markdown/index.md b/log4j-to-jul/src/site/markdown/index.md
new file mode 100644
index 0000000..ffa5af5
--- /dev/null
+++ b/log4j-to-jul/src/site/markdown/index.md
@@ -0,0 +1,41 @@
+<!-- vim: set syn=markdown : -->
+<!--
+    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.
+-->
+
+# Log4j to JUL Adapter
+
+The Log4j 2 to JUL Adapter allows applications coded to the Log4j 2 API to be routed to `java.util.logging` (JUL).
+
+Use of this adapter may cause some loss of performance as the Log4j 2 Messages must be formatted
+before they can be passed to JUL. With Log4j 2 as the implementation these would normally be
+formatted only when they are accessed by a Filter or Appender.
+
+## Requirements
+
+The Log4j 2 to JUL adapter is dependent on the Log4j 2 API.
+For more information, see [Runtime Dependencies](../runtime-dependencies.html).
+
+## Usage
+
+Include this JAR. Configure JUL as usual.
+
+<div class="alert alert-danger">
+Use of the JUL adapter (log4j-to-jul-2.x.jar) together with
+the JUL bridge (log4j-jul-2.x.jar) should
+never be attempted as it will cause events to endlessly be routed between
+SLF4J and JUL.
+</div>
\ No newline at end of file
diff --git a/log4j-to-jul/src/site/site.xml b/log4j-to-jul/src/site/site.xml
new file mode 100644
index 0000000..6b84b63
--- /dev/null
+++ b/log4j-to-jul/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ 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.
+
+-->
+<project name="Log4j Provider Using JUL"
+         xmlns="http://maven.apache.org/DECORATION/1.4.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+  <body>
+    <links>
+      <item name="Apache" href="http://www.apache.org/" />
+      <item name="Logging Services" href="http://logging.apache.org/"/>
+      <item name="Log4j" href="../index.html"/>
+    </links>
+
+    <!-- Component-specific reports -->
+    <menu ref="reports"/>
+
+	<!-- Overall Project Info -->
+    <menu name="Log4j Project Information" img="icon-info-sign">
+      <item name="Dependencies" href="../dependencies.html" />
+      <item name="Dependency Convergence" href="../dependency-convergence.html" />
+      <item name="Dependency Management" href="../dependency-management.html" />
+      <item name="Project Team" href="../team-list.html" />
+      <item name="Mailing Lists" href="../mail-lists.html" />
+      <item name="Issue Tracking" href="../issue-tracking.html" />
+      <item name="Project License" href="../license.html" />
+      <item name="Source Repository" href="../source-repository.html" />
+      <item name="Project Summary" href="../project-summary.html" />
+    </menu>
+
+    <menu name="Log4j Project Reports" img="icon-cog">
+      <item name="Changes Report" href="../changes-report.html" />
+      <item name="JIRA Report" href="../jira-report.html" />
+      <item name="Surefire Report" href="../surefire-report.html" />
+      <item name="RAT Report" href="../rat-report.html" />
+    </menu>
+  </body>
+</project>
diff --git a/log4j-to-jul/src/test/java/org/apache/logging/log4j/tojul/LoggerTest.java b/log4j-to-jul/src/test/java/org/apache/logging/log4j/tojul/LoggerTest.java
new file mode 100644
index 0000000..c418376
--- /dev/null
+++ b/log4j-to-jul/src/test/java/org/apache/logging/log4j/tojul/LoggerTest.java
@@ -0,0 +1,204 @@
+/*
+* 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.tojul;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.google.common.testing.TestLogHandler;
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import org.apache.logging.log4j.LogManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LoggerTest {
+
+    // Save levels so that we can reset them @After clearLogs()
+    private static final java.util.logging.Logger globalLogger = java.util.logging.Logger.getGlobal();
+    private static final java.util.logging.Logger rootLogger = java.util.logging.Logger.getLogger("");
+    private static final Level globalLevel = globalLogger.getLevel();
+    private static final Level rootLevel = rootLogger.getLevel();
+
+    private org.apache.logging.log4j.Logger log4jLogger;
+    private java.util.logging.Logger julLogger;
+    private Level julLoggerDefaultLevel;
+
+    // https://javadoc.io/doc/com.google.guava/guava-testlib/latest/com/google/common/testing/TestLogHandler.html
+    private TestLogHandler handler;
+
+
+    @Before public void setupLogCapture() {
+        handler = new TestLogHandler();
+        // Beware, the order here should not be changed!
+        // Let the bridge do whatever it does BEFORE we create a JUL Logger (which SHOULD be the same)
+        log4jLogger = LogManager.getLogger(getClass());
+        assertThat(log4jLogger).isInstanceOf(JULLogger.class);
+        julLogger = java.util.logging.Logger.getLogger(getClass().getName());
+        assertThat(julLogger).isSameAs(((JULLogger)log4jLogger).getWrappedLogger());
+        julLogger.addHandler(handler);
+
+        julLoggerDefaultLevel = julLogger.getLevel();
+
+        // Check that there is no configuration file which invalidates our assumption that the root logger is the parent of our julLogger
+        assertThat(julLogger.getParent()).isEqualTo(rootLogger);
+    }
+
+    @After public void clearLogs() {
+        julLogger.removeHandler(handler);
+        // Reset all Levels what any tests set anymore
+        julLogger.setLevel(julLoggerDefaultLevel);
+        rootLogger.setLevel(rootLevel);
+        globalLogger.setLevel(globalLevel);
+    }
+
+    @Test public void infoAtInfo() {
+        julLogger.setLevel(Level.INFO);
+        log4jLogger.info("hello, world");
+
+        List<LogRecord> logs = handler.getStoredLogRecords();
+        assertThat(logs).hasSize(1);
+        LogRecord log1 = logs.get(0);
+        assertThat(log1.getLoggerName()).isEqualTo(getClass().getName());
+        assertThat(log1.getLevel()).isEqualTo(java.util.logging.Level.INFO);
+        assertThat(log1.getMessage()).isEqualTo("hello, world");
+        assertThat(log1.getParameters()).isNull();
+        assertThat(log1.getThrown()).isNull();
+        assertThat(log1.getSourceClassName()).isNull();
+        assertThat(log1.getSourceMethodName()).isNull();
+    }
+
+    @Test public void infoAtInfoWithParameters() {
+        julLogger.setLevel(Level.INFO);
+        log4jLogger.info("hello, {}", "world");
+
+        List<LogRecord> logs = handler.getStoredLogRecords();
+        assertThat(logs).hasSize(1);
+        LogRecord log1 = logs.get(0);
+        assertThat(log1.getMessage()).isEqualTo("hello, world");
+        assertThat(log1.getParameters()).isNull();
+        assertThat(log1.getThrown()).isNull();
+    }
+
+    @Test public void errorAtSevereWithException() {
+        julLogger.setLevel(Level.SEVERE);
+        log4jLogger.error("hello, {}", "world", new IOException("Testing, testing"));
+
+        List<LogRecord> logs = handler.getStoredLogRecords();
+        assertThat(logs).hasSize(1);
+        LogRecord log1 = logs.get(0);
+        assertThat(log1.getMessage()).isEqualTo("hello, world");
+        assertThat(log1.getParameters()).isNull();
+        assertThat(log1.getThrown()).isInstanceOf(IOException.class);
+    }
+
+    @Test public void infoAtInfoWithLogBuilder() {
+        julLogger.setLevel(Level.INFO);
+        log4jLogger.atInfo().log("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @Test public void infoAtInfoOnParent() {
+        julLogger.getParent().setLevel(Level.INFO);
+        log4jLogger.info("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @Test public void infoWithoutAnyLevel() {
+        // We're not setting any level.
+        log4jLogger.info("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @Test public void debugAtInfo() {
+        julLogger.setLevel(Level.INFO);
+        log4jLogger.debug("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+    }
+
+    @Test public void debugAtFiner() {
+        julLogger.setLevel(Level.FINER);
+        log4jLogger.debug("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @Test public void traceAtFine() {
+        julLogger.setLevel(Level.FINE);
+        log4jLogger.trace("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+    }
+
+    @Test public void traceAtAllOnParent() {
+        julLogger.getParent().setLevel(Level.ALL);
+        log4jLogger.trace("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @Test public void fatalAtOff() {
+        julLogger.getParent().setLevel(Level.OFF);
+        log4jLogger.fatal("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+    }
+
+    @Test public void fatalAtSevere() {
+        julLogger.getParent().setLevel(Level.SEVERE);
+        log4jLogger.atFatal().log("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @Test public void warnAtFatal() {
+        julLogger.getParent().setLevel(Level.SEVERE);
+        log4jLogger.atWarn().log("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+    }
+
+    @Test public void customLevelJustUnderWarning() {
+        julLogger.getParent().setLevel(new CustomLevel("Just under Warning", Level.WARNING.intValue() - 1));
+
+        log4jLogger.info("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+
+        log4jLogger.warn("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+
+        log4jLogger.error("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(2);
+    }
+
+    @Test public void customLevelJustAboveWarning() {
+        julLogger.getParent().setLevel(new CustomLevel("Just above Warning", Level.WARNING.intValue() + 1));
+
+        log4jLogger.info("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+
+        log4jLogger.warn("hello, world");
+        assertThat(handler.getStoredLogRecords()).isEmpty();
+
+        log4jLogger.error("hello, world");
+        assertThat(handler.getStoredLogRecords()).hasSize(1);
+    }
+
+    @SuppressWarnings("serial")
+    private static class CustomLevel extends Level {
+        CustomLevel(String name, int value) {
+            super(name, value);
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index b6603a2..eeccb4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1016,6 +1016,13 @@
         <version>0.8.1</version>
         <scope>test</scope>
       </dependency>
+      <dependency>
+        <groupId>com.google.guava</groupId>
+        <!-- https://javadoc.io/doc/com.google.guava/guava-testlib/latest/com/google/common/testing/TestLogHandler.html used in log4j-to-jul tests -->
+        <artifactId>guava-testlib</artifactId>
+        <version>31.0.1-jre</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <build>
@@ -1714,6 +1721,7 @@
     <module>log4j-slf4j-impl</module>
     <module>log4j-slf4j18-impl</module>
     <module>log4j-to-slf4j</module>
+    <module>log4j-to-jul</module>
     <module>log4j-jcl</module>
     <module>log4j-jndi</module>
     <module>log4j-jndi-test</module>