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 ⛑️ <[email protected]>
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>

Reply via email to