diff --git a/jul-to-log4j/pom.xml b/jul-to-log4j/pom.xml new file mode 100644 index 00000000000..9e1842005f7 --- /dev/null +++ b/jul-to-log4j/pom.xml @@ -0,0 +1,133 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j + ${revision} + ../log4j-parent + + + jul-to-log4j + Apache Log4j JUL LogManager + A `java.util.logging` LogManager that forwards events to the Log4j API. + + + + + + org.jspecify.*;resolution:=optional + + + + org.jspecify;transitive=false + + + + + + + org.apache.logging.log4j + log4j-api + + + + org.apache.logging.log4j + log4j-kit + + + + org.assertj + assertj-core + test + + + + org.hamcrest + hamcrest + test + + + + junit + junit + test + + + + org.apache.logging.log4j + log4j-async-logger + test + + + + org.apache.logging.log4j + log4j-core + test + + + + org.apache.logging.log4j + log4j-core-test + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + -Xms256m -Xmx1024m + 1 + false + + + + + org.apache.maven.surefire + surefire-junit47 + ${surefire.version} + + + + + default-test + + test + + test + + + + org.apache.logging.jul.tolog4j.LogManager + + + + + + + + diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LevelTranslator.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java similarity index 93% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/LevelTranslator.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java index cc6ea5168bf..142b19fe759 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LevelTranslator.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LevelTranslator.java @@ -14,8 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j; +import org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter; +import org.apache.logging.jul.tolog4j.internal.JulProperties; +import org.apache.logging.jul.tolog4j.spi.LevelConverter; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.kit.env.PropertyEnvironment; diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java similarity index 66% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java index b92d87bdf8c..6e105aa679f 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/LogManager.java @@ -14,36 +14,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; -import org.apache.logging.log4j.LoggingException; +import org.apache.logging.jul.tolog4j.internal.ApiLoggerAdapter; +import org.apache.logging.jul.tolog4j.internal.JulProperties; +import org.apache.logging.jul.tolog4j.internal.NoOpLogger; +import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter; import org.apache.logging.log4j.kit.env.PropertyEnvironment; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.LoaderUtil; /** - * Log4j implementation of {@link java.util.logging.LogManager}. Note that the system property - * {@code java.util.logging.manager} must be set to {@code org.apache.logging.log4j.jul.LogManager} in order to use - * this adaptor. This LogManager requires the {@code log4j-api} library to be available. If {@code log4j-core} is - * also available, then more features of {@link java.util.logging.Logger} are supported. - * - *

To override the default {@link AbstractLoggerAdapter} that is used, specify the Log4j property - * {@code log4j.jul.LoggerAdapter} and set it to the fully qualified class name of a custom - * implementation. All implementations must have a default constructor.

+ * Log4j implementation of {@link java.util.logging.LogManager}. + *

+ * Note that the system property {@code java.util.logging.manager} must be set to + * {@code org.apache.logging.jul.tolog4j.LogManager} in order to use this adaptor. + * This LogManager requires the {@code log4j-api} library to be available. + *

+ *

+ * To override the default {@link AbstractLoggerAdapter} that is used, specify the Log4j property + * {@code log4j.jul.LoggerAdapter} and set it to the fully qualified class name of a custom + * implementation. + * All implementations must have a default constructor. + *

* * @since 2.1 */ public class LogManager extends java.util.logging.LogManager { private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); - private static final String CORE_LOGGER_CLASS_NAME = "org.apache.logging.log4j.core.Logger"; - private static final String CORE_LOGGER_ADAPTER_CLASS_NAME = "org.apache.logging.log4j.jul.CoreLoggerAdapter"; - private static final String API_LOGGER_ADAPTER_CLASS_NAME = "org.apache.logging.log4j.jul.ApiLoggerAdapter"; private final AbstractLoggerAdapter loggerAdapter; // Contains the set of logger names that are actively being requested using getLogger. private final ThreadLocal> recursive = ThreadLocal.withInitial(HashSet::new); @@ -62,21 +66,9 @@ public LogManager() { } } if (adapter == null) { - // default adapter - String adapterClassName; - try { - // find out if log4j-core is available - LoaderUtil.loadClass(CORE_LOGGER_CLASS_NAME); - adapterClassName = CORE_LOGGER_ADAPTER_CLASS_NAME; - } catch (final ClassNotFoundException ignored) { - adapterClassName = API_LOGGER_ADAPTER_CLASS_NAME; - } - LOGGER.debug("Attempting to use {}", adapterClassName); - try { - adapter = LoaderUtil.newCheckedInstanceOf(adapterClassName, AbstractLoggerAdapter.class); - } catch (final Exception e) { - throw LOGGER.throwing(new LoggingException(e)); - } + // Use API by default + // See https://github.com/apache/logging-log4j2/issues/2353 + adapter = new ApiLoggerAdapter(); } loggerAdapter = adapter; LOGGER.info("Registered Log4j as the java.util.logging.LogManager."); diff --git a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java new file mode 100644 index 00000000000..bde8a191009 --- /dev/null +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLogger.java @@ -0,0 +1,74 @@ +/* + * 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.jul.tolog4j.internal; + +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.support.AbstractLogger; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Implementation of {@link java.util.logging.Logger} that ignores all method calls that do not have an equivalent in + * the Log4j API. + */ +public class ApiLogger extends AbstractLogger { + + private static final String MUTATOR_DISABLED = + """ + Ignoring call to `j.ul.Logger.{}()`, since the Log4j API does not provide methods to modify the underlying implementation. + To modify the configuration using JUL, use an `AbstractLoggerAdapter` appropriate for your logging implementation. + See https://logging.apache.org/log4j/3.x/log4j-jul.html#log4j.jul.loggerAdapter for more information."""; + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + public ApiLogger(ExtendedLogger logger) { + super(logger); + } + + @Override + public void setFilter(Filter newFilter) throws SecurityException { + LOGGER.warn(MUTATOR_DISABLED, "setFilter"); + } + + @Override + public void setLevel(Level newLevel) throws SecurityException { + LOGGER.warn(MUTATOR_DISABLED, "setLevel"); + } + + @Override + public void addHandler(Handler handler) throws SecurityException { + LOGGER.warn(MUTATOR_DISABLED, "addHandler"); + } + + @Override + public void removeHandler(Handler handler) throws SecurityException { + LOGGER.warn(MUTATOR_DISABLED, "removeHandler"); + } + + @Override + public void setUseParentHandlers(boolean useParentHandlers) { + LOGGER.warn(MUTATOR_DISABLED, "setUseParentHandlers"); + } + + @Override + public void setParent(Logger parent) { + throw new UnsupportedOperationException( + ApiLogger.class.getSimpleName() + " does not support `j.u.l.Logger#setParent()`."); + } +} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLoggerAdapter.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java similarity index 88% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLoggerAdapter.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java index 03f994c68b8..6e6778f9e2d 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLoggerAdapter.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/ApiLoggerAdapter.java @@ -14,9 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j.internal; import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter; import org.apache.logging.log4j.message.MessageFactory; import org.apache.logging.log4j.message.MessageFormatMessageFactory; import org.apache.logging.log4j.spi.LoggerContext; @@ -32,7 +33,7 @@ public class ApiLoggerAdapter extends AbstractLoggerAdapter { private static final MessageFactory MESSAGE_FACTORY = new MessageFormatMessageFactory(); @Override - protected Logger newLogger(final String name, final LoggerContext context) { + public Logger newLogger(final String name, final LoggerContext context) { return new ApiLogger(context.getLogger(name, MESSAGE_FACTORY)); } } diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/DefaultLevelConverter.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java similarity index 97% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/DefaultLevelConverter.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java index 0a6bdbf6aba..9a8ed846b55 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/DefaultLevelConverter.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/DefaultLevelConverter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j.internal; import java.util.ArrayList; import java.util.Collections; @@ -24,6 +24,8 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.jul.tolog4j.spi.LevelConverter; import org.apache.logging.log4j.Level; /** diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/JulProperties.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java similarity index 86% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/JulProperties.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java index ec5a4af0790..1b500b678bc 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/JulProperties.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/JulProperties.java @@ -14,8 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j.internal; +import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter; +import org.apache.logging.jul.tolog4j.spi.LevelConverter; import org.apache.logging.log4j.kit.env.Log4jProperty; import org.jspecify.annotations.Nullable; diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/NoOpLogger.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java similarity index 98% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/NoOpLogger.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java index 2ed1c4f0289..879d19abb43 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/NoOpLogger.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/internal/NoOpLogger.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j.internal; import java.util.ResourceBundle; import java.util.function.Supplier; @@ -27,7 +27,7 @@ */ public class NoOpLogger extends Logger { - protected NoOpLogger(final String name) { + public NoOpLogger(final String name) { super(name, null); } diff --git a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java new file mode 100644 index 00000000000..d0fc9b2d63e --- /dev/null +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ +@Export +@Version("3.0.0") +package org.apache.logging.jul.tolog4j; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.java new file mode 100644 index 00000000000..8c43dc8a1a2 --- /dev/null +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/AbstractLoggerAdapter.java @@ -0,0 +1,67 @@ +/* + * 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.jul.tolog4j.spi; + +import java.util.logging.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.StackLocatorUtil; + +/** + * Abstract Logger registry. + *

