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 © {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>