+ * JUL contains methods, such as {@link Logger#setLevel}, which modify the configuration of the logging backend. + * To fully implement all {@code Logger} methods, we need to provide a different {@code Logger} implementation + * for each Log4j API implementation. + *

+ *

+ * Older Log4j versions provided an alternative {@code CoreLoggerAdapter} implementation that supported + * the modification of Log4j Core configuration using JUL. + *

+ * Since version 2.24.0, however, this implementation was deprecated for removal. + * If you wish to enable this feature again, you need to implement this class and provide its FQCN + * as {@code log4j.jul.loggerAdapter} configuration property. + *

+ *

+ * Implementation note: since version 3.0.0, this interface was moved to a new package. + *

+ * + * @see Issue #2353 + * @since 2.1 + */ +public abstract class AbstractLoggerAdapter extends org.apache.logging.log4j.spi.AbstractLoggerAdapter { + + /** + * Creates a new {@link java.util.logging.Logger} + *

+ * Each implementation should provide this method. + *

+ */ + @Override + public abstract Logger newLogger(String name, LoggerContext context); + + /** + * Provides the most appropriate {@link LoggerContext} for the caller. + */ + @Override + public LoggerContext getContext() { + return getContext( + LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(java.util.logging.LogManager.class) + : null); + } +} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LevelConverter.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java similarity index 87% rename from log4j-jul/src/main/java/org/apache/logging/log4j/jul/LevelConverter.java rename to jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java index d9d26116329..489477ebaa9 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LevelConverter.java +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/LevelConverter.java @@ -14,13 +14,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul; +package org.apache.logging.jul.tolog4j.spi; +import org.apache.logging.jul.tolog4j.internal.JulProperties; import org.apache.logging.log4j.Level; /** * Strategy interface to convert between custom Log4j {@link Level Levels} and JUL * {@link java.util.logging.Level Levels}. + *

+ * Implementation note: since version 3.0.0, this interface was moved to a new package. + *

* * @see JulProperties#levelConverter() * @since 2.1 diff --git a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java new file mode 100644 index 00000000000..b7eb30cbacc --- /dev/null +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/spi/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ +/** + * Contains interfaces an abstract classes to extend the functionality of Log4j JUL Adapter. + */ +@Export +@Version("3.0.0") +package org.apache.logging.jul.tolog4j.spi; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java new file mode 100644 index 00000000000..f91812b45b6 --- /dev/null +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/AbstractLogger.java @@ -0,0 +1,441 @@ +/* + * 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.jul.tolog4j.support; + +import static org.apache.logging.log4j.spi.AbstractLogger.ENTRY_MARKER; +import static org.apache.logging.log4j.spi.AbstractLogger.EXIT_MARKER; +import static org.apache.logging.log4j.spi.AbstractLogger.THROWING_MARKER; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.log4j.BridgeAware; +import org.apache.logging.log4j.LogBuilder; +import org.apache.logging.log4j.message.DefaultFlowMessageFactory; +import org.apache.logging.log4j.message.LocalizedMessage; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.spi.ExtendedLogger; + +/** + * Log4j API implementation of the JUL {@link Logger} class. + *

+ * Note that this implementation does not use the {@link java.util.logging.Handler} class. + * Instead, + * logging is delegated to the underlying Log4j {@link org.apache.logging.log4j.Logger} + * which may be implemented in one of many different ways. + * Consult the documentation for your Log4j API Provider for more details. + *

+ *

+ * Note that the methods {@link #getParent()} and mutator methods such as {@link #setLevel(java.util.logging.Level)} + * must be provided by implementations of this class. + * The default {@link org.apache.logging.jul.tolog4j.internal.ApiLogger} implementations just ignores them. + * If you need support for these methods, then you'll need to provide your own + * {@link org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter}. + *

+ * + * @since 3.0.0 + */ +public abstract class AbstractLogger extends Logger { + + private final ExtendedLogger logger; + private static final String FQCN = AbstractLogger.class.getName(); + + protected AbstractLogger(final ExtendedLogger logger) { + super(logger.getName(), null); + final Level javaLevel = LevelTranslator.toJavaLevel(logger.getLevel()); + super.setLevel(javaLevel); + this.logger = logger; + } + + @Override + public void log(final LogRecord record) { + final org.apache.logging.log4j.Level level = LevelTranslator.toLevel(record.getLevel()); + final Object[] parameters = record.getParameters(); + final MessageFactory messageFactory = logger.getMessageFactory(); + final Message message = parameters == null + ? messageFactory.newMessage(record.getMessage()) /* LOG4J2-1251: not formatted case */ + : messageFactory.newMessage(record.getMessage(), parameters); + final Throwable thrown = record.getThrown(); + logger.logIfEnabled(FQCN, level, null, message, thrown); + } + + // + // Methods + + @Override + public abstract void setFilter(Filter newFilter) throws SecurityException; + + @Override + public abstract void setLevel(Level newLevel) throws SecurityException; + + @Override + public abstract void addHandler(Handler handler) throws SecurityException; + + @Override + public abstract void removeHandler(Handler handler) throws SecurityException; + + @Override + public abstract void setUseParentHandlers(boolean useParentHandlers); + + @Override + public abstract void setParent(Logger parent); + + @Override + public Filter getFilter() { + return null; + } + + /** + * Returns the configured level of a logger. + *

+ * Implementation note: this method returns the level explicitly configured + * in the Log4j API logging implementation and is implementation specific. + * The default implementation always returns {@code null}. + *

+ *

+ * To test if a logger is enabled for a specific logging level, i.e. to test its effective + * level, use {@link Logger#isLoggable(Level)}. + *

+ * @see #isLoggable(Level) + */ + @Override + public Level getLevel() { + return null; + } + + @Override + public Handler[] getHandlers() { + return new Handler[0]; + } + + @Override + public boolean getUseParentHandlers() { + return false; + } + + @Override + public Logger getParent() { + return null; + } + + //
+ + // + // Implementation of methods used for logging + + @Override + public boolean isLoggable(final Level level) { + return logger.isEnabled(LevelTranslator.toLevel(level)); + } + + @Override + public String getName() { + return logger.getName(); + } + + private org.apache.logging.log4j.util.Supplier toLog4jSupplier(Supplier msgSupplier) { + return msgSupplier::get; + } + + private org.apache.logging.log4j.util.Supplier toMessageSupplier(Supplier msgSupplier) { + return () -> logger.getMessageFactory().newMessage(msgSupplier.get()); + } + + private org.apache.logging.log4j.util.Supplier toMessageSupplier(ResourceBundle bundle, String msg) { + return () -> new LocalizedMessage(bundle, msg); + } + + private org.apache.logging.log4j.util.Supplier toMessageSupplier( + ResourceBundle bundle, String msg, Object[] params) { + return () -> new LocalizedMessage(bundle, msg, params); + } + + private StackTraceElement toLocation(String sourceClass, String sourceMethod) { + return new StackTraceElement(sourceClass, sourceMethod, null, 0); + } + + @Override + public void log(final Level level, final String msg) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg); + } + + /** + * @since 3.0.0 + */ + @Override + public void log(Level level, Supplier msgSupplier) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void log(final Level level, final String msg, final Object param1) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg, param1); + } + + @Override + public void log(final Level level, final String msg, final Object[] params) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg, params); + } + + @Override + public void log(final Level level, final String msg, final Throwable thrown) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, msg, thrown); + } + + /** + * @since 3.0.0 + */ + @Override + public void log(Level level, Throwable thrown, Supplier msgSupplier) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, toLog4jSupplier(msgSupplier), thrown); + } + + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(msg); + } + + /** + * @since 3.0.0 + */ + @Override + public void logp(Level level, String sourceClass, String sourceMethod, Supplier msgSupplier) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(toMessageSupplier(msgSupplier)); + } + + @Override + public void logp( + final Level level, + final String sourceClass, + final String sourceMethod, + final String msg, + final Object param1) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(msg, param1); + } + + @Override + public void logp( + final Level level, + final String sourceClass, + final String sourceMethod, + final String msg, + final Object[] params) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(msg, params); + } + + @Override + public void logp( + final Level level, + final String sourceClass, + final String sourceMethod, + final String msg, + final Throwable thrown) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .withThrowable(thrown) + .log(msg); + } + + /** + * @since 3.0.0 + */ + @Override + public void logp( + Level level, String sourceClass, String sourceMethod, Throwable thrown, Supplier msgSupplier) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .withThrowable(thrown) + .log(toMessageSupplier(msgSupplier)); + } + + /** + * @since 3.0.0 + */ + @Override + public void logrb( + Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg, Object... params) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(toMessageSupplier(bundle, msg, params)); + } + + @Override + public void logrb( + Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) { + logger.atLevel(LevelTranslator.toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .withThrowable(thrown) + .log(toMessageSupplier(bundle, msg)); + } + + /** + * @since 3.0.0 + */ + @Override + public void logrb(Level level, ResourceBundle bundle, String msg, Object... params) { + logger.logIfEnabled(FQCN, LevelTranslator.toLevel(level), null, toMessageSupplier(bundle, msg, params), null); + } + + /** + * @since 3.0.0 + */ + @Override + public void logrb(Level level, ResourceBundle bundle, String msg, Throwable thrown) { + LogBuilder builder = logger.atLevel(LevelTranslator.toLevel(level)).withThrowable(thrown); + if (builder instanceof BridgeAware bridgeAware) { + bridgeAware.setEntryPoint(FQCN); + } + builder.log(toMessageSupplier(bundle, msg)); + } + + @Override + public void entering(final String sourceClass, final String sourceMethod) { + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(ENTRY_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, (Object[]) null)); + } + + @Override + public void entering(final String sourceClass, final String sourceMethod, final Object param1) { + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(ENTRY_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, param1)); + } + + @Override + public void entering(final String sourceClass, final String sourceMethod, final Object[] params) { + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(ENTRY_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, params)); + } + + @Override + public void exiting(final String sourceClass, final String sourceMethod) { + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(EXIT_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, (Object) null)); + } + + @Override + public void exiting(final String sourceClass, final String sourceMethod, final Object result) { + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(EXIT_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, result)); + } + + @Override + public void throwing(final String sourceClass, final String sourceMethod, final Throwable thrown) { + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(THROWING_MARKER) + .withThrowable(thrown) + .log("Throwing"); + } + + @Override + public void severe(final String msg) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, msg); + } + + /** + * @since 3.0.0 + */ + @Override + public void severe(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void warning(final String msg) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, msg); + } + + @Override + public void warning(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void info(final String msg) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, msg); + } + + @Override + public void info(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void config(final String msg) { + logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg); + } + + @Override + public void config(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void fine(final String msg) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, msg); + } + + @Override + public void fine(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void finer(final String msg) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, msg); + } + + @Override + public void finer(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, toLog4jSupplier(msgSupplier), null); + } + + @Override + public void finest(final String msg) { + logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg); + } + + @Override + public void finest(Supplier msgSupplier) { + logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, toLog4jSupplier(msgSupplier), null); + } + // +} diff --git a/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java new file mode 100644 index 00000000000..8a9b13054b2 --- /dev/null +++ b/jul-to-log4j/src/main/java/org/apache/logging/jul/tolog4j/support/package-info.java @@ -0,0 +1,26 @@ +/* + * 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. + */ +/** + * Utility classes that can be used in implementing providing implementation of the classes in + * {@link org.apache.logging.log4j.jul.spi}. + */ +@Export +@Version("3.0.0") +package org.apache.logging.jul.tolog4j.support; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-jul/src/main/resources/META-INF/log4j/propertyMapping.json b/jul-to-log4j/src/main/resources/META-INF/log4j/propertyMapping.json similarity index 100% rename from log4j-jul/src/main/resources/META-INF/log4j/propertyMapping.json rename to jul-to-log4j/src/main/resources/META-INF/log4j/propertyMapping.json diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AsyncLoggerThreadsTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java similarity index 98% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AsyncLoggerThreadsTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java index bb7553822b9..1ec5eb6b166 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AsyncLoggerThreadsTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/AsyncLoggerThreadsTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test; import static org.junit.Assert.assertEquals; diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/BracketInNotInterpolatedMessageTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java similarity index 95% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/BracketInNotInterpolatedMessageTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java index 18b382ad3be..3bb6fd0afca 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/BracketInNotInterpolatedMessageTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/BracketInNotInterpolatedMessageTest.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test; import static java.util.logging.Level.INFO; import static org.hamcrest.Matchers.hasSize; @@ -24,9 +24,9 @@ import java.util.List; import java.util.logging.LogRecord; import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LogManager; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.apache.logging.log4j.jul.LogManager; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; diff --git a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java new file mode 100644 index 00000000000..1562b70169e --- /dev/null +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/CallerInformationTest.java @@ -0,0 +1,162 @@ +/* + * 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.jul.tolog4j.test; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LogManager; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +public class CallerInformationTest { + + private static final String PARAM_1 = "PARAM_1"; + private static final String[] PARAMS = {PARAM_1, "PARAM_2"}; + private static final String SOURCE_CLASS = "SourceClass"; + private static final String SOURCE_METHOD = "sourceMethod"; + + @Rule + public final LoggerContextRule ctx = new LoggerContextRule("CallerInformationTest.xml"); + + @BeforeClass + public static void setUpClass() { + System.setProperty("java.util.logging.manager", LogManager.class.getName()); + } + + @AfterClass + public static void tearDownClass() { + System.clearProperty("java.util.logging.manager"); + } + + @Test + public void testClassLogger() { + final ListAppender app = ctx.getListAppender("Class").clear(); + final Logger logger = Logger.getLogger("ClassLogger"); + // Eager methods + logger.severe("CATASTROPHE INCOMING!"); + logger.warning("ZOMBIES!!!"); + logger.info("brains~~~"); + logger.config("Config!"); + logger.fine("Itchy. Tasty."); + logger.finer("Finer message."); + logger.finest("Finest message."); + logger.log(Level.FINEST, "Finest message."); + logger.log(Level.FINEST, "Message of level {1}.", Level.FINEST); + logger.log(Level.FINEST, "Hello {1} and {2}!.", new Object[] {"foo", "bar"}); + // Lazy methods + logger.severe(() -> "CATASTROPHE INCOMING!"); + logger.warning(() -> "ZOMBIES!!!"); + logger.info(() -> "brains~~~"); + logger.config(() -> "Config!"); + logger.fine(() -> "Itchy. Tasty."); + logger.finer(() -> "Finer message."); + logger.finest(() -> "Finest message."); + logger.log(Level.FINEST, () -> "Finest message."); + logger.log(Level.FINEST, new RuntimeException(), () -> "Message with exception."); + List messages = app.getMessages(); + assertEquals("Incorrect number of messages.", 19, messages.size()); + for (int i = 0; i < messages.size(); i++) { + String message = messages.get(i); + assertEquals( + "Incorrect caller class name for message " + i, + this.getClass().getName(), + message); + } + + // Test passing the location information directly + app.clear(); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!"); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1}!", PARAM_1); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1} and {2}!", PARAMS); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!", new RuntimeException()); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, () -> "Hello" + PARAM_1 + "!"); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, new RuntimeException(), () -> "Hello " + PARAM_1 + "!"); + logger.entering(SOURCE_CLASS, SOURCE_METHOD); + logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAM_1); + logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAMS); + logger.exiting(SOURCE_CLASS, SOURCE_METHOD); + logger.exiting(SOURCE_CLASS, SOURCE_METHOD, PARAM_1); + logger.throwing(SOURCE_CLASS, SOURCE_METHOD, new RuntimeException()); + messages = app.getMessages(); + assertEquals("Incorrect number of messages.", 12, messages.size()); + for (int i = 0; i < messages.size(); i++) { + String message = messages.get(i); + assertEquals("Incorrect caller class name for message " + i, SOURCE_CLASS, message); + } + } + + @Test + public void testMethodLogger() { + final ListAppender app = ctx.getListAppender("Method").clear(); + final Logger logger = Logger.getLogger("MethodLogger"); + // Eager methods + logger.severe("CATASTROPHE INCOMING!"); + logger.warning("ZOMBIES!!!"); + logger.info("brains~~~"); + logger.config("Config!"); + logger.fine("Itchy. Tasty."); + logger.finer("Finer message."); + logger.finest("Finest message."); + logger.log(Level.FINEST, "Finest message."); + logger.log(Level.FINEST, "Message of level {1}.", Level.FINEST); + logger.log(Level.FINEST, "Hello {1} and {2}!.", new Object[] {"foo", "bar"}); + // Lazy methods + logger.severe(() -> "CATASTROPHE INCOMING!"); + logger.warning(() -> "ZOMBIES!!!"); + logger.info(() -> "brains~~~"); + logger.config(() -> "Config!"); + logger.fine(() -> "Itchy. Tasty."); + logger.finer(() -> "Finer message."); + logger.finest(() -> "Finest message."); + logger.log(Level.FINEST, () -> "Finest message."); + logger.log(Level.FINEST, new RuntimeException(), () -> "Message with exception."); + List messages = app.getMessages(); + assertEquals("Incorrect number of messages.", 19, messages.size()); + for (int i = 0; i < messages.size(); i++) { + String message = messages.get(i); + assertEquals("Incorrect caller class name for message " + i, "testMethodLogger", message); + } + + // Test passing the location information directly + app.clear(); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!"); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1}!", PARAM_1); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello {1} and {2}!", PARAMS); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, "Hello!", new RuntimeException()); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, () -> "Hello " + PARAM_1 + "!"); + logger.logp(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, new RuntimeException(), () -> "Hello " + PARAM_1 + "!"); + logger.entering(SOURCE_CLASS, SOURCE_METHOD); + logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAM_1); + logger.entering(SOURCE_CLASS, SOURCE_METHOD, PARAMS); + logger.exiting(SOURCE_CLASS, SOURCE_METHOD); + logger.exiting(SOURCE_CLASS, SOURCE_METHOD, PARAM_1); + logger.throwing(SOURCE_CLASS, SOURCE_METHOD, new RuntimeException()); + messages = app.getMessages(); + assertEquals("Incorrect number of messages.", 12, messages.size()); + for (int i = 0; i < messages.size(); i++) { + String message = messages.get(i); + assertEquals("Incorrect caller class name for message " + i, SOURCE_METHOD, message); + } + } +} diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/JavaLevelTranslatorTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java similarity index 96% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/JavaLevelTranslatorTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java index d000d258dc1..76744a3959d 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/JavaLevelTranslatorTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JavaLevelTranslatorTest.java @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collection; +import org.apache.logging.jul.tolog4j.LevelTranslator; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.jul.LevelTranslator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/JulTestProperties.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java similarity index 95% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/JulTestProperties.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java index 4c5e8a0b618..28c4b2f6122 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/JulTestProperties.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/JulTestProperties.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test; public final class JulTestProperties { diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jLevelTranslatorTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java similarity index 95% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jLevelTranslatorTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java index 2d2d8c2ca30..43b6796a2a0 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jLevelTranslatorTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/Log4jLevelTranslatorTest.java @@ -14,14 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test; import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collection; +import org.apache.logging.jul.tolog4j.LevelTranslator; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.jul.LevelTranslator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; diff --git a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java new file mode 100644 index 00000000000..ed94daa6e47 --- /dev/null +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/ResourceBundleTest.java @@ -0,0 +1,91 @@ +/* + * 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.jul.tolog4j.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test methods that accept a resource bundle + */ +public class ResourceBundleTest { + + private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("ResourceBundleTest"); + private static final String SOURCE_CLASS = "SourceClass"; + private static final String SOURCE_METHOD = "sourceMethod"; + + private static final String[] EXPECTED_MESSAGES = {"Hello!", "Hello Log4j and JUL!"}; + private static final String[] EXPECTED_CLASS_NAMES = {ResourceBundleTest.class.getName(), SOURCE_CLASS}; + private static final String[] EXPECTED_METHOD_NAMES = {"testCorrectMessageAndLocation", SOURCE_METHOD}; + + @Rule + public final LoggerContextRule ctx = new LoggerContextRule("ResourceBundleTest.xml"); + + @BeforeClass + public static void setUpClass() { + System.setProperty("java.util.logging.manager", LogManager.class.getName()); + } + + @AfterClass + public static void tearDownClass() { + System.clearProperty("java.util.logging.manager"); + } + + @Test + public void testCorrectMessageAndLocation() { + ListAppender appender = ctx.getListAppender("LIST").clear(); + Logger logger = Logger.getLogger(ResourceBundleTest.class.getName()); + + Throwable thrown = new RuntimeException(); + logger.logrb(Level.INFO, BUNDLE, "msg_1", thrown); + logger.logrb(Level.INFO, BUNDLE, "msg_2", "Log4j", "JUL"); + logger.logrb(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, BUNDLE, "msg_1", thrown); + logger.logrb(Level.INFO, SOURCE_CLASS, SOURCE_METHOD, BUNDLE, "msg_2", "Log4j", "JUL"); + + LogEvent[] logEvents = appender.getEvents().toArray(LogEvent[]::new); + for (int idx = 0; idx < logEvents.length; ++idx) { + assertEquals( + String.format("Message of event %d", idx), + EXPECTED_MESSAGES[idx % 2], + logEvents[idx].getMessage().getFormattedMessage()); + assertEquals( + String.format("Source class of event %d", idx), + EXPECTED_CLASS_NAMES[idx / 2], + logEvents[idx].getSource().getClassName()); + assertEquals( + String.format("Source method of event %d", idx), + EXPECTED_METHOD_NAMES[idx / 2], + logEvents[idx].getSource().getMethodName()); + assertSame( + String.format("Exception of event %d", idx), + idx % 2 == 0 ? thrown : null, + logEvents[idx].getThrown()); + } + } +} diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/ApiLoggerTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java similarity index 72% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/ApiLoggerTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java index ce58daf2028..b7da8115fc0 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/ApiLoggerTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/ApiLoggerTest.java @@ -14,19 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test.internal; -import static org.apache.logging.log4j.jul.test.JulTestProperties.JUL_LOGGER_ADAPTER; -import static org.hamcrest.Matchers.equalTo; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; import java.util.logging.Logger; -import org.apache.logging.log4j.core.test.TestConstants; +import org.apache.logging.jul.tolog4j.LogManager; +import org.apache.logging.jul.tolog4j.test.support.AbstractLoggerTest; import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.apache.logging.log4j.jul.ApiLoggerAdapter; -import org.apache.logging.log4j.jul.LogManager; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -35,25 +32,26 @@ public class ApiLoggerTest extends AbstractLoggerTest { - private static String oldAdapter; - @BeforeClass public static void setUpClass() { System.setProperty("java.util.logging.manager", LogManager.class.getName()); - oldAdapter = TestConstants.setSystemProperty(JUL_LOGGER_ADAPTER, ApiLoggerAdapter.class.getName()); } @AfterClass public static void tearDownClass() { System.clearProperty("java.util.logging.manager"); - TestConstants.setSystemProperty(JUL_LOGGER_ADAPTER, oldAdapter); } @Before - public void setUp() throws Exception { + public void setUp() { logger = Logger.getLogger(LOGGER_NAME); logger.setFilter(null); - assertThat(logger.getLevel(), equalTo(java.util.logging.Level.FINE)); + assertThat(logger.isLoggable(java.util.logging.Level.FINE)) + .as("Level %s is enabled", java.util.logging.Level.FINE) + .isTrue(); + assertThat(logger.isLoggable(java.util.logging.Level.FINER)) + .as("Level %s is enabled", java.util.logging.Level.FINER) + .isFalse(); eventAppender = ListAppender.getListAppender("TestAppender"); flowAppender = ListAppender.getListAppender("FlowAppender"); stringAppender = ListAppender.getListAppender("StringAppender"); @@ -63,7 +61,7 @@ public void setUp() throws Exception { } @After - public void tearDown() throws Exception { + public void tearDown() { if (eventAppender != null) { eventAppender.clear(); } @@ -76,18 +74,18 @@ public void tearDown() throws Exception { } @Test - public void testGetParent() throws Exception { + public void testGetParent() { final Logger parent = logger.getParent(); assertNull("No parent logger should be automatically set up using log4j-api", parent); } @Test(expected = UnsupportedOperationException.class) - public void testSetParentFails() throws Exception { + public void testSetParentFails() { logger.setParent(null); } @Test - public void testSetLevelFails() throws Exception { + public void testSetLevelFails() { logger.setLevel(null); } } diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/DefaultLevelConverterCustomJulLevelsTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java similarity index 96% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/DefaultLevelConverterCustomJulLevelsTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java index 14b6416eb70..825b476c489 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/DefaultLevelConverterCustomJulLevelsTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterCustomJulLevelsTest.java @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test.internal; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.jul.DefaultLevelConverter; -import org.apache.logging.log4j.jul.LevelTranslator; import org.junit.Assert; import org.junit.Test; @@ -31,8 +31,6 @@ public class DefaultLevelConverterCustomJulLevelsTest { static class CustomLevel extends java.util.logging.Level { - private static final long serialVersionUID = 1L; - static CustomLevel ALL_P_1 = new CustomLevel("ALL_P_1", java.util.logging.Level.ALL.intValue() + 1); static CustomLevel FINEST_P_1 = new CustomLevel("FINEST_P_1", java.util.logging.Level.FINEST.intValue() + 1); diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/DefaultLevelConverterTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java similarity index 81% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/DefaultLevelConverterTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java index dc7d334fa6b..f93a4f4ca54 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/DefaultLevelConverterTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/internal/DefaultLevelConverterTest.java @@ -14,10 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test.internal; -import org.apache.logging.log4j.jul.DefaultLevelConverter; -import org.junit.Assert; +import static org.junit.Assert.assertNull; + +import org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter; import org.junit.Test; public class DefaultLevelConverterTest { @@ -27,6 +28,6 @@ public class DefaultLevelConverterTest { */ @Test public void testJulSetNull() { - Assert.assertEquals(null, new DefaultLevelConverter().toLevel(null)); + assertNull(new DefaultLevelConverter().toLevel(null)); } } diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java similarity index 76% rename from log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java rename to jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java index 303ff62db33..9e620cdffc9 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/AbstractLoggerTest.java @@ -14,18 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.logging.log4j.jul.test; +package org.apache.logging.jul.tolog4j.test.support; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.jul.tolog4j.support.AbstractLogger; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.impl.MementoLogEvent; import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.apache.logging.log4j.jul.ApiLogger; -import org.apache.logging.log4j.jul.LevelTranslator; import org.junit.Test; /** @@ -39,12 +39,12 @@ public abstract class AbstractLoggerTest { protected ListAppender stringAppender; @Test - public void testGetName() throws Exception { + public void testGetName() { assertThat(logger.getName()).isEqualTo(LOGGER_NAME); } @Test - public void testGlobalLogger() throws Exception { + public void testGlobalLogger() { final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); root.info("Test info message"); root.config("Test info message"); @@ -58,18 +58,18 @@ public void testGlobalLogger() throws Exception { } @Test - public void testGlobalLoggerName() throws Exception { + public void testGlobalLoggerName() { final Logger root = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); assertThat(root.getName()).isEqualTo(Logger.GLOBAL_LOGGER_NAME); } @Test - public void testIsLoggable() throws Exception { + public void testIsLoggable() { assertThat(logger.isLoggable(java.util.logging.Level.SEVERE)).isTrue(); } @Test - public void testLog() throws Exception { + public void testLog() { logger.info("Informative message here."); final List events = eventAppender.getEvents(); assertThat(events).hasSize(1); @@ -78,38 +78,7 @@ public void testLog() throws Exception { assertThat(event.getLevel()).isEqualTo(Level.INFO); assertThat(event.getLoggerName()).isEqualTo(LOGGER_NAME); assertThat(event.getMessage().getFormattedMessage()).isEqualTo("Informative message here."); - assertThat(event.getLoggerFqcn()).isEqualTo(ApiLogger.class.getName()); - } - - @Test - public void testLogFilter() throws Exception { - logger.setFilter(record -> false); - logger.severe("Informative message here."); - logger.warning("Informative message here."); - logger.info("Informative message here."); - logger.config("Informative message here."); - logger.fine("Informative message here."); - logger.finer("Informative message here."); - logger.finest("Informative message here."); - final List events = eventAppender.getEvents(); - assertThat(events).isEmpty(); - } - - @Test - public void testAlteringLogFilter() throws Exception { - logger.setFilter(record -> { - record.setMessage("This is not the message you are looking for."); - return true; - }); - logger.info("Informative message here."); - final List events = eventAppender.getEvents(); - assertThat(events).hasSize(1); - final LogEvent event = events.get(0); - assertThat(event).isInstanceOf(MementoLogEvent.class); - assertThat(event.getLevel()).isEqualTo(Level.INFO); - assertThat(event.getLoggerName()).isEqualTo(LOGGER_NAME); - assertThat(event.getMessage().getFormattedMessage()).isEqualTo("This is not the message you are looking for."); - assertThat(event.getLoggerFqcn()).isEqualTo(ApiLogger.class.getName()); + assertThat(event.getLoggerFqcn()).isEqualTo(AbstractLogger.class.getName()); } @Test @@ -121,7 +90,7 @@ public void testLogParamMarkers() { } @Test - public void testLogUsingCustomLevel() throws Exception { + public void testLogUsingCustomLevel() { logger.config("Config level"); final List events = eventAppender.getEvents(); assertThat(events).hasSize(1); @@ -130,7 +99,7 @@ public void testLogUsingCustomLevel() throws Exception { } @Test - public void testLogWithCallingClass() throws Exception { + public void testLogWithCallingClass() { final Logger log = Logger.getLogger("Test.CallerClass"); log.config("Calling from LoggerTest"); final List messages = stringAppender.getMessages(); diff --git a/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java new file mode 100644 index 00000000000..83d90f325dd --- /dev/null +++ b/jul-to-log4j/src/test/java/org/apache/logging/jul/tolog4j/test/support/CustomLoggerAdapterTest.java @@ -0,0 +1,95 @@ +/* + * 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.jul.tolog4j.test.support; + +import static org.junit.Assert.assertTrue; + +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LogManager; +import org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter; +import org.apache.logging.jul.tolog4j.support.AbstractLogger; +import org.apache.logging.jul.tolog4j.test.JulTestProperties; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.status.StatusLogger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests if the logger adapter can be customized. + */ +public class CustomLoggerAdapterTest { + + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + @BeforeClass + public static void setUpClass() { + System.setProperty("java.util.logging.manager", LogManager.class.getName()); + System.setProperty(JulTestProperties.JUL_LOGGER_ADAPTER, CustomLoggerAdapter.class.getName()); + } + + @AfterClass + public static void tearDownClass() { + System.clearProperty("java.util.logging.manager"); + System.clearProperty(JulTestProperties.JUL_LOGGER_ADAPTER); + } + + @Test + public void testCustomLoggerAdapter() { + Logger logger = Logger.getLogger(CustomLoggerAdapterTest.class.getName()); + assertTrue("CustomLoggerAdapter is used", logger instanceof CustomLogger); + } + + public static class CustomLoggerAdapter extends AbstractLoggerAdapter { + + @Override + public Logger newLogger(String name, LoggerContext context) { + return new CustomLogger(context.getLogger(name)); + } + } + + private static class CustomLogger extends AbstractLogger { + + CustomLogger(ExtendedLogger logger) { + super(logger); + } + + @Override + public void setFilter(Filter newFilter) {} + + @Override + public void setLevel(final Level newLevel) throws SecurityException { + LOGGER.error("Cannot set JUL log level through Log4j API: ignoring call to Logger.setLevel({})", newLevel); + } + + @Override + public void addHandler(Handler handler) {} + + @Override + public void removeHandler(Handler handler) {} + + @Override + public void setUseParentHandlers(boolean useParentHandlers) {} + + @Override + public void setParent(Logger parent) {} + } +} diff --git a/jul-to-log4j/src/test/resources/CallerInformationTest.xml b/jul-to-log4j/src/test/resources/CallerInformationTest.xml new file mode 100644 index 00000000000..087587ea5df --- /dev/null +++ b/jul-to-log4j/src/test/resources/CallerInformationTest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/jul-to-log4j/src/test/resources/ResourceBundleTest.properties b/jul-to-log4j/src/test/resources/ResourceBundleTest.properties new file mode 100644 index 00000000000..3563a119784 --- /dev/null +++ b/jul-to-log4j/src/test/resources/ResourceBundleTest.properties @@ -0,0 +1,21 @@ +# +# 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. +# +## +# Resource bundle used in ResourceBundleTest + +msg_1 = Hello! +msg_2 = Hello %s and %s! diff --git a/log4j-jul/src/test/resources/log4j2-calling-class.xml b/jul-to-log4j/src/test/resources/ResourceBundleTest.xml similarity index 68% rename from log4j-jul/src/test/resources/log4j2-calling-class.xml rename to jul-to-log4j/src/test/resources/ResourceBundleTest.xml index 1af22eaa82c..3d2d891500e 100644 --- a/log4j-jul/src/test/resources/log4j2-calling-class.xml +++ b/jul-to-log4j/src/test/resources/ResourceBundleTest.xml @@ -15,22 +15,17 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + - - - - - - + - - - - - - - + + + diff --git a/jul-to-log4j/src/test/resources/log4j2-test.xml b/jul-to-log4j/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000000..9c5bdb23ab9 --- /dev/null +++ b/jul-to-log4j/src/test/resources/log4j2-test.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/log4j-jul/pom.xml b/log4j-jul/pom.xml index 5fdabdc7175..fc598b85775 100644 --- a/log4j-jul/pom.xml +++ b/log4j-jul/pom.xml @@ -25,91 +25,46 @@ log4j-jul - Apache Log4j JUL Adapter - The Apache Log4j implementation of java.util.logging - - - - - - org.apache.logging.log4j.core.*;resolution:=optional - - - - org.apache.logging.log4j.core;transitive=false - - - - false - + Apache Log4j JUL Handler + A `java.util.logging` Handler that forwards events to the Log4j API. + org.apache.logging.log4j log4j-api + + + org.apache.logging.log4j + jul-to-log4j + + org.apache.logging.log4j log4j-core - true + org.assertj assertj-core test - - - com.lmax - disruptor - test - - - org.hamcrest - hamcrest - test - - - junit - junit - test - + - org.apache.logging.log4j - log4j-async-logger - test - - - org.apache.logging.log4j - log4j-core-test + org.junit.jupiter + junit-jupiter-api test + + org.apache.maven.plugins maven-surefire-plugin - - - true - - -Xms256m -Xmx1024m - 1 - false - - - - - - org.apache.maven.surefire - surefire-junit47 - ${surefire.version} - - default-test @@ -121,27 +76,14 @@ Log4jBridgeHandlerTest.java - - - - - bridgeHandler-test - - test - - test - - - Log4jBridgeHandlerTest.java - - src/test/resources/logging-test.properties - classpath:log4j2-julBridge-test.xml + ${project.basedir}/src/test/resources/logging-test.properties + diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java deleted file mode 100644 index 7c33636a364..00000000000 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/AbstractLoggerAdapter.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul; - -import java.util.logging.Logger; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.spi.LoggerContext; -import org.apache.logging.log4j.util.StackLocatorUtil; - -/** - * Abstract Logger registry. Due to the optionality of using log4j-core, there are two registries available at runtime - * to create: {@link ApiLoggerAdapter} and {@link CoreLoggerAdapter}. - * - * @since 2.1 - */ -public abstract class AbstractLoggerAdapter extends org.apache.logging.log4j.spi.AbstractLoggerAdapter { - - @Override - protected LoggerContext getContext() { - return getContext( - LogManager.getFactory().isClassLoaderDependent() - ? StackLocatorUtil.getCallerClass(java.util.logging.LogManager.class) - : null); - } -} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java deleted file mode 100644 index eee946fc8ce..00000000000 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul; - -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.logging.Filter; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import java.util.logging.Logger; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.spi.ExtendedLogger; -import org.apache.logging.log4j.status.StatusLogger; - -/** - * Log4j API implementation of the JUL {@link Logger} class. Note that this implementation does - * not use the {@link java.util.logging.Handler} class. Instead, logging is delegated to the - * underlying Log4j {@link org.apache.logging.log4j.Logger} which may be implemented in one of many different ways. - * Consult the documentation for your Log4j Provider for more details. - *

Note that the methods {@link #getParent()} and {@link #setLevel(java.util.logging.Level)} are not supported by - * this implementation. If you need support for these methods, then you'll need to use log4j-core. The - * {@link #getParent()} method will not fail (thanks to JUL API limitations), but it won't necessarily be - * accurate!

- *

Also note that {@link #setParent(java.util.logging.Logger)} is explicitly unsupported. Parent loggers are - * determined using the syntax of the logger name; not through an arbitrary graph of loggers.

- * - * @since 2.1 - */ -public class ApiLogger extends Logger { - - private final WrappedLogger logger; - private static final String FQCN = ApiLogger.class.getName(); - - ApiLogger(final ExtendedLogger logger) { - super(logger.getName(), null); - final Level javaLevel = LevelTranslator.toJavaLevel(logger.getLevel()); - // "java.util.logging.LoggingPermission" "control" - AccessController.doPrivileged((PrivilegedAction) () -> { - ApiLogger.super.setLevel(javaLevel); - return null; - }); - this.logger = new WrappedLogger(logger); - } - - @Override - public void log(final LogRecord record) { - if (isFiltered(record)) { - return; - } - final org.apache.logging.log4j.Level level = LevelTranslator.toLevel(record.getLevel()); - final Object[] parameters = record.getParameters(); - final MessageFactory messageFactory = logger.getMessageFactory(); - final Message message = parameters == null - ? messageFactory.newMessage(record.getMessage()) /* LOG4J2-1251: not formatted case */ - : messageFactory.newMessage(record.getMessage(), parameters); - final Throwable thrown = record.getThrown(); - logger.logIfEnabled(FQCN, level, null, message, thrown); - } - - // support for Logger.getFilter()/Logger.setFilter() - boolean isFiltered(final LogRecord logRecord) { - final Filter filter = getFilter(); - return filter != null && !filter.isLoggable(logRecord); - } - - @Override - public boolean isLoggable(final Level level) { - return logger.isEnabled(LevelTranslator.toLevel(level)); - } - - @Override - public String getName() { - return logger.getName(); - } - - @Override - public void setLevel(final Level newLevel) throws SecurityException { - StatusLogger.getLogger() - .error( - "Cannot set JUL log level through log4j-api: " + "ignoring call to Logger.setLevel({})", - newLevel); - } - - /** - * Provides access to {@link Logger#setLevel(java.util.logging.Level)}. This method should only be used by child - * classes. - * - * @see Logger#setLevel(java.util.logging.Level) - */ - protected void doSetLevel(final Level newLevel) throws SecurityException { - super.setLevel(newLevel); - } - - /** - * Unsupported operation. - * - * @throws UnsupportedOperationException always - */ - @Override - public void setParent(final Logger parent) { - throw new UnsupportedOperationException("Cannot set parent logger"); - } - - @Override - public void log(final Level level, final String msg) { - if (getFilter() == null) { - logger.log(LevelTranslator.toLevel(level), msg); - } else { - super.log(level, msg); - } - } - - @Override - public void log(final Level level, final String msg, final Object param1) { - if (getFilter() == null) { - logger.log(LevelTranslator.toLevel(level), msg, param1); - } else { - super.log(level, msg, param1); - } - } - - @Override - public void log(final Level level, final String msg, final Object[] params) { - if (getFilter() == null) { - logger.log(LevelTranslator.toLevel(level), msg, params); - } else { - super.log(level, msg, params); - } - } - - @Override - public void log(final Level level, final String msg, final Throwable thrown) { - if (getFilter() == null) { - logger.log(LevelTranslator.toLevel(level), msg, thrown); - } else { - super.log(level, msg, thrown); - } - } - - @Override - public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg) { - log(level, msg); - } - - @Override - public void logp( - final Level level, - final String sourceClass, - final String sourceMethod, - final String msg, - final Object param1) { - log(level, msg, param1); - } - - @Override - public void logp( - final Level level, - final String sourceClass, - final String sourceMethod, - final String msg, - final Object[] params) { - log(level, msg, params); - } - - @Override - public void logp( - final Level level, - final String sourceClass, - final String sourceMethod, - final String msg, - final Throwable thrown) { - log(level, msg, thrown); - } - - @Override - public void logrb( - final Level level, - final String sourceClass, - final String sourceMethod, - final String bundleName, - final String msg) { - log(level, msg); - } - - @Override - public void logrb( - final Level level, - final String sourceClass, - final String sourceMethod, - final String bundleName, - final String msg, - final Object param1) { - log(level, msg, param1); - } - - @Override - public void logrb( - final Level level, - final String sourceClass, - final String sourceMethod, - final String bundleName, - final String msg, - final Object[] params) { - log(level, msg, params); - } - - @Override - public void logrb( - final Level level, - final String sourceClass, - final String sourceMethod, - final String bundleName, - final String msg, - final Throwable thrown) { - log(level, msg, thrown); - } - - @Override - public void entering(final String sourceClass, final String sourceMethod) { - logger.traceEntry(); - } - - @Override - public void entering(final String sourceClass, final String sourceMethod, final Object param1) { - logger.traceEntry(null, param1); - } - - @Override - public void entering(final String sourceClass, final String sourceMethod, final Object[] params) { - logger.traceEntry(null, params); - } - - @Override - public void exiting(final String sourceClass, final String sourceMethod) { - logger.traceExit(); - } - - @Override - public void exiting(final String sourceClass, final String sourceMethod, final Object result) { - logger.traceExit(result); - } - - @Override - public void throwing(final String sourceClass, final String sourceMethod, final Throwable thrown) { - logger.throwing(thrown); - } - - @Override - public void severe(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, msg); - } else { - super.severe(msg); - } - } - - @Override - public void warning(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, msg); - } else { - super.warning(msg); - } - } - - @Override - public void info(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, msg); - } else { - super.info(msg); - } - } - - @Override - public void config(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg); - } else { - super.config(msg); - } - } - - @Override - public void fine(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, msg); - } else { - super.fine(msg); - } - } - - @Override - public void finer(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, msg); - } else { - super.finer(msg); - } - } - - @Override - public void finest(final String msg) { - if (getFilter() == null) { - logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg); - } else { - super.finest(msg); - } - } -} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLogger.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLogger.java deleted file mode 100644 index ebc06985a8b..00000000000 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLogger.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul; - -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Log4j Core implementation of the JUL {@link Logger} class. Note that this implementation does - * not use the {@link java.util.logging.Handler} class. Instead, logging is delegated to the - * underlying Log4j {@link org.apache.logging.log4j.core.Logger} which uses - * {@link org.apache.logging.log4j.core.Appender Appenders} instead. - * - * @since 2.1 - */ -public class CoreLogger extends ApiLogger { - - private final org.apache.logging.log4j.core.Logger logger; - - /** - * Constructs a Logger using a Log4j {@link org.apache.logging.log4j.core.Logger}. - * - * @param logger the underlying Logger to base this Logger on - */ - CoreLogger(final org.apache.logging.log4j.core.Logger logger) { - super(logger); - this.logger = logger; - } - - @Override - public void setLevel(final Level level) throws SecurityException { - super.doSetLevel(level); // checks permissions - logger.setLevel(LevelTranslator.toLevel(level)); - } - - /** - * Marks the underlying {@link org.apache.logging.log4j.core.Logger} as additive. - * - * @param additive {@code true} if this Logger should be additive - * @see org.apache.logging.log4j.core.Logger#setAdditive(boolean) - */ - @Override - public synchronized void setUseParentHandlers(final boolean additive) { - logger.setAdditive(additive); - } - - /** - * Indicates if the underlying {@link org.apache.logging.log4j.core.Logger} is additive. Note that the - * Log4j version of JDK Loggers do not use Handlers. - * - * @return {@code true} if this Logger is additive, or {@code false} otherwise - * @see org.apache.logging.log4j.core.Logger#isAdditive() - */ - @Override - public synchronized boolean getUseParentHandlers() { - return logger.isAdditive(); - } - - @Override - public Logger getParent() { - final org.apache.logging.log4j.core.Logger parent = logger.getParent(); - return parent == null ? null : Logger.getLogger(parent.getName()); - } -} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLoggerAdapter.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLoggerAdapter.java deleted file mode 100644 index 4ca968f1976..00000000000 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/CoreLoggerAdapter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul; - -import java.util.logging.Logger; -import org.apache.logging.log4j.message.MessageFactory; -import org.apache.logging.log4j.message.MessageFormatMessageFactory; -import org.apache.logging.log4j.spi.LoggerContext; - -/** - * {@link Logger} registry implementation that uses log4j-core. - * - * @since 2.1 - */ -public class CoreLoggerAdapter extends AbstractLoggerAdapter { - - private static final MessageFactory MESSAGE_FACTORY = new MessageFormatMessageFactory(); - - @Override - protected Logger newLogger(final String name, final LoggerContext context) { - final org.apache.logging.log4j.spi.ExtendedLogger original = context.getLogger(name, MESSAGE_FACTORY); - if (original instanceof org.apache.logging.log4j.core.Logger) { - return new CoreLogger((org.apache.logging.log4j.core.Logger) original); - } - return new ApiLogger(original); // LOG4J2-1618 during shutdown, a SimpleLogger may be returned - } -} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java index d5c44ecdc79..7eca5cd12b7 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java +++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java @@ -17,89 +17,77 @@ package org.apache.logging.log4j.jul; // note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j + +import aQute.bnd.annotation.Cardinality; +import aQute.bnd.annotation.spi.ServiceConsumer; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; +import java.util.ServiceLoader; import java.util.logging.LogRecord; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.log4j.jul.internal.JulLevelPropagator; +import org.apache.logging.log4j.jul.spi.LevelChangePropagator; import org.apache.logging.log4j.spi.ExtendedLogger; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.ServiceLoaderUtil; /** - * Bridge from JUL to log4j2.
- * This is an alternative to log4j.jul.LogManager (running as complete JUL replacement), - * especially useful for webapps running on a container for which the LogManager cannot or - * should not be used.

+ * Bridge from JUL to Log4j API + *

+ * This is an alternative to {@link org.apache.logging.jul.tolog4j.LogManager} (running as complete JUL replacement), + * especially useful for webapps running on a container for which the LogManager cannot or should not be used. + *

* - * Installation/usage:
    - *
  • Declaratively inside JUL's logging.properties:
    - * handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler
    - * (and typically also:   org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true )
    - * Note: in a webapp running on Tomcat, you may create a WEB-INF/classes/logging.properties - * file to configure JUL for this webapp only: configured handlers and log levels affect your webapp only! - * This file is then the complete JUL configuration, so JUL's defaults (e.g. log level INFO) apply - * for stuff not explicitly defined therein. - *
  • Programmatically by calling install() method, - * e.g. inside ServletContextListener static-class-init. or contextInitialized() - *
- * Configuration (in JUL's logging.properties):
    - *
  • Log4jBridgeHandler.suffixToAppend
    - * String, suffix to append to JUL logger names, to easily recognize bridged log messages. - * A dot "." is automatically prepended, so configuration for the basis logger is used
    - * Example: Log4jBridgeHandler.suffixToAppend = _JUL
    - * Useful, for example, if you use JSF because it logs exceptions and throws them afterwards; - * you can easily recognize the duplicates with this (or concentrate on the non-JUL-logs). - *
  • Log4jBridgeHandler.propagateLevels   boolean, "true" to automatically propagate log4j log levels to JUL. - *
  • Log4jBridgeHandler.sysoutDebug   boolean, perform some (developer) debug output to sysout + *

    + * Installation/usage: + *

    + *
      + *
    • + *

      Declaratively inside JUL's {@code logging.properties}):

      + *
      + *     handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler
      + *     # Enable log level propagation
      + *     # This requires `log4j-jul-propagator` to be present
      + *     org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true
      + *         
      + *

      Note: if your application is running on Tomcat and has a + * {@code WEB-INF/classes/logging.properties} class, the configured handlers and log level changes will + * only affect you web application.

      + *
    • + *
    • + * Programmatically by calling {@link #install(boolean, String, boolean)}, e.g., inside a + * ServletContextListener. + *
    • *
    * - * Log levels are translated with {@link LevelTranslator}, see also - * log4j doc.

    - * - * Restrictions:
      - *
    • Manually given source/location info in JUL (e.g. entering(), exiting(), throwing(), logp(), logrb() ) - * will NOT be considered, i.e. gets lost in log4j logging. - *
    • Log levels of JUL have to be adjusted according to log4j log levels: - * Either by using "propagateLevels" (preferred), or manually by specifying them explicitly, - * i.e. logging.properties and log4j2.xml have some redundancies. - *
    • Only JUL log events that are allowed according to the JUL log level get to this handler and thus to log4j. - * This is only relevant and important if you NOT use "propagateLevels". - * If you set .level = SEVERE only error logs will be seen by this handler and thus log4j - * - even if the corresponding log4j log level is ALL.
      - * On the other side, you should NOT set .level = FINER or FINEST if the log4j level is higher. - * In this case a lot of JUL log events would be generated, sent via this bridge to log4j and thrown away by the latter.
      - * Note: JUL's default log level (i.e. none specified in logger.properties) is INFO. - *
    - * - * (Credits: idea and concept originate from org.slf4j.bridge.SLF4JBridgeHandler; - * level propagation idea originates from logback/LevelChangePropagator; - * but no source code has been copied) - * + *

    + * Credits: the idea and concept originate from {@code org.slf4j.bridge.SLF4JBridgeHandler}. + * The level propagation idea originates from {@code ch.qos.logback.classic.jul.LevelChangePropagator}. + * No source code has been copied. + *

    + * @see Log4j documentation site * @since 2.15.0 */ -public class Log4jBridgeHandler extends java.util.logging.Handler implements Consumer { - private static final org.apache.logging.log4j.Logger SLOGGER = StatusLogger.getLogger(); +@ServiceConsumer(value = LevelChangePropagator.class, cardinality = Cardinality.SINGLE) +public class Log4jBridgeHandler extends java.util.logging.Handler { + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); // the caller of the logging is java.util.logging.Logger (for location info) private static final String FQCN = java.util.logging.Logger.class.getName(); private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger"; private static final java.util.logging.Formatter julFormatter = new java.util.logging.SimpleFormatter(); - private boolean doDebugOutput = false; private String julSuffixToAppend = null; + private LevelChangePropagator levelPropagator; private volatile boolean installAsLevelPropagator = false; /** * Adds a new Log4jBridgeHandler instance to JUL's root logger. - * This is a programmatic alternative to specify - * handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler - * and its configuration in logging.properties.
    - * @param removeHandlersForRootLogger true to remove all other installed handlers on JUL root level + *

    + * This is a programmatic alternative for a {@code logging.properties} file. + *

    + * @param removeHandlersForRootLogger If {@code true}, remove all other installed handlers on JUL root level + * @param suffixToAppend The suffix to append to each JUL logger + * @param propagateLevels If {@code true}, the logging implementation levels will propagate to JUL. */ public static void install(boolean removeHandlersForRootLogger, String suffixToAppend, boolean propagateLevels) { final java.util.logging.Logger rootLogger = getJulRootLogger(); @@ -108,8 +96,7 @@ public static void install(boolean removeHandlersForRootLogger, String suffixToA rootLogger.removeHandler(hdl); } } - rootLogger.addHandler(new Log4jBridgeHandler(false, suffixToAppend, propagateLevels)); - // note: filter-level of Handler defaults to ALL, so nothing to do here + rootLogger.addHandler(new Log4jBridgeHandler(suffixToAppend, propagateLevels)); } private static java.util.logging.Logger getJulRootLogger() { @@ -120,28 +107,21 @@ private static java.util.logging.Logger getJulRootLogger() { public Log4jBridgeHandler() { final java.util.logging.LogManager julLogMgr = java.util.logging.LogManager.getLogManager(); final String className = this.getClass().getName(); - init( - Boolean.parseBoolean(julLogMgr.getProperty(className + ".sysoutDebug")), + configure( julLogMgr.getProperty(className + ".appendSuffix"), Boolean.parseBoolean(julLogMgr.getProperty(className + ".propagateLevels"))); } /** Initialize this handler with given configuration. */ - public Log4jBridgeHandler(boolean debugOutput, String suffixToAppend, boolean propagateLevels) { - init(debugOutput, suffixToAppend, propagateLevels); + private Log4jBridgeHandler(String suffixToAppend, boolean propagateLevels) { + configure(suffixToAppend, propagateLevels); } /** Perform init. of this handler with given configuration (typical use is for constructor). */ @SuppressFBWarnings( value = "INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE", justification = "The data is available only in debug mode.") - protected void init(boolean debugOutput, String suffixToAppend, boolean propagateLevels) { - this.doDebugOutput = debugOutput; - if (debugOutput) { - new Exception("DIAGNOSTIC ONLY (sysout): Log4jBridgeHandler instance created (" + this + ")") - .printStackTrace(System.out); // is no error thus no syserr - } - + private void configure(String suffixToAppend, boolean propagateLevels) { if (suffixToAppend != null) { suffixToAppend = suffixToAppend.trim(); // remove spaces if (suffixToAppend.isEmpty()) { @@ -152,9 +132,18 @@ protected void init(boolean debugOutput, String suffixToAppend, boolean propagat } this.julSuffixToAppend = suffixToAppend; - this.installAsLevelPropagator = propagateLevels; + installAsLevelPropagator = propagateLevels; + if (installAsLevelPropagator) { + levelPropagator = ServiceLoaderUtil.safeStream( + LevelChangePropagator.class, + ServiceLoader.load( + LevelChangePropagator.class, getClass().getClassLoader()), + LOGGER) + .findAny() + .orElse(JulLevelPropagator.INSTANCE); + } - SLOGGER.debug( + LOGGER.debug( "Log4jBridgeHandler init. with: suffix='{}', lvlProp={}, instance={}", suffixToAppend, propagateLevels, @@ -163,11 +152,8 @@ protected void init(boolean debugOutput, String suffixToAppend, boolean propagat @Override public void close() { - // cleanup and remove listener and JUL logger references - julLoggerRefs = null; - LoggerContext.getContext(false).removeConfigurationStartedListener(this); - if (doDebugOutput) { - System.out.println("sysout: Log4jBridgeHandler close(): " + this); + if (levelPropagator != null) { + levelPropagator.stop(); } } @@ -178,29 +164,18 @@ public void publish(final LogRecord record) { } // Only execute synchronized code if we really have to - if (this.installAsLevelPropagator) { + if (installAsLevelPropagator) { synchronized (this) { - // Check again to make sure we still have to propagate the levels at this point - if (this.installAsLevelPropagator) { - // no need to close the AutoCloseable ctx here - @SuppressWarnings("resource") - final LoggerContext context = LoggerContext.getContext(false); - context.addConfigurationStartedListener(this); - propagateLogLevels(context.getConfiguration()); - // note: java.util.logging.LogManager.addPropertyChangeListener() could also - // be set here, but a call of JUL.readConfiguration() will be done on purpose - this.installAsLevelPropagator = false; + // Check again to make sure we still have to propagate the levels at this point + if (installAsLevelPropagator) { + levelPropagator.start(); + installAsLevelPropagator = false; } } } final org.apache.logging.log4j.Logger log4jLogger = getLog4jLogger(record); - final String msg = julFormatter.formatMessage(record); // use JUL's implementation to get real msg - /* log4j allows nulls: - if (msg == null) { - // JUL allows nulls, but other log system may not - msg = ""; - } */ + final String msg = julFormatter.formatMessage(record); final org.apache.logging.log4j.Level log4jLevel = LevelTranslator.toLevel(record.getLevel()); final Throwable thrown = record.getThrown(); if (log4jLogger instanceof ExtendedLogger) { @@ -236,90 +211,4 @@ private org.apache.logging.log4j.Logger getLog4jLogger(final LogRecord record) { } return org.apache.logging.log4j.LogManager.getLogger(name); } - - ///// log level propagation code - - @Override - public void accept(final Configuration configuration) { - SLOGGER.debug("Log4jBridgeHandler.accept(): {}", configuration); - propagateLogLevels(configuration); - } - - /** Save "hard" references to configured JUL loggers. (is lazy init.) */ - private Set julLoggerRefs; - /** Perform developer tests? (Should be unused/outcommented for real code) */ - // private static final boolean DEVTEST = false; - - private void propagateLogLevels(final Configuration config) { - SLOGGER.debug("Log4jBridgeHandler.propagateLogLevels(): {}", config); - // clear or init. saved JUL logger references - // JUL loggers have to be explicitly referenced because JUL internally uses - // weak references so not instantiated loggers may be garbage collected - // and their level config gets lost then. - if (julLoggerRefs == null) { - julLoggerRefs = new HashSet<>(); - } else { - julLoggerRefs.clear(); - } - - // if (DEVTEST) debugPrintJulLoggers("Start of propagation"); - // walk through all log4j configured loggers and set JUL level accordingly - final Map log4jLoggers = config.getLoggers(); - // java.util.List outTxt = new java.util.ArrayList<>(); // DEVTEST / DEV-DEBUG ONLY - for (LoggerConfig lcfg : log4jLoggers.values()) { - final java.util.logging.Logger julLog = - java.util.logging.Logger.getLogger(lcfg.getName()); // this also fits for root = "" - final java.util.logging.Level julLevel = - LevelTranslator.toJavaLevel(lcfg.getLevel()); // lcfg.getLevel() never returns null - julLog.setLevel(julLevel); - julLoggerRefs.add(julLog); // save an explicit reference to prevent GC - // if (DEVTEST) outTxt.add("propagating '" + lcfg.getName() + "' / " + lcfg.getLevel() + " -> " + - // julLevel); - } // for - // if (DEVTEST) java.util.Collections.sort(outTxt, String.CASE_INSENSITIVE_ORDER); - // if (DEVTEST) for (String s : outTxt) System.out.println("+ " + s); - // if (DEVTEST) debugPrintJulLoggers("After propagation"); - - // cleanup JUL: reset all log levels not explicitly given by log4j - // This has to happen after propagation because JUL creates and inits. the loggers lazily - // so a nested logger might be created during the propagation-for-loop above and gets - // its JUL-configured level not until then. - final java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager(); - for (Enumeration en = julMgr.getLoggerNames(); en.hasMoreElements(); ) { - final java.util.logging.Logger julLog = julMgr.getLogger(en.nextElement()); - if (julLog != null - && julLog.getLevel() != null - && !"".equals(julLog.getName()) - && !log4jLoggers.containsKey(julLog.getName())) { - julLog.setLevel(null); - } - } // for - // if (DEVTEST) debugPrintJulLoggers("After JUL cleanup"); - } - - /* DEV-DEBUG ONLY (comment out for release) *xx/ - private void debugPrintJulLoggers(String infoStr) { - if (!DEVTEST) return; - java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager(); - System.out.println("sysout: " + infoStr + " - for " + julMgr); - java.util.List txt = new java.util.ArrayList<>(); - int n = 1; - for (Enumeration en = julMgr.getLoggerNames(); en.hasMoreElements(); ) { - String ln = en.nextElement(); - java.util.logging.Logger lg = julMgr.getLogger(ln); - if (lg == null) { - txt.add("(!null-Logger '" + ln + "') #" + n); - } else if (lg.getLevel() == null) { - txt.add("(null-Level Logger '" + ln + "') #" + n); - } else { - txt.add("Logger '" + ln + "', lvl = " + lg.getLevel() + " #" + n); - } - n++; - } // for - java.util.Collections.sort(txt, String.CASE_INSENSITIVE_ORDER); - for (String s : txt) { - System.out.println(" - " + s); - } - } /**/ - } diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java deleted file mode 100644 index 8c5d0356283..00000000000 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.message.EntryMessage; -import org.apache.logging.log4j.spi.ExtendedLogger; -import org.apache.logging.log4j.spi.ExtendedLoggerWrapper; - -/** - * Wrapper class to ensure proper FQCN support in Logger calls. - * - * @since 2.1 - */ -class WrappedLogger extends ExtendedLoggerWrapper { - - private static final long serialVersionUID = 1L; - private static final String FQCN = ApiLogger.class.getName(); - - WrappedLogger(final ExtendedLogger logger) { - super(logger, logger.getName(), logger.getMessageFactory()); - } - - @Override - public void log(final Level level, final String message, final Throwable t) { - logIfEnabled(FQCN, level, null, message, t); - } - - @Override - public void log(final Level level, final String message, final Object... params) { - logIfEnabled(FQCN, level, null, message, params); - } - - @Override - public void log(final Level level, final String message) { - logIfEnabled(FQCN, level, null, message); - } - - @Override - public EntryMessage traceEntry() { - return enter(FQCN, null, (Object[]) null); - } - - @Override - public EntryMessage traceEntry(final String message, final Object... params) { - return enter(FQCN, message, params); - } - - @Override - public T throwing(final T t) { - return throwing(FQCN, LevelTranslator.toLevel(java.util.logging.Level.FINER), t); - } -} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/internal/JulLevelPropagator.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/internal/JulLevelPropagator.java new file mode 100644 index 00000000000..53a933dfed5 --- /dev/null +++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/internal/JulLevelPropagator.java @@ -0,0 +1,100 @@ +/* + * 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.jul.internal; + +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.jul.spi.LevelChangePropagator; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Propagates Log4j Core level to JUL. + */ +public class JulLevelPropagator implements LevelChangePropagator, Consumer { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + public static final LevelChangePropagator INSTANCE = new JulLevelPropagator(); + + private final AtomicInteger installCount = new AtomicInteger(); + private LoggerContext context; + /** Save "hard" references to configured JUL loggers. */ + private final Set julLoggerRefs = new HashSet<>(); + + @Override + public void start() { + if (installCount.getAndIncrement() == 0) { + context = LoggerContext.getContext(false); + LOGGER.info("Installing Log4j Core to JUL level propagator for context `{}`", context); + context.addConfigurationStartedListener(this); + propagateLogLevels(context.getConfiguration()); + } + } + + @Override + public void stop() { + if (installCount.decrementAndGet() == 0) { + LOGGER.info("Uninstalling Log4j Core to JUL level propagator for context `{}`", context); + context.removeConfigurationStartedListener(this); + context = null; + julLoggerRefs.clear(); + } + } + + @Override + public void accept(Configuration configuration) { + propagateLogLevels(configuration); + } + + private void propagateLogLevels(final Configuration configuration) { + LOGGER.info("Starting Log4j Core to JUL level propagation for configuration `{}`", configuration); + // clear or init. saved JUL logger references + // JUL loggers have to be explicitly referenced because JUL internally uses + // weak references so not instantiated loggers may be garbage collected + // and their level config gets lost then. + julLoggerRefs.clear(); + + final Map log4jLoggers = configuration.getLoggers(); + for (LoggerConfig loggerConfig : log4jLoggers.values()) { + final java.util.logging.Logger julLog = + java.util.logging.Logger.getLogger(loggerConfig.getName()); // this also fits for root = "" + final java.util.logging.Level julLevel = + LevelTranslator.toJavaLevel(loggerConfig.getLevel()); // loggerConfig.getLevel() never returns null + julLog.setLevel(julLevel); + julLoggerRefs.add(julLog); + } + final java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager(); + for (Enumeration en = julMgr.getLoggerNames(); en.hasMoreElements(); ) { + final java.util.logging.Logger julLog = julMgr.getLogger(en.nextElement()); + if (julLog != null + && julLog.getLevel() != null + && !"".equals(julLog.getName()) + && !log4jLoggers.containsKey(julLog.getName())) { + julLog.setLevel(null); + } + } + } +} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java index dd227473066..dd534ee454f 100644 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java +++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/package-info.java @@ -15,7 +15,7 @@ * limitations under the license. */ @Export -@Version("2.20.2") +@Version("3.0.0") package org.apache.logging.log4j.jul; import org.osgi.annotation.bundle.Export; diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/spi/LevelChangePropagator.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/spi/LevelChangePropagator.java new file mode 100644 index 00000000000..f62fb16adc9 --- /dev/null +++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/spi/LevelChangePropagator.java @@ -0,0 +1,45 @@ +/* + * 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.jul.spi; + +import org.apache.logging.log4j.jul.Log4jBridgeHandler; + +/** + * Propagates level configuration from the Log4j API implementation used + *

    + * Using the {@link Log4jBridgeHandler} is expensive, since disabled log events must be formatted by JUL, + * before they can be dropped by the Log4j API implementation. + *

    + *

    + * This class introduces a mechanism that can be implemented by each Log4j API implementation to be notified, + * whenever a {@link Log4jBridgeHandler} is used. The logging implementation will be able to synchronize + * its levels with the levels of JUL loggers each time it is reconfigured. + *

    + * @since 3.0.0 + */ +public interface LevelChangePropagator { + + /** + * Start propagating log levels. + */ + void start(); + + /** + * Stop propagating log levels. + */ + void stop(); +} diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/spi/package-info.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/spi/package-info.java new file mode 100644 index 00000000000..a7ef55a76a0 --- /dev/null +++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/spi/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ +/** + * Contains interfaces an abstract classes to extend the functionality of Log4j JUL Adapter. + */ +@Export +@Version("3.0.0") +package org.apache.logging.log4j.jul.spi; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jBridgeHandlerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jBridgeHandlerTest.java new file mode 100644 index 00000000000..5965650dd57 --- /dev/null +++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jBridgeHandlerTest.java @@ -0,0 +1,269 @@ +/* + * 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.jul; + +// note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.Arrays; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Tests the Log4jBridgeHandler. + *

    + * Requires some configurations: + *

    + *
      + *
    • + * The {@code java.util.logging.config.file} system property must be set to the location of the + * {@code Log4jBridgeHandlerTest-jul.properties} resource. + *
    • + *
    • + * The Log4j Core configuration file must be set to the location of the + * {@code Log4jBridgeHandlerTest-log4j2.xml} resource. + *
    • + *
    + */ +class Log4jBridgeHandlerTest { + + /** This classes' simple name = relevant part of logger name. */ + private static final String CSNAME = Log4jBridgeHandlerTest.class.getSimpleName(); + // loggers used in many tests + private static final java.util.logging.Logger julLog = + java.util.logging.Logger.getLogger(Log4jBridgeHandlerTest.class.getName()); + private static final org.apache.logging.log4j.Logger log4jLog = org.apache.logging.log4j.LogManager.getLogger(); + + // capture sysout/syserr + // @Rule public final SystemErrRule systemOutRule = new SystemErrRule().enableLog(); + private static final ByteArrayOutputStream sysoutBytes = new ByteArrayOutputStream(1024); + private static PrintStream prevSysErrStream; + + @BeforeAll + static void beforeClass() { + prevSysErrStream = System.err; + System.setErr(new PrintStream(sysoutBytes, true)); + } + + @AfterAll + static void afterClass() { + // reset sysout/err to original value + System.setErr(prevSysErrStream); + } + + @BeforeEach + void beforeTest() { + // reset sysout collector + sysoutBytes.reset(); + } + + /** Assert that captured sysout matches given regexp (any text may follow afterwards). */ + private void assertSysoutMatches(String regex) { + // String logOutput = systemOutRule.getLogWithNormalizedLineSeparator(); + String logOutput = sysoutBytes.toString(); + logOutput = logOutput.replace("\r\n", "\n"); + regex = regex + "(.|\\n)*"; // allow any text with NL afterwards + Assertions.assertThat(logOutput).matches(regex); + } + + /** Get regex for a JUL console output. Must match JUL-Console-Formatter! */ + private String jul( + final java.util.logging.Level lvl, + final String locationPartRE, + final String msgPartRE, + final String exceptionClassAndMsgRE) { + return "JUL:.*" + lvl.getLocalizedName() + ".*" + CSNAME + + ".*" + locationPartRE + ".*" + msgPartRE + ".*\n" // use real \n at end here for better error output + + (exceptionClassAndMsgRE == null ? "" : ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?"); + } + + /** Get regex for a log4j console output. Must match log4j2-Console-Layout! */ + private String log4j( + final org.apache.logging.log4j.Level lvl, + final boolean julBridged, + final String methodPartRE, + final String msgPartRE, + final String exceptionClassAndMsgRE) { + return "log4j2:.*" + lvl.name() + ".*" + CSNAME + (julBridged ? "\\._JUL" : "") + + ".*" + CSNAME + "/\\w*" + methodPartRE + "\\w*/.*" + + msgPartRE + ".*\n" // use real \n at end here for better error output + + (exceptionClassAndMsgRE == null ? "" : ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?"); + } + + @Test + void test1SimpleLoggings1Jul() { + julLog.info("Test-'Info'-Log with JUL"); + julLog.fine("Test-'Fine'-Log with JUL"); + julLog.finest("Test-'Finest'-Log with JUL"); // should not be logged because JUL-level is FINER + julLog.warning("Test-'Warn'-Log with JUL"); // thus add another log afterwards to allow checking + final String methodRE = "SimpleLoggings1Jul"; + assertSysoutMatches(log4j(org.apache.logging.log4j.Level.INFO, true, methodRE, "'Info'-Log with JUL", null) + + jul(java.util.logging.Level.INFO, methodRE, "'Info'-Log with JUL", null) + + log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL", null) + + jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL", null) + // no finest/trace + + log4j(org.apache.logging.log4j.Level.WARN, true, methodRE, "'Warn'-Log with JUL", null) + + jul(java.util.logging.Level.WARNING, methodRE, "'Warn'-Log with JUL", null)); + } + + @Test + void test1SimpleLoggings2Log4jDirect() { + log4jLog.info("Test-'Info'-Log with log4j2"); + log4jLog.debug("Test-'Debug'-Log with log4j2"); + log4jLog.trace("Test-'Trace'-Log with log4j2"); + final String methodRE = "SimpleLoggings2Log4jDirect"; + assertSysoutMatches(log4j(org.apache.logging.log4j.Level.INFO, false, methodRE, "'Info'-Log with log4j2", null) + + log4j(org.apache.logging.log4j.Level.DEBUG, false, methodRE, "'Debug'-Log with log4j2", null) + + log4j(org.apache.logging.log4j.Level.TRACE, false, methodRE, "'Trace'-Log with log4j2", null)); + } + + @Test + void test2SubMethod() { + subMethodWithLogs(); // location info is sub method now + final String methodRE = "subMethodWithLogs"; + assertSysoutMatches( + log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL in subMethod", null) + + jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL in subMethod", null) + + log4j( + org.apache.logging.log4j.Level.INFO, + false, + methodRE, + "'Info'-Log with log4j2 in subMethod", + null)); + } + + private void subMethodWithLogs() { + julLog.fine("Test-'Fine'-Log with JUL in subMethod"); + log4jLog.info("Test-'Info'-Log with log4j2 in subMethod"); + } + + @Test + void test3JulFlow1() { + // note: manually given source information get lost in log4j! + julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam"); + final String methodRE = "JulFlow"; + assertSysoutMatches(log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE, "ENTRY", null) + + jul( + java.util.logging.Level.FINER, + "enteringExampleClassParam enteringExampleMethodParam", + "ENTRY", + null)); + } + + @Test + void test3JulFlow2() { + // note: manually given source information get lost in log4j! + julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam_withParams", new Object[] { + "with some", "parameters", 42 + }); + final String methodRE = "JulFlow"; + assertSysoutMatches( + log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE, "ENTRY.*with some.*param.*42", null) + + jul( + java.util.logging.Level.FINER, + "enteringExampleClassParam enteringExampleMethodParam_withParams", + "ENTRY.*with some.*param.*42", + null)); + } + + @Test + void test3JulFlow3() { + // note: manually given source information get lost in log4j! + julLog.exiting( + "exitingExampleClassParam", + "exitingExampleMethodParam", + Arrays.asList("array of Strings", "that are the exit", "result")); + final String methodRE = "JulFlow"; + assertSysoutMatches(log4j( + org.apache.logging.log4j.Level.TRACE, + true, + methodRE, + "RETURN.*array of Str.*that are.*result", + null) + + jul( + java.util.logging.Level.FINER, + "exitingExampleClassParam exitingExampleMethodParam", + "RETURN.*array of Str.*that are.*result", + null)); + } + + @Test + void test3JulFlow4() { + // note: manually given source information get lost in log4j! + julLog.throwing( + "throwingExampleClassParam", + "throwingExampleMethodParam", + new IllegalStateException("ONLY TEST for JUL-throwing()")); + final String methodRE = "JulFlow"; + assertSysoutMatches(log4j( + org.apache.logging.log4j.Level.TRACE, + true, + methodRE, + "THROW", + "IllegalStateException.*ONLY TEST for JUL-throwing") + + jul( + java.util.logging.Level.FINER, + "throwingExampleClassParam throwingExampleMethodParam", + "THROW", + "IllegalStateException.*ONLY TEST for JUL-throwing")); + } + + @Test + void test4JulSpecials1() { + julLog.log( + java.util.logging.Level.WARNING, + "JUL-Test via log() as warning with exception", + new java.util.zip.DataFormatException("ONLY TEST for JUL.log()")); + final String methodRE = "JulSpecials"; + assertSysoutMatches(log4j( + org.apache.logging.log4j.Level.WARN, + true, + methodRE, + "JUL-Test via log\\(\\) as warning", + "DataFormatException.*ONLY TEST for JUL") + + jul( + java.util.logging.Level.WARNING, + methodRE, + "JUL-Test via log\\(\\) as warning", + "DataFormatException.*ONLY TEST for JUL")); + } + + @Test + void test4JulSpecials2() { + // test with MessageFormat + julLog.log( + java.util.logging.Level.INFO, + "JUL-Test via log() with parameters (0={0}, 1={1}, 2={2,number,##000.0})", + new Object[] {"a", "b", 42}); + final String methodRE = "JulSpecials"; + assertSysoutMatches(log4j( + org.apache.logging.log4j.Level.INFO, + true, + methodRE, + "JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", + null) + + jul( + java.util.logging.Level.INFO, + methodRE, + "JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", + null)); + } +} diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/internal/JulLevelPropagatorTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/internal/JulLevelPropagatorTest.java new file mode 100644 index 00000000000..960e9c5ea57 --- /dev/null +++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/internal/JulLevelPropagatorTest.java @@ -0,0 +1,67 @@ +/* + * 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.jul.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.jul.tolog4j.LevelTranslator; +import org.apache.logging.log4j.core.config.Configurator; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class JulLevelPropagatorTest { + + @BeforeAll + static void setup() { + // Ensure that at least one message was sent. + Logger.getGlobal().info("Initialize"); + } + + @AfterEach + void cleanup() { + org.apache.logging.log4j.core.LoggerContext.getContext(false).reconfigure(); + } + + @Test + void initial_synchronization_works() { + // JUL levels are set from config files and the initial propagation + assertThat(Logger.getLogger("").getLevel()).isEqualTo(Level.FINER); + assertThat(Logger.getLogger("foo").getLevel()).isEqualTo(Level.WARNING); + assertThat(Logger.getLogger("foo.bar").getLevel()).isEqualTo(Level.INFO); + } + + @Test + void synchronization_retained_after_GC() { + initial_synchronization_works(); + System.gc(); // a single call is sufficient + initial_synchronization_works(); + } + + @Test + void when_set_level_synchronization_works() { + Configurator.setLevel("", LevelTranslator.CONFIG); + Configurator.setLevel("foo", org.apache.logging.log4j.Level.DEBUG); + Configurator.setLevel("foo.bar", org.apache.logging.log4j.Level.TRACE); + + assertThat(Logger.getLogger("").getLevel()).isEqualTo(Level.CONFIG); + assertThat(Logger.getLogger("foo").getLevel()).isEqualTo(Level.FINE); + assertThat(Logger.getLogger("foo.bar").getLevel()).isEqualTo(Level.FINER); + } +} diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java deleted file mode 100644 index ff644a40d35..00000000000 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CallerInformationTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul.test; - -import static org.junit.Assert.assertEquals; - -import java.util.List; -import java.util.logging.Logger; -import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.apache.logging.log4j.core.test.junit.LoggerContextRule; -import org.apache.logging.log4j.jul.LogManager; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; - -public class CallerInformationTest { - - // config from log4j-core test-jar - private static final String CONFIG = "log4j2-calling-class.xml"; - - @Rule - public final LoggerContextRule ctx = new LoggerContextRule(CONFIG); - - @BeforeClass - public static void setUpClass() { - System.setProperty("java.util.logging.manager", LogManager.class.getName()); - } - - @AfterClass - public static void tearDownClass() { - System.clearProperty("java.util.logging.manager"); - } - - @Test - public void testClassLogger() throws Exception { - final ListAppender app = ctx.getListAppender("Class").clear(); - final Logger logger = Logger.getLogger("ClassLogger"); - logger.info("Ignored message contents."); - logger.warning("Verifying the caller class is still correct."); - logger.severe("Hopefully nobody breaks me!"); - final List messages = app.getMessages(); - assertEquals("Incorrect number of messages.", 3, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller class name.", this.getClass().getName(), message); - } - } - - @Test - public void testMethodLogger() throws Exception { - final ListAppender app = ctx.getListAppender("Method").clear(); - final Logger logger = Logger.getLogger("MethodLogger"); - logger.info("More messages."); - logger.warning("CATASTROPHE INCOMING!"); - logger.severe("ZOMBIES!!!"); - logger.warning("brains~~~"); - logger.info("Itchy. Tasty."); - final List messages = app.getMessages(); - assertEquals("Incorrect number of messages.", 5, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller method name.", "testMethodLogger", message); - } - } -} diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java deleted file mode 100644 index 57d63b9372c..00000000000 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CoreLoggerTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul.test; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; - -import java.util.logging.Level; -import java.util.logging.Logger; -import org.apache.logging.log4j.core.test.appender.ListAppender; -import org.apache.logging.log4j.jul.LogManager; -import org.apache.logging.log4j.util.Strings; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -public class CoreLoggerTest extends AbstractLoggerTest { - - @BeforeClass - public static void setUpClass() { - System.setProperty("java.util.logging.manager", LogManager.class.getName()); - } - - @AfterClass - public static void tearDownClass() { - System.clearProperty("java.util.logging.manager"); - } - - @Before - public void setUp() throws Exception { - logger = Logger.getLogger(LOGGER_NAME); - logger.setFilter(null); - assertThat(logger.getLevel(), equalTo(Level.FINE)); - eventAppender = ListAppender.getListAppender("TestAppender"); - flowAppender = ListAppender.getListAppender("FlowAppender"); - stringAppender = ListAppender.getListAppender("StringAppender"); - assertNotNull(eventAppender); - assertNotNull(flowAppender); - assertNotNull(stringAppender); - } - - @After - public void tearDown() throws Exception { - if (eventAppender != null) { - eventAppender.clear(); - } - if (flowAppender != null) { - flowAppender.clear(); - } - if (stringAppender != null) { - stringAppender.clear(); - } - } - - @Test - public void testRootSetLevelToNull() throws Exception { - final Logger rootLogger = Logger.getLogger(Strings.EMPTY); - assertThat(rootLogger.getLevel(), equalTo(Level.SEVERE)); - assertThat(rootLogger.isLoggable(Level.SEVERE), is(true)); - // null test - rootLogger.setLevel(null); - assertThat(rootLogger.getLevel(), equalTo(null)); - assertThat(rootLogger.isLoggable(Level.SEVERE), is(true)); - // now go back to a different one - rootLogger.setLevel(Level.INFO); - assertThat(rootLogger.getLevel(), equalTo(Level.INFO)); - assertThat(rootLogger.isLoggable(Level.FINE), is(false)); - } - - @Test - public void testSetLevel() throws Exception { - final Logger childLogger = Logger.getLogger(LOGGER_NAME + ".Child"); - assertThat(childLogger.getLevel(), equalTo(Level.FINE)); - logger.setLevel(Level.SEVERE); - assertThat(childLogger.getLevel(), equalTo(Level.FINE)); - assertThat(logger.getLevel(), equalTo(Level.SEVERE)); - logger.setLevel(Level.FINER); - assertThat(logger.getLevel(), equalTo(Level.FINER)); - logger.setLevel(Level.FINE); - assertThat(logger.getLevel(), equalTo(Level.FINE)); - assertThat(childLogger.getLevel(), equalTo(Level.FINE)); - assertThat(childLogger.isLoggable(Level.ALL), is(false)); - } - - @Test - public void testSetLevelToNull() throws Exception { - final Logger childLogger = Logger.getLogger(LOGGER_NAME + ".NullChild"); - assertThat(childLogger.getLevel(), equalTo(Level.FINE)); - assertThat(childLogger.isLoggable(Level.FINE), is(true)); - childLogger.setLevel(Level.SEVERE); - assertThat(childLogger.getLevel(), equalTo(Level.SEVERE)); - assertThat(childLogger.isLoggable(Level.FINE), is(false)); - // null test - childLogger.setLevel(null); - assertThat(childLogger.getLevel(), equalTo(null)); - assertThat(childLogger.isLoggable(Level.FINE), is(true)); - // now go back - childLogger.setLevel(Level.SEVERE); - assertThat(childLogger.getLevel(), equalTo(Level.SEVERE)); - assertThat(childLogger.isLoggable(Level.FINE), is(false)); - } -} diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java deleted file mode 100644 index 86425c24125..00000000000 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/Log4jBridgeHandlerTest.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to you under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.logging.log4j.jul.test; - -// note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.Map.Entry; -import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.logging.log4j.core.config.LoggerConfig; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.FixMethodOrder; -import org.junit.Test; - -/** - * Test the Log4jBridgeHandler. - * Requires some configurations in the log-config-files, for format/layout - * see also jul() and log4j(): - * - JUL-config ("logging-test.properties", must be set on JVM-start via "-D..."): - * + handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler, java.util.logging.ConsoleHandler - * + org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix = _JUL - * + java.util.logging.ConsoleHandler.level = ALL - * + java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] - %5$s%6$s%n - * + .level = FINER - * - log4j2-config ("log4j2-test.xml"): - * + - * + with target="SYSTEM_ERR", follow="true", - * "log4j2: %d{HH:mm:ss.SSS} %5level - [%thread][%logger: %class/%method/%line] - %message%n" - * - * This test needs to capture syserr because it uses java.util.logging.ConsoleHandler. - * Also, it performs some outputs to console (sysout and syserr); see also field OUTPUT_CAPTURED. - * - * The code also contains evaluation/test code for development time. It is not used for the unit tests - * but kept here for reference and info. See field DEVTEST. - */ -@FixMethodOrder(org.junit.runners.MethodSorters.NAME_ASCENDING) // is nicer for manually checking console output -public class Log4jBridgeHandlerTest { - /** Perform developer tests? */ - private static final boolean DEVTEST = false; - - /** Do output the captured logger-output to stdout? */ - private static final boolean OUTPUT_CAPTURED = - !DEVTEST && Boolean.parseBoolean(System.getProperty("log4j.Log4jBridgeHandlerTest.outputCaptured")); - - /** This classes' simple name = relevant part of logger name. */ - private static final String CSNAME = Log4jBridgeHandlerTest.class.getSimpleName(); - // loggers used in many tests - private static final java.util.logging.Logger julLog = - java.util.logging.Logger.getLogger(Log4jBridgeHandlerTest.class.getName()); - private static final org.apache.logging.log4j.Logger log4jLog = org.apache.logging.log4j.LogManager.getLogger(); - - // capture sysout/syserr - // @Rule public final SystemErrRule systemOutRule = new SystemErrRule().enableLog(); - private static final ByteArrayOutputStream sysoutBytes = new ByteArrayOutputStream(1024); - private static PrintStream prevSysErrStream; - - @BeforeClass - public static void beforeClass() { - // debug output to easily recognize misconfig.: - // System.out.println("sys-props:\n" + System.getProperties()); - System.out.println("sysout: logging-cfg-file: " + System.getProperty("java.util.logging.config.file")); - if (DEVTEST) devTestBeforeClass(); // call before stderr capturing - - // JUL does not like setting stderr inbetween, so set it once and reset collecting stream - // for each method; (thus com.github.stefanbirkner:system-rules:SystemErrRule cannot be used) - System.err.println("vvv--- BEGIN capturing output to stderr ---vvv" - + " (do output of captured text to orig. stderr: " + OUTPUT_CAPTURED + ")"); - prevSysErrStream = System.err; - System.setErr(new PrintStream(sysoutBytes, true)); - } - - @AfterClass - public static void afterClass() { - // reset sysout/err to original value - System.setErr(prevSysErrStream); - System.err.println("^^^--- END capturing output of stderr ---^^^"); - } - - @Before - public void beforeTest() { - // reset sysout collector - sysoutBytes.reset(); - } - - /** Assert that captured sysout matches given regexp (any text may follow afterwards). */ - private void assertSysoutMatches(String regex) { - // String logOutput = systemOutRule.getLogWithNormalizedLineSeparator(); - String logOutput = sysoutBytes.toString(); - if (OUTPUT_CAPTURED) prevSysErrStream.print(logOutput); - logOutput = logOutput.replace("\r\n", "\n"); - regex = regex + "(.|\\n)*"; // allow any text with NL afterwards - assertTrue("Unmatching output:\n" + logOutput + "\n-- vs: --\n" + regex + "\n----", logOutput.matches(regex)); - } - - /** Get regex for a JUL console output. Must match JUL-Console-Formatter! */ - private String jul( - final java.util.logging.Level lvl, - final String locationPartRE, - final String msgPartRE, - final String exceptionClassAndMsgRE) { - return "JUL:.*" + lvl.getLocalizedName() + ".*" + CSNAME - + ".*" + locationPartRE + ".*" + msgPartRE + ".*\n" // use real \n at end here for better error output - + (exceptionClassAndMsgRE == null ? "" : ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?"); - } - - /** Get regex for a log4j console output. Must match log4j2-Console-Layout! */ - private String log4j( - final org.apache.logging.log4j.Level lvl, - final boolean julBridged, - final String methodPartRE, - final String msgPartRE, - final String exceptionClassAndMsgRE) { - return "log4j2:.*" + lvl.name() + ".*" + CSNAME + (julBridged ? "\\._JUL" : "") - + ".*" + CSNAME + "/\\w*" + methodPartRE + "\\w*/.*" - + msgPartRE + ".*\n" // use real \n at end here for better error output - + (exceptionClassAndMsgRE == null ? "" : ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?"); - } - - @Test - public void test1SimpleLoggings1Jul() { - julLog.info("Test-'Info'-Log with JUL"); - julLog.fine("Test-'Fine'-Log with JUL"); - julLog.finest("Test-'Finest'-Log with JUL"); // should not be logged because JUL-level is FINER - julLog.warning("Test-'Warn'-Log with JUL"); // thus add another log afterwards to allow checking - final String methodRE = "SimpleLoggings1Jul"; - assertSysoutMatches(log4j(org.apache.logging.log4j.Level.INFO, true, methodRE, "'Info'-Log with JUL", null) - + jul(java.util.logging.Level.INFO, methodRE, "'Info'-Log with JUL", null) - + log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL", null) - + jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL", null) - // no finest/trace - + log4j(org.apache.logging.log4j.Level.WARN, true, methodRE, "'Warn'-Log with JUL", null) - + jul(java.util.logging.Level.WARNING, methodRE, "'Warn'-Log with JUL", null)); - } - - @Test - public void test1SimpleLoggings2Log4jDirect() { - log4jLog.info("Test-'Info'-Log with log4j2"); - log4jLog.debug("Test-'Debug'-Log with log4j2"); - log4jLog.trace("Test-'Trace'-Log with log4j2"); - final String methodRE = "SimpleLoggings2Log4jDirect"; - assertSysoutMatches(log4j(org.apache.logging.log4j.Level.INFO, false, methodRE, "'Info'-Log with log4j2", null) - + log4j(org.apache.logging.log4j.Level.DEBUG, false, methodRE, "'Debug'-Log with log4j2", null) - + log4j(org.apache.logging.log4j.Level.TRACE, false, methodRE, "'Trace'-Log with log4j2", null)); - } - - @Test - public void test2SubMethod() { - subMethodWithLogs(); // location info is sub method now - final String methodRE = "subMethodWithLogs"; - assertSysoutMatches( - log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL in subMethod", null) - + jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL in subMethod", null) - + log4j( - org.apache.logging.log4j.Level.INFO, - false, - methodRE, - "'Info'-Log with log4j2 in subMethod", - null)); - } - - private void subMethodWithLogs() { - julLog.fine("Test-'Fine'-Log with JUL in subMethod"); - log4jLog.info("Test-'Info'-Log with log4j2 in subMethod"); - } - - @Test - public void test3JulFlow1() { - // note: manually given source information get lost in log4j! - julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam"); - final String methodRE = "JulFlow"; - assertSysoutMatches(log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE, "ENTRY", null) - + jul( - java.util.logging.Level.FINER, - "enteringExampleClassParam enteringExampleMethodParam", - "ENTRY", - null)); - } - - @Test - public void test3JulFlow2() { - // note: manually given source information get lost in log4j! - julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam_withParams", new Object[] { - "with some", "parameters", 42 - }); - final String methodRE = "JulFlow"; - assertSysoutMatches( - log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE, "ENTRY.*with some.*param.*42", null) - + jul( - java.util.logging.Level.FINER, - "enteringExampleClassParam enteringExampleMethodParam_withParams", - "ENTRY.*with some.*param.*42", - null)); - } - - @Test - public void test3JulFlow3() { - // note: manually given source information get lost in log4j! - julLog.exiting( - "exitingExampleClassParam", - "exitingExampleMethodParam", - Arrays.asList("array of Strings", "that are the exit", "result")); - final String methodRE = "JulFlow"; - assertSysoutMatches(log4j( - org.apache.logging.log4j.Level.TRACE, - true, - methodRE, - "RETURN.*array of Str.*that are.*result", - null) - + jul( - java.util.logging.Level.FINER, - "exitingExampleClassParam exitingExampleMethodParam", - "RETURN.*array of Str.*that are.*result", - null)); - } - - @Test - public void test3JulFlow4() { - // note: manually given source information get lost in log4j! - julLog.throwing( - "throwingExampleClassParam", - "throwingExampleMethodParam", - new IllegalStateException("ONLY TEST for JUL-throwing()")); - final String methodRE = "JulFlow"; - assertSysoutMatches(log4j( - org.apache.logging.log4j.Level.TRACE, - true, - methodRE, - "THROW", - "IllegalStateException.*ONLY TEST for JUL-throwing") - + jul( - java.util.logging.Level.FINER, - "throwingExampleClassParam throwingExampleMethodParam", - "THROW", - "IllegalStateException.*ONLY TEST for JUL-throwing")); - } - - @Test - public void test4JulSpecials1() { - julLog.log( - java.util.logging.Level.WARNING, - "JUL-Test via log() as warning with exception", - new java.util.zip.DataFormatException("ONLY TEST for JUL.log()")); - final String methodRE = "JulSpecials"; - assertSysoutMatches(log4j( - org.apache.logging.log4j.Level.WARN, - true, - methodRE, - "JUL-Test via log\\(\\) as warning", - "DataFormatException.*ONLY TEST for JUL") - + jul( - java.util.logging.Level.WARNING, - methodRE, - "JUL-Test via log\\(\\) as warning", - "DataFormatException.*ONLY TEST for JUL")); - } - - @Test - public void test4JulSpecials2() { - // test with MessageFormat - julLog.log( - java.util.logging.Level.INFO, - "JUL-Test via log() with parameters (0={0}, 1={1}, 2={2,number,##000.0})", - new Object[] {"a", "b", 42}); - final String methodRE = "JulSpecials"; - assertSysoutMatches(log4j( - org.apache.logging.log4j.Level.INFO, - true, - methodRE, - "JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", - null) - + jul( - java.util.logging.Level.INFO, - methodRE, - "JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", - null)); - } - - // no test for logrb(ResourceBundle)-case as this is very specific and seldom used (in - // my opinion); and it does not add any real thing to test here - - private void assertLogLevel(final String loggerName, final java.util.logging.Level julLevel) { - final java.util.logging.Logger lg = - java.util.logging.LogManager.getLogManager().getLogger(loggerName); - assertEquals("Logger '" + loggerName + "'", julLevel, (lg == null ? null : lg.getLevel())); - } - - @Test - public void test5LevelPropFromConfigFile() { - // JUL levels are set from config files and the initial propagation - assertLogLevel("", java.util.logging.Level.FINE); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", java.util.logging.Level.FINE); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1", java.util.logging.Level.FINER); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", java.util.logging.Level.WARNING); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2", java.util.logging.Level.ALL); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested", java.util.logging.Level.INFO); - // these are set in logging.properties but not in log4j2.xml: - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested", null); - assertLogLevel("javax.mail", null); - // these should not exist: - assertLogLevel("log4j.Log4jBridgeHandlerTest", null); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested", null); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1.deeplyNested", null); - } - - @Test - public void test5LevelPropSetLevel() { - String name = "log4j.test.new_logger_level_set"; - Configurator.setLevel(name, org.apache.logging.log4j.Level.DEBUG); - assertLogLevel(name, java.util.logging.Level.FINE); - test5LevelPropFromConfigFile(); // the rest should be untouched! - - name = "log4j.Log4jBridgeHandlerTest.propagate1.nested1"; - Configurator.setLevel(name, org.apache.logging.log4j.Level.WARN); - assertLogLevel(name, java.util.logging.Level.WARNING); - // the others around should be untouched - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", java.util.logging.Level.FINE); - assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", java.util.logging.Level.WARNING); - - // note: no need to check for the other set[Root]Level() methods, because they all call - // loggerContext.updateLoggers() which calls firePropertyChangeEvent() - } - - @Test - public void test5LevelPropGC() { - // this test will fail if you comment out "julLoggerRefs.add(julLog);" in propagateLogLevels() - test5LevelPropFromConfigFile(); // at start, all should be fine - final java.util.logging.Logger julLogRef = - java.util.logging.Logger.getLogger("log4j.Log4jBridgeHandlerTest.propagate1.nested1"); - System.gc(); // a single call is sufficient - System.out.println("sysout: test5LevelPropGC() still has reference to JUL-logger: " + julLogRef.getName() - + " / " + julLogRef); - try { - test5LevelPropFromConfigFile(); // even after GC the not referenced loggers should still be there - } catch (Throwable t) { - debugPrintJulLoggers("After GC"); - // => JUL root logger, above explicitly referenced logger and its parent ("...propagate1") - // and the global referenced julLog ("...jul.Log4jBridgeHandlerTest") are still there, the - // others are null-references now - throw t; - } - } - - /** Print all available JUL loggers to stdout. */ - private static void debugPrintJulLoggers(final String infoStr) { - final java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager(); - System.out.println("sysout: " + infoStr + " - for " + julMgr); - final java.util.List txt = new java.util.ArrayList<>(); - int n = 1; - for (Enumeration en = julMgr.getLoggerNames(); en.hasMoreElements(); ) { - final String ln = en.nextElement(); - final java.util.logging.Logger lg = julMgr.getLogger(ln); - if (lg == null) { - txt.add("(!null-Logger '" + ln + "') #" + n); - } else if (lg.getLevel() == null) { - txt.add("(null-Level Logger '" + ln + "') #" + n); - } else { - txt.add("Logger '" + ln + "', lvl = " + lg.getLevel() + " #" + n); - } - n++; - } // for - java.util.Collections.sort(txt, String.CASE_INSENSITIVE_ORDER); - for (String s : txt) { - System.out.println(" - " + s); - } - } - - //////////////// - //////////////// INTERNAL DEVELOPER TESTS follow - //////////////// (these are NOT neccessary for unit test but source is kept here for reference and info) - - static { - if (DEVTEST) { - System.out.println("sysout: static init. BEGIN"); - - // get log4j context impl. (requires log4j-core!) - // note: "LogManager.getContext();" does not work, it returns another instance!?! - final LoggerContext context = LoggerContext.getContext(false); // this matches Configurator.setLevel() impl. - final Configuration cfg = context.getConfiguration(); - // print real loggers (=> is empty when using LogManager.getContext()!?! only contains already instantiated - // loggers) - System.out.println("LogCtx " + context + " '" + context.getName() + "', loc = " - + context.getConfigLocation() + ", cfg = " + cfg + " = " + System.identityHashCode(cfg)); - for (org.apache.logging.log4j.Logger lg : context.getLoggers()) { - System.out.println("- Logger '" + lg.getName() + "', lvl = " + lg.getLevel()); - } - // print logger configurations (=> all loggers with level are present here) - System.out.println("Loggers in Cfg:"); - for (Entry entry : cfg.getLoggers().entrySet()) { - final LoggerConfig lcfg = entry.getValue(); - System.out.println( - "- '" + entry.getKey() + "' = '" + lcfg.getName() + "' / " + lcfg.getLevel() + "; " + lcfg); - } - - // print JUL loggers (=> is completely init. here, even if first JUL log and BridgeHandler-creation happens - // later) - debugPrintJulLoggers("in static-class-init"); - /* java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager(); - System.out.println("\nJUL-Loggers for " + julMgr); - for (Enumeration en = julMgr.getLoggerNames(); en.hasMoreElements(); ) { - String ln = en.nextElement(); - java.util.logging.Logger lg = julMgr.getLogger(ln); - if (lg.getLevel() == null) { - System.out.println("- (null-Level Logger '" + ln + "')"); - } else { - System.out.println("- Logger '" + ln + "', lvl = " + lg.getLevel()); - } - } */ - - // changing of log4j config. is to be done via log4j.core.config.Configurator, - // e.g. setLevel(loggerName, newLevel) - // Note: the (internal) log4j.core.Logger has a setLevel() but this should not be used. - cfg.addListener(reconfigurable -> - System.out.println("sysout: CfgListener.PropChLi-reconfigure: " + reconfigurable)); - context.addConfigurationStartedListener(configuration -> - System.out.println("sysout: CfgListener.PropChLi-configurationStarted: " + configuration)); - - System.out.println("sysout: static init. END"); - } // if - } - - private static void devTestBeforeClass() { - log4jLog.info("Dummy-Start-Log in beforeClass()"); // force init. of log4j (needed?? does not harm) - @SuppressWarnings("resource") - final LoggerContext context = LoggerContext.getContext( - false); // this matches Configurator.setLevel() impl. (instead of "LogManager.getContext();") - System.out.println("beforeClass(): LogCtx " + context + " '" + context.getName() + "', loc = " - + context.getConfigLocation() + ", cfg = " + context.getConfiguration()); - for (org.apache.logging.log4j.Logger lg : context.getLoggers()) { - System.out.println("- Logger '" + lg.getName() + "', lvl = " + lg.getLevel()); - } - - // force level change - System.out.println("sysout: now calling log4j-setLevel()"); - Configurator.setLevel("log4jTest.Dummy_set_in_devTestBeforeClass", org.apache.logging.log4j.Level.DEBUG); - } -} diff --git a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml deleted file mode 100644 index 18fb678c564..00000000000 --- a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/log4j-jul/src/test/resources/log4j2-test.xml b/log4j-jul/src/test/resources/log4j2-test.xml index 9c5bdb23ab9..91f39c768f5 100644 --- a/log4j-jul/src/test/resources/log4j2-test.xml +++ b/log4j-jul/src/test/resources/log4j2-test.xml @@ -15,32 +15,16 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/log4j-jul/src/test/resources/logging-test.properties b/log4j-jul/src/test/resources/logging-test.properties index 9a0a4756c7f..0154c180e1f 100644 --- a/log4j-jul/src/test/resources/logging-test.properties +++ b/log4j-jul/src/test/resources/logging-test.properties @@ -14,34 +14,22 @@ # See the License for the specific language governing permissions and # limitations under the License. # -### JUL configuration for Log4jBridgeHandler test +### JUL configuration for Log4jBridgeHandlerTest # JVM must be started with to use this file: -Djava.util.logging.config.file=path_to_this_file - -# install bridge but also output JUL-logs to console (order of handler matters!): +# Install bridge but also output JUL-logs to console (order of handler matters!): handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler, java.util.logging.ConsoleHandler -#org.apache.logging.log4j.jul.Log4jBridgeHandler.sysoutDebug = true # append given suffix to logger names (e.g. "_JUL"); a dot is prepended automatically org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix = _JUL +# Propagate levels org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true # ConsoleHandler defaults to INFO filtering, but we need all here java.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] - %5$s%6$s%n - - -# note: JUL levels are SEVERE, WARNING, INFO, FINE, FINER, FINEST, ALL +java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] - %5$s%6$s%n +# Note: JUL levels are: SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL # set default JUL logging level (FINER is for entering, exiting etc.) -# out-comment to use default of "INFO" - will be set by level propagation to DEBUG=FINE again -#.level = FINE org.apache.logging.log4j.jul.Log4jBridgeHandlerTest.level = FINER -# do not log mail-init. (is done on INFO-level) because this would init. JUL before setErr() happens -javax.mail.level = WARNING - -# configure (initial) JUL levels differently to log4j-config (and use high levels here) -log4j.Log4jBridgeHandlerTest.propagate1.nested1.level = SEVERE -# this is a logger not directly available in log4j, but the level above and below is defined in log4j: -log4j.Log4jBridgeHandlerTest.propagate2.nested.level = WARNING diff --git a/pom.xml b/pom.xml index dc412611b06..a4d5aa0ad8b 100644 --- a/pom.xml +++ b/pom.xml @@ -232,6 +232,7 @@ + jul-to-log4j log4j-async-logger log4j-compress log4j-config-jackson @@ -374,6 +375,12 @@ + + org.apache.logging.log4j + jul-to-log4j + ${project.version} + + org.apache.logging.log4j log4j-api @@ -572,12 +579,6 @@ ${project.version} - - org.apache.logging.log4j - log4j-web - ${project.version} - - diff --git a/src/site/antora/modules/ROOT/pages/components.adoc b/src/site/antora/modules/ROOT/pages/components.adoc index 4b99ac7335e..2576bd195f2 100644 --- a/src/site/antora/modules/ROOT/pages/components.adoc +++ b/src/site/antora/modules/ROOT/pages/components.adoc @@ -19,6 +19,31 @@ The Log4j 2 distribution contains the following artifacts: +[#jul-to-log4j] +== `jul-to-log4j` + +|=== +| JPMS module +| `org.apache.logging.jul.tolog4j` +|=== + +The `jul-to-log4j` artifact contains a bridge from +https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html[`java.util.logging`] +to the xref:manual/api.adoc[]. + +See +xref:manual/installation.adoc#impl-core-bridge-jul[Installing the JUL-to-Log4j API bridge] +on how to install it or +xref:log4j-jul.adoc#bridge-logmanager[Using `j.u.l.LogManager`] +for more details. + +[IMPORTANT] +==== +Don't deploy this artifact together with <>. +==== + +include::partial$components/jul-to-log4j.adoc[] + [#log4j-bom] == `log4j-bom` @@ -322,11 +347,11 @@ include::partial$components/log4j-jpl.adoc[] | `org.apache.logging.log4j.jul` |=== -The `log4j-jul` artifact contains a bridge from +The `log4j-jul` artifact contains an alternative bridge from https://docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html[`java.util.logging`] to the xref:manual/api.adoc[]. -See xref:manual/installation.adoc#impl-core-bridge-jul[Installing the JUL-to-Log4j API bridge] for more details. +See xref:log4j-jul.adoc#bridge-handler[Using `j.u.l.Handler`] for more details. [IMPORTANT] ==== diff --git a/src/site/antora/modules/ROOT/pages/log4j-jul.adoc b/src/site/antora/modules/ROOT/pages/log4j-jul.adoc index 854b102c388..5fb0575e3cd 100644 --- a/src/site/antora/modules/ROOT/pages/log4j-jul.adoc +++ b/src/site/antora/modules/ROOT/pages/log4j-jul.adoc @@ -15,116 +15,179 @@ Licensed to the Apache Software Foundation (ASF) under one or more limitations under the License. //// -= Log4j JDK Logging Adapter += JUL-to-Log4j bridge + +The JUL-to-Log4j bridge provides components that allow application and library that use +https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html[`java.util.logging.Logger`] +(JUL) to log to the Log4j API instead. + +[IMPORTANT] +==== +This chapter covers advanced usage scenarios of the JUL-to-Log4j bridge. +For the installation procedure and basic configuration see +xref:manual/installation.adoc#impl-core-bridge-jul[Using JUL-to-Log4j] +section of our +xref:manual/installation.adoc[Installation guide]. +==== + +[#configuration] +== Configuration + +.Struggling with the logging API, implementation, and bridge concepts? Click for an introduction. +[%collapsible] +==== +include::partial$concepts.adoc[tag=!software-type] +==== + +The `java.util.logging` logging API, available since JRE 1.4, shares many similarities with other logging API, such as SLF4J or Log4j API. +Similarly to other APIs, it allows users to change the underlying +https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/LogManager.html[`LogManager`] +implementation, but unlike other APIs, it has two big limitations: + +* it is part of JRE, which means that each JVM can contain only one instance of the `LogManager` class and all the applications of an application server must use the same `LogManager` implementation, +* it does not support auto-detection of the logging backend through `ServiceLoader` or a similar mechanim (see +https://bugs.openjdk.org/browse/JDK-8262741[JDK-8262741] +). +In order to switch to an alternate `LogManager` implementation you must be able to set the `java.util.logging.manager` system property **before** the first logging call. + +To work around the limitations of JUL, the JUL-to-Log4j bridge offers two installation options: + +. If you are able to modify the `java.util.logging.manager` system property very early in the JVM startup process, you can replace the default `LogManager` implementation with a Log4j-specific one. +This option gives the best performance. +See <> for details. +. If JUL initializes **before** your application does, which is a typical behavior in application servers, you can still configure JUL to use Log4j as appender. +See <> for details. + +[#bridge-logmanager] +== Using `LogManager` + +The best way to install the JUL-to-Log4j bridge on your system is to set the value of the `java.util.logging.manager` Java system property to + +---- +org.apache.logging.jul.tolog4j.LogManager +---- + +This property must be set very early in an application initialization process, e.g. using the `-D=` command line option of the `java` executable or by adding: + +[source,java] +---- +static { + if (System.getProperty("java.util.logging.manager") == null) { + System.setProperty("java.util.logging.manager", "org.apache.logging.jul.tolog4j.LogManager"); + } +} +---- + +at the top of your main class. + +Setting this property will replace the default JUL `LogManager` implementation with a custom implementation that translates JUL `Logger` method calls into Log4j `Logger` calls with a **minimal** overhead. + +[#bridge-logmanager-features] +=== `LogManager`-specific features + +The use of the following `j.u.l.Logger` methods is **not** supported by the default `AbstractLoggerAdapter` implementation: + +* https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html#setFilter(java.util.logging.Filter)[`Logger.setFilter(j.u.l.Filter)`], +* https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html#setLevel(java.util.logging.Level)[`Logger.setLevel(j.u.l.Level)`], +* https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html#setParent(java.util.logging.Logger)[`Logger.setParent(j.u.l.Logger)`], +* https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html#setUseParentHandlers(boolean)[`Logger.setUseParentHandlers(boolean)`], +* https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html#addHandler(java.util.logging.Handler)[`Logger.addHandler(j.u.l.Handler)`], +* https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html#removeHandler(java.util.logging.Handler)[`Logger.removeHandler(j.u.l.Handler)`]. + +The implementation of these methods depends upon the specific Log4j API implementation used. +If you need to modify the configuration of the logging backend programmatically: + +* either modify the logging backend configuration directly. +See xref:manual/customconfig.adoc[] if you are using the xref:manual/implementation.adoc[Log4j Core implementation]. +* or provide a custom `AbstractLoggerAdapter` that provides an implementation for these methods. +See <> for details about custom adapters. + +[#bridge-handler] +== Using `Log4jBridgeHandler` + +.Are you a Spring Boot user? +[TIP] +==== +Spring Boot will automatically configure `Log4jBridgeHandler`. +==== + +If setting the `java.util.logging.manager` system property is not possible, the JUL-to-Log4j bridge offers an implementation of +https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Handler.html[`j.u.l.Handler`] +abstract class, which redirects all log events to Log4j Core: +`org.apache.logging.log4j.jul.Log4jBridgeHandler`. + +[WARNING] +==== +The `Log4jBridgeHandler` requires Log4j Core as logging implementation and will fail with other Log4j API implementations. +==== + +In order to use `Log4jBridgeHandler` you can either: + +* modify the default JUL configuration file `logging.properties` to only contain: ++ +[source,properties] +---- +# Set Log4jBridgeHandler as only handler for all JUL loggers +handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler +---- ++ +See the +https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/LogManager.html[JRE documentation] +for details about the format and location of the `logging.properties` file. -The JDK Logging Adapter is a custom implementation of -https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/LogManager.html[`java.util.logging.LogManager`] -that uses link:javadoc/log4j-core/index.html[Log4j]. -This adapter can be used with either the Log4j API or Log4j Core. -When used with the API, there are a couple features of JUL that aren't supported. -However, this does allow any other Log4j Provider besides the Core provider to be used with JUL. +* or call the `Log4jBridgeHandler.install()` method in your code. -== Requirements +[IMPORTANT] +==== +Usage of `Log4jBridgeHandler` introduces a considerably higher overhead, since logging events need to traverse the entire JUL logging pipeline followed by the logging pipeline of the Log4j API implementation. -The JDK Logging Adapter is dependent on the Log4j API and optionally Log4j Core. +Consider setting <<#bridge-handler-propagateLevels>> to `true` to reduce the overhead. +==== -== Usage +[#bridge-handler-propagator] +=== Level propagators -To use the JDK Logging Adapter, you must set the system property `java.util.logging.manager` to `org.apache.logging.log4j.jul.LogManager`. +[#bridge-handler-config] +=== `Log4jBridgeHandler` configuration options -This must be done either through the command line (i.e., using the `-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager` argument) or by using `System.setProperty()` before any calls are made to `LogManager` or `Logger`. +You can tune the behavior of `Log4jBridgeHandler` by adding the following properties to the `logging.properties` configuration file, which are also available as parameters to the `install()` method call: -== Compatibility +[#bridge-handler-appendSuffix] +=== `appendSuffix` -The use of a -https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Filter.html[`java.util.logging.Filter`] -is supported on a -per-link:https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html[`Logger`] -basis. -However, it is recommended to use the standard xref:manual/filters.adoc[filters] feature in Log4j instead. - -The use of -https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Handler.html[`java.util.logging.Handler`] -classes is -_NOT_ supported. -Custom Handlers should instead use an appropriate -xref:manual/appenders.adoc[Appender] -or code their own -link:../javadoc/log4j-core/org/apache/logging/log4j/core/Appender.html[`Appender`] -plugin. - -Java logging levels are translated into Log4j logging levels dynamically. -The following table lists the conversions between a Java logging level and its equivalent Log4j level. -Custom levels should be implemented as an implementation of `LevelConverter`, and the Log4j property -xref:manual/systemproperties.adoc#log4j.jul.levelConverter[`log4j.jul.levelConverter`] -must be set to your custom class name. -Using the default `LevelConverter` implementation, custom logging levels are mapped to whatever the current level of the `Logger` being logged to is using. - -[#default-level-conversions] -=== Default Level Conversions - -.JUL to Log4j level conversion -[%header] +[cols="2h,5"] |=== -| Java Level | Log4j Level - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#OFF[`OFF`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#OFF[`OFF`] - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#SEVERE[`SEVERE`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#ERROR[`ERROR`] - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#WARNING[`WARNING`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#WARN[`WARN`] - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#OFF[`OFF`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#INFO[`INFO`] - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#CONFIG[`CONFIG`] -| custom `CONFIG` level - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINE[`FINE`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#DEBUG[`DEBUG`] - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINER[`FINER`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#TRACE[`TRACE`] - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINEST[`FINEST`] -| custom `FINEST` level - -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#ALL[`ALL`] -| {log4j2-url}/javadoc/log4j-api/org/apache/logging/log4j/Level.html#ALL[`ALL`] +| Property name | `org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix` +| `install()` parameter | `suffixToAppend` +| Type | `String` +| Default value | `null` |=== -== Log4j JDK Logging Bridge Handler - -The LogManager is not always useable because you have to set a JVM wide effective system property - e.g. -in web servers this is not possible if you are not the administrator. +Specifies the suffix to append to the name of all JUL loggers, which allows to differentiate JUL log messages from native Log4j API messages. -The `Log4jBridgeHandler` is an alternative that can be declaratively used via `logging.properties`. +[#bridge-handler-propagateLevels] +=== `propagateLevels` -It is less performant than the LogManager but still okay to use: the LogManager replaces the JDK implementation, so your logging code (using JDK syntax) effectively directly uses log4j. -When using the BridgeHandler the original JDK implementation along with its configuration (e.g. -log levels) is still fully working but the log events are "written" via this handler to log4j as if you would have called log4j.Logger.debug() etc.; -it is like a FileHandler but instead of writing to a file, it "writes" to log4j Loggers - thus there is some overhead compared to using LogManager. - -== Usage - -The JUL configuration file `logging.properties` needs the line +[cols="2h,5"] +|=== +| Property name | `org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels` +| `install()` parameter | `propagateLevels` +| Type | `boolean` +| Default value | `false` +|=== -`handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler` +The additional overhead of `Log4jBridgeHandler` can be especially heavy for **disabled** log statements. +This is why you must ensure that log event filtering of the Log4j implementation and JUL are aligned. +You can do it by either: -and JUL logs go to log4j2. -Additionally, you typically want to use to following: +* configuring JUL loggers with the same levels as the Log4j loggers, +* or setting this property to `true`, which will perform the synchronization automatically. -`org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true` +[#common-configuration] +== Common configuration -In a webapp on Tomcat (and maybe other servers, too), you may simply create a `WEB-INF/classes/logging.properties` file with above content. -The bridge and the log levels defined in this file are only valid for your webapp and do _not_ have any impact on the other webapps on the same Tomcat instance. +Independently of the way you install the JUL-to-Log4j bridge, you can finely tune the behavior of the bridge using the following configuration properties. +See xref:manual/systemproperties.adoc[] for more details. -Alternatively you may call `Log4jBridgeHandler.install()` inside your webapp's initialization code, e.g. -inside `ServletContextListener` or a `ServletFilter` static-class-init. -or `contextInitialized()`. +include::partial$manual/systemproperties/properties-log4j-jul.adoc[leveloffset=+2] -IMPORTANT: Log levels of JDK should match the ones of log4j. -You may do this manually or use the automatic level propagation via `Log4jBridgeHandler.propagateLevels = true`. diff --git a/src/site/antora/modules/ROOT/pages/manual/installation.adoc b/src/site/antora/modules/ROOT/pages/manual/installation.adoc index 719e097fb2d..4d21ed7be5f 100644 --- a/src/site/antora/modules/ROOT/pages/manual/installation.adoc +++ b/src/site/antora/modules/ROOT/pages/manual/installation.adoc @@ -246,13 +246,13 @@ include::partial$components/log4j-slf4j-impl.adoc[] You can translate {jul-link} calls to Log4j API using the `log4j-jul` artifact: -include::partial$components/log4j-jul.adoc[] +include::partial$components/jul-to-log4j.adoc[] In order to activate the bridge from JUL to Log4j API, you also need to add: [source] ---- --Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager +-Djava.util.logging.manager=org.apache.logging.jul.tolog4j.LogManager ---- to the JVM parameters in your application launcher. diff --git a/src/site/antora/modules/ROOT/partials/components/jul-to-log4j.adoc b/src/site/antora/modules/ROOT/partials/components/jul-to-log4j.adoc new file mode 100644 index 00000000000..c9a61f561c7 --- /dev/null +++ b/src/site/antora/modules/ROOT/partials/components/jul-to-log4j.adoc @@ -0,0 +1,41 @@ +//// + 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. +//// + +[tabs] +==== +Maven:: ++ +We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. ++ +[source,xml] +---- + + org.apache.logging.log4j + jul-to-log4j + runtime + +---- + +Gradle:: ++ +We assume you use xref:components.adoc#log4j-bom[`log4j-bom`] for dependency management. ++ +[source,groovy] +---- +runtimeOnly 'org.apache.logging.log4j:jul-to-log4j' +---- +==== \ No newline at end of file diff --git a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc index d7949a889b8..1addf668dca 100644 --- a/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc +++ b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc @@ -21,12 +21,46 @@ |=== | Env. variable | `LOG4J_JUL_LEVEL_CONVERTER` | Type | `Class` -| Default value | `org.apache.logging.log4j.jul.DefaultLevelConverter` +| Default value | `org.apache.logging.jul.tolog4j.internal.DefaultLevelConverter` |=== -Fully qualified name of an alternative `org.apache.logging.log4j.jul.LevelConverter` implementation. +Fully qualified name of an alternative `org.apache.logging.jul.tolog4j.spi.LevelConverter` implementation. -See xref:log4j-jul.adoc#default-level-conversions[Default Level Conversions] for the default implementation. +.Default level conversions +[%collapsible] +==== +[cols="1m,1",id=default-level-conversions] +|=== +| Java Level | Log4j Level + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#OFF[OFF] +| `OFF` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#SEVERE[SEVERE] +| `ERROR` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#WARNING[WARNING] +| `WARN` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#INFO[INFO] +| `INFO` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#CONFIG[CONFIG] +| custom `CONFIG` level with a numeric value of `450` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINE[FINE] +| `DEBUG` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINER[FINER] +| `TRACE` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINEST[FINEST] +| custom `FINEST` level with a numeric value of `700` + +| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#ALL[ALL] +| `ALL` +|=== +==== [id=log4j.jul.loggerAdapter] == `log4j.jul.loggerAdapter` @@ -35,17 +69,9 @@ See xref:log4j-jul.adoc#default-level-conversions[Default Level Conversions] for |=== | Env. variable | `LOG4J_JUL_LOGGER_ADAPTER` | Type | `Class` -| Default value | _depends on classpath_ +| Default value | `org.apache.logging.jul.tolog4j.internal.ApiLoggerAdapter` |=== -Fully qualified class name of the `org.apache.logging.log4j.jul.AbstractLoggerAdapter` implementation to use. - -This property allows users to choose between two implementation of the logging bridge: - -org.apache.logging.log4j.jul.CoreLoggerAdapter:: -The default if `log4j-core` is found in the class path. -It allows users to modify the Log4j Core configuration through the JUL https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Logger.html[`Logger`] interface. +Fully qualified class name of a custom `org.apache.logging.jul.tolog4j.spi.AbstractLoggerAdapter` implementation to use, which provides an implementation of the `j.u.l.Logger` mutator methods (e.g., `Logger.setLevel()`). -org.apache.logging.log4j.jul.ApiLoggerAdapter:: -The default if `log4j-core` cannot be found in the class path. -It disables the level mutators in the JUL `Logger` interface. \ No newline at end of file +By default `org.apache.logging.jul.tolog4j.internal.ApiLoggerAdapter` is used and the mutator methods are disabled. \ No newline at end of file