This is an automated email from the ASF dual-hosted git repository. pkarwasz pushed a commit to branch feature/main/split-jul in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git
commit 0de1b929040f2b92b302fca3aaf04b1cb07f93c6 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Mon Sep 9 20:14:59 2024 +0200 Remove `CoreLogger` and mode level propagator to new module --- {log4j-jul => log4j-jul-propagator}/pom.xml | 73 +--- .../log4j/jul/propagator/JulLevelPropagator.java | 100 ++++++ .../jul/propagator/JulLevelPropagatorTest.java | 67 ++++ .../src/test/resources/log4j2-test.xml | 31 +- .../src/test/resources/logging-test.properties | 21 ++ log4j-jul/pom.xml | 14 + .../org/apache/logging/log4j/jul/ApiLogger.java | 369 ++++++++++++--------- .../org/apache/logging/log4j/jul/CoreLogger.java | 78 ----- .../logging/log4j/jul/CoreLoggerAdapter.java | 41 --- .../logging/log4j/jul/Log4jBridgeHandler.java | 276 ++++++--------- .../apache/logging/log4j/jul/WrappedLogger.java | 75 ----- .../org/apache/logging/log4j/jul/package-info.java | 2 +- .../logging/log4j/jul/test/AbstractLoggerTest.java | 9 +- .../log4j/jul/test/CallerInformationTest.java | 139 ++++++-- .../logging/log4j/jul/test/CoreLoggerTest.java | 120 ------- .../log4j/jul/test/CustomLoggerAdapterTest.java | 69 ++++ .../log4j/jul/test/Log4jBridgeHandlerTest.java | 60 +--- .../src/test/resources/CallerInformationTest.xml | 4 +- .../src/test/resources/log4j2-julBridge-test.xml | 23 +- .../src/test/resources/logging-test.properties | 15 +- pom.xml | 1 + src/site/antora/modules/ROOT/pages/log4j-jul.adoc | 217 +++++++----- .../systemproperties/properties-log4j-jul.adoc | 50 ++- 23 files changed, 911 insertions(+), 943 deletions(-) diff --git a/log4j-jul/pom.xml b/log4j-jul-propagator/pom.xml similarity index 54% copy from log4j-jul/pom.xml copy to log4j-jul-propagator/pom.xml index 888dd58a5c..21ae711405 100644 --- a/log4j-jul/pom.xml +++ b/log4j-jul-propagator/pom.xml @@ -24,9 +24,9 @@ <relativePath>../log4j-parent</relativePath> </parent> - <artifactId>log4j-jul</artifactId> - <name>Apache Log4j JUL Adapter</name> - <description>The Apache Log4j implementation of java.util.logging</description> + <artifactId>log4j-jul-propagator</artifactId> + <name>Apache Log4j API to JUL bridge: level propagator</name> + <description>Propagates Log4j levels from Log4j Core to JUL to improve performance.</description> <dependencies> @@ -37,68 +37,34 @@ <dependency> <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-kit</artifactId> - </dependency> - - <dependency> - <groupId>org.assertj</groupId> - <artifactId>assertj-core</artifactId> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest</artifactId> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> + <artifactId>log4j-core</artifactId> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-async-logger</artifactId> - <scope>test</scope> + <artifactId>log4j-jul</artifactId> </dependency> <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-core</artifactId> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-core-test</artifactId> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> + </dependencies> <build> <plugins> + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <configuration> - <systemPropertyVariables> - <java.awt.headless>true</java.awt.headless> - </systemPropertyVariables> - <argLine>-Xms256m -Xmx1024m</argLine> - <forkCount>1</forkCount> - <reuseForks>false</reuseForks> - </configuration> - <dependencies> - <!-- Manual override of the Surefire provider --> - <!-- The `surefire-platform` provider initializes JUL before calling our tests --> - <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-junit47</artifactId> - <version>${surefire.version}</version> - </dependency> - </dependencies> <executions> <execution> <id>default-test</id> @@ -110,27 +76,14 @@ <excludes> <exclude>Log4jBridgeHandlerTest.java</exclude> </excludes> - </configuration> - </execution> - <execution> - <!-- this test needs some special configuration, thus its own run --> - <id>bridgeHandler-test</id> - <goals> - <goal>test</goal> - </goals> - <phase>test</phase> - <configuration> - <includes> - <include>Log4jBridgeHandlerTest.java</include> - </includes> <systemPropertyVariables> - <java.util.logging.config.file>src/test/resources/logging-test.properties</java.util.logging.config.file> - <log4j.configuration.location>classpath:log4j2-julBridge-test.xml</log4j.configuration.location> + <java.util.logging.config.file>${project.basedir}/src/test/resources/logging-test.properties</java.util.logging.config.file> </systemPropertyVariables> </configuration> </execution> </executions> </plugin> + </plugins> </build> </project> diff --git a/log4j-jul-propagator/src/main/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagator.java b/log4j-jul-propagator/src/main/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagator.java new file mode 100644 index 0000000000..9008db2a3f --- /dev/null +++ b/log4j-jul-propagator/src/main/java/org/apache/logging/log4j/jul/propagator/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.propagator; + +import aQute.bnd.annotation.spi.ServiceProvider; +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.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.LevelTranslator; +import org.apache.logging.log4j.jul.Log4jBridgeHandler; +import org.apache.logging.log4j.status.StatusLogger; + +/** + * Propagates Log4j Core level to JUL. + */ +@ServiceProvider(value = Log4jBridgeHandler.LevelPropagator.class) +public class JulLevelPropagator implements Log4jBridgeHandler.LevelPropagator, Consumer<Configuration> { + + private static final Logger LOGGER = StatusLogger.getLogger(); + + private final AtomicInteger installCount = new AtomicInteger(); + private LoggerContext context; + /** Save "hard" references to configured JUL loggers. */ + private final Set<java.util.logging.Logger> 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<String, LoggerConfig> 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<String> 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-propagator/src/test/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagatorTest.java b/log4j-jul-propagator/src/test/java/org/apache/logging/log4j/jul/propagator/JulLevelPropagatorTest.java new file mode 100644 index 0000000000..d55b8a3f90 --- /dev/null +++ b/log4j-jul-propagator/src/test/java/org/apache/logging/log4j/jul/propagator/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.propagator; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.jul.LevelTranslator; +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.SEVERE); + 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/resources/CallerInformationTest.xml b/log4j-jul-propagator/src/test/resources/log4j2-test.xml similarity index 52% copy from log4j-jul/src/test/resources/CallerInformationTest.xml copy to log4j-jul-propagator/src/test/resources/log4j2-test.xml index 4a73dddcc7..092fb50375 100644 --- a/log4j-jul/src/test/resources/CallerInformationTest.xml +++ b/log4j-jul-propagator/src/test/resources/log4j2-test.xml @@ -15,28 +15,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<Configuration xmlns="https://logging.apache.org/xml/ns" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation=" - https://logging.apache.org/xml/ns - https://logging.apache.org/xml/ns/log4j-config-3.xsd"> - <Appenders> - <List name="Class"> - <PatternLayout pattern="%class" - alwaysWriteExceptions="false"/> - </List> - <List name="Method"> - <PatternLayout pattern="%method" - alwaysWriteExceptions="false"/> - </List> - </Appenders> - <Loggers> - <Logger name="ClassLogger" level="INFO"> - <AppenderRef ref="Class"/> - </Logger> - <Logger name="MethodLogger" level="INFO"> - <AppenderRef ref="Method"/> - </Logger> - <Root level="OFF"/> - </Loggers> +<Configuration xmlns="https://logging.apache.org/xml/ns"> + <Appenders/> + <Loggers> + <Root level="ERROR"/> + <Logger name="foo" level="WARN"/> + <Logger name="foo.bar" level="INFO"/> + </Loggers> </Configuration> diff --git a/log4j-jul-propagator/src/test/resources/logging-test.properties b/log4j-jul-propagator/src/test/resources/logging-test.properties new file mode 100644 index 0000000000..ac2233e9a7 --- /dev/null +++ b/log4j-jul-propagator/src/test/resources/logging-test.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. +# +### JUL configuration for JulLevelPropagatorTest +# Install the bridge +handlers=org.apache.logging.log4j.jul.Log4jBridgeHandler +# Propagate levels +org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels=true diff --git a/log4j-jul/pom.xml b/log4j-jul/pom.xml index 888dd58a5c..a8ff7c6946 100644 --- a/log4j-jul/pom.xml +++ b/log4j-jul/pom.xml @@ -28,6 +28,17 @@ <name>Apache Log4j JUL Adapter</name> <description>The Apache Log4j implementation of java.util.logging</description> + <properties> + <bnd-extra-package-options> + <!-- Optional annotations --> + org.jspecify.*;resolution:=optional + </bnd-extra-package-options> + <bnd-extra-module-options> + <!-- Optional modules can not be `transitive` --> + org.jspecify;transitive=false + </bnd-extra-module-options> + </properties> + <dependencies> <dependency> @@ -110,6 +121,9 @@ <excludes> <exclude>Log4jBridgeHandlerTest.java</exclude> </excludes> + <systemPropertyVariables> + <java.util.logging.manager>org.apache.logging.log4j.jul.LogManager</java.util.logging.manager> + </systemPropertyVariables> </configuration> </execution> <execution> 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 index fc4d4d38a3..21ded344c1 100644 --- 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 @@ -18,15 +18,20 @@ package org.apache.logging.log4j.jul; import static org.apache.logging.log4j.jul.LevelTranslator.toLevel; +import java.text.MessageFormat; import java.util.ResourceBundle; import java.util.function.Supplier; 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.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.AbstractLogger; import org.apache.logging.log4j.spi.ExtendedLogger; import org.apache.logging.log4j.status.StatusLogger; @@ -46,14 +51,16 @@ import org.apache.logging.log4j.status.StatusLogger; */ public class ApiLogger extends Logger { - private final WrappedLogger logger; + private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger(); + + private final ExtendedLogger logger; private static final String FQCN = ApiLogger.class.getName(); - ApiLogger(final ExtendedLogger logger) { + protected ApiLogger(final ExtendedLogger logger) { super(logger.getName(), null); final Level javaLevel = LevelTranslator.toJavaLevel(logger.getLevel()); super.setLevel(javaLevel); - this.logger = new WrappedLogger(logger); + this.logger = logger; } @Override @@ -72,11 +79,32 @@ public class ApiLogger extends Logger { } // support for Logger.getFilter()/Logger.setFilter() - boolean isFiltered(final LogRecord logRecord) { + private boolean isFiltered(final LogRecord logRecord) { final Filter filter = getFilter(); return filter != null && !filter.isLoggable(logRecord); } + private boolean isFiltered(Level level, Throwable thrown, String msg, Object... params) { + final Filter filter = getFilter(); + if (filter == null) { + return false; + } + LogRecord lr = + new LogRecord(level, params != null && params.length > 0 ? MessageFormat.format(msg, params) : msg); + lr.setThrown(thrown); + return !filter.isLoggable(lr); + } + + private boolean isFiltered(Level level, Throwable thrown, Supplier<String> msgSupplier) { + final Filter filter = getFilter(); + if (filter == null) { + return false; + } + LogRecord lr = new LogRecord(level, msgSupplier.get()); + lr.setThrown(thrown); + return !filter.isLoggable(lr); + } + @Override public boolean isLoggable(final Level level) { return logger.isEnabled(toLevel(level)); @@ -89,20 +117,7 @@ public class ApiLogger extends Logger { @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); + LOGGER.error("Cannot set JUL log level through Log4j API: ignoring call to Logger.setLevel({})", newLevel); } /** @@ -123,12 +138,11 @@ public class ApiLogger extends Logger { return () -> logger.getMessageFactory().newMessage(msgSupplier.get()); } - private org.apache.logging.log4j.util.Supplier<LocalizedMessage> toLocalizedMessageSupplier( - ResourceBundle bundle, String msg) { + private org.apache.logging.log4j.util.Supplier<Message> toMessageSupplier(ResourceBundle bundle, String msg) { return () -> new LocalizedMessage(bundle, msg); } - private org.apache.logging.log4j.util.Supplier<LocalizedMessage> toLocalizedMessageSupplier( + private org.apache.logging.log4j.util.Supplier<Message> toMessageSupplier( ResourceBundle bundle, String msg, Object[] params) { return () -> new LocalizedMessage(bundle, msg, params); } @@ -139,11 +153,10 @@ public class ApiLogger extends Logger { @Override public void log(final Level level, final String msg) { - if (hasFilter()) { - super.log(level, msg); - } else { - logger.log(toLevel(level), msg); + if (isFiltered(level, null, msg)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, msg); } /** @@ -151,38 +164,34 @@ public class ApiLogger extends Logger { */ @Override public void log(Level level, Supplier<String> msgSupplier) { - if (hasFilter()) { - super.log(level, msgSupplier); - } else { - logger.log(toLevel(level), toLog4jSupplier(msgSupplier)); + if (isFiltered(level, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, toLog4jSupplier(msgSupplier), null); } @Override public void log(final Level level, final String msg, final Object param1) { - if (hasFilter()) { - super.log(level, msg, param1); - } else { - logger.log(toLevel(level), msg, param1); + if (isFiltered(level, null, msg, param1)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, msg, param1); } @Override public void log(final Level level, final String msg, final Object[] params) { - if (hasFilter()) { - super.log(level, msg, params); - } else { - logger.log(toLevel(level), msg, params); + if (isFiltered(level, null, msg, params)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, msg, params); } @Override public void log(final Level level, final String msg, final Throwable thrown) { - if (hasFilter()) { - super.log(level, msg, thrown); - } else { - logger.log(toLevel(level), msg, thrown); + if (isFiltered(level, thrown, msg)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, msg, thrown); } /** @@ -190,22 +199,20 @@ public class ApiLogger extends Logger { */ @Override public void log(Level level, Throwable thrown, Supplier<String> msgSupplier) { - if (hasFilter()) { - super.log(level, thrown, msgSupplier); - } else { - logger.log(toLevel(level), toLog4jSupplier(msgSupplier), thrown); + if (isFiltered(level, thrown, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, toLog4jSupplier(msgSupplier), thrown); } @Override public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg) { - if (hasFilter()) { - super.logp(level, sourceClass, sourceMethod, msg); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .log(msg); + if (isFiltered(level, null, msg)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(msg); } /** @@ -213,13 +220,12 @@ public class ApiLogger extends Logger { */ @Override public void logp(Level level, String sourceClass, String sourceMethod, Supplier<String> msgSupplier) { - if (hasFilter()) { - super.logp(level, sourceClass, sourceMethod, msgSupplier); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .log(toMessageSupplier(msgSupplier)); + if (isFiltered(level, null, msgSupplier)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(toMessageSupplier(msgSupplier)); } @Override @@ -229,13 +235,12 @@ public class ApiLogger extends Logger { final String sourceMethod, final String msg, final Object param1) { - if (hasFilter()) { - super.logp(level, sourceClass, sourceMethod, msg, param1); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .log(msg, param1); + if (isFiltered(level, null, msg, param1)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(msg, param1); } @Override @@ -245,13 +250,12 @@ public class ApiLogger extends Logger { final String sourceMethod, final String msg, final Object[] params) { - if (hasFilter()) { - super.logp(level, sourceClass, sourceMethod, msg, params); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .log(msg, params); + if (isFiltered(level, null, msg, params)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .log(msg, params); } @Override @@ -261,14 +265,13 @@ public class ApiLogger extends Logger { final String sourceMethod, final String msg, final Throwable thrown) { - if (hasFilter()) { - super.logp(level, sourceClass, sourceMethod, msg, thrown); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .withThrowable(thrown) - .log(msg); + if (isFiltered(level, thrown, msg)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .withThrowable(thrown) + .log(msg); } /** @@ -277,14 +280,13 @@ public class ApiLogger extends Logger { @Override public void logp( Level level, String sourceClass, String sourceMethod, Throwable thrown, Supplier<String> msgSupplier) { - if (hasFilter()) { - super.logp(level, sourceClass, sourceMethod, thrown, msgSupplier); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .withThrowable(thrown) - .log(toMessageSupplier(msgSupplier)); + if (isFiltered(level, thrown, msgSupplier)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .withThrowable(thrown) + .log(toMessageSupplier(msgSupplier)); } /** @@ -293,26 +295,24 @@ public class ApiLogger extends Logger { @Override public void logrb( Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg, Object... params) { - if (hasFilter()) { - super.logrb(level, sourceClass, sourceMethod, bundle, msg, params); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .log(toLocalizedMessageSupplier(bundle, msg, params)); + if (isFiltered(level, null, msg, params)) { + return; } + logger.atLevel(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) { - if (hasFilter()) { - super.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown); - } else { - logger.atLevel(toLevel(level)) - .withLocation(toLocation(sourceClass, sourceMethod)) - .withThrowable(thrown) - .log(toLocalizedMessageSupplier(bundle, msg)); + if (isFiltered(level, thrown, msg)) { + return; } + logger.atLevel(toLevel(level)) + .withLocation(toLocation(sourceClass, sourceMethod)) + .withThrowable(thrown) + .log(toMessageSupplier(bundle, msg)); } /** @@ -320,11 +320,10 @@ public class ApiLogger extends Logger { */ @Override public void logrb(Level level, ResourceBundle bundle, String msg, Object... params) { - if (hasFilter()) { - super.logrb(level, bundle, msg, params); - } else { - logger.log(toLevel(level), toLocalizedMessageSupplier(bundle, msg, params)); + if (isFiltered(level, null, msg, params)) { + return; } + logger.logIfEnabled(FQCN, toLevel(level), null, toMessageSupplier(bundle, msg, params), null); } /** @@ -332,50 +331,101 @@ public class ApiLogger extends Logger { */ @Override public void logrb(Level level, ResourceBundle bundle, String msg, Throwable thrown) { - if (hasFilter()) { - super.logrb(level, bundle, msg, thrown); - } else { - logger.atLevel(toLevel(level)).withThrowable(thrown).log(toLocalizedMessageSupplier(bundle, msg)); + if (isFiltered(level, thrown, msg)) { + return; + } + LogBuilder builder = logger.atLevel(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.traceEntry(); + if (isFiltered(Level.FINER, null, "ENTRY")) { + return; + } + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(AbstractLogger.ENTRY_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, (Object[]) null)); } @Override public void entering(final String sourceClass, final String sourceMethod, final Object param1) { - logger.traceEntry(null, param1); + if (isFiltered(Level.FINER, null, "ENTRY {0}", param1)) { + return; + } + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(AbstractLogger.ENTRY_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, param1)); } @Override public void entering(final String sourceClass, final String sourceMethod, final Object[] params) { - logger.traceEntry(null, params); + if (hasFilter()) { + // Emulate standard behavior + if (!isLoggable(Level.FINER)) { + return; + } + final StringBuilder b = new StringBuilder("ENTRY"); + if (params != null) { + for (int i = 0; i < params.length; i++) { + b.append(' ').append('{').append(i).append('}'); + } + } + if (isFiltered(Level.FINER, null, b.toString(), params)) { + return; + } + } + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(AbstractLogger.ENTRY_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newEntryMessage(null, params)); } @Override public void exiting(final String sourceClass, final String sourceMethod) { - logger.traceExit(); + if (isFiltered(Level.FINER, null, "RETURN")) { + return; + } + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(AbstractLogger.EXIT_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, (Object) null)); } @Override public void exiting(final String sourceClass, final String sourceMethod, final Object result) { - logger.traceExit(result); + if (isFiltered(Level.FINER, null, "RETURN {0}", result)) { + return; + } + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(AbstractLogger.EXIT_MARKER) + .log(DefaultFlowMessageFactory.INSTANCE.newExitMessage(null, result)); } @Override public void throwing(final String sourceClass, final String sourceMethod, final Throwable thrown) { - logger.throwing(thrown); + if (isFiltered(Level.FINER, thrown, "THROW")) { + return; + } + logger.atTrace() + .withLocation(toLocation(sourceClass, sourceMethod)) + .withMarker(AbstractLogger.THROWING_MARKER) + .withThrowable(thrown) + .log("Throwing"); } @Override public void severe(final String msg) { - if (hasFilter()) { - super.severe(msg); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, msg); + if (isFiltered(Level.SEVERE, null, msg)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, msg); } /** @@ -383,119 +433,106 @@ public class ApiLogger extends Logger { */ @Override public void severe(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.severe(msgSupplier); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.SEVERE, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, toLog4jSupplier(msgSupplier), null); } @Override public void warning(final String msg) { - if (hasFilter()) { - super.warning(msg); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, msg); + if (isFiltered(Level.WARNING, null, msg)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, msg); } @Override public void warning(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.warning(msgSupplier); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.WARNING, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, toLog4jSupplier(msgSupplier), null); } @Override public void info(final String msg) { - if (hasFilter()) { - super.info(msg); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, msg); + if (isFiltered(Level.INFO, null, msg)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, msg); } @Override public void info(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.info(msgSupplier); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.INFO, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, toLog4jSupplier(msgSupplier), null); } @Override public void config(final String msg) { - if (hasFilter()) { - super.config(msg); - } else { - logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg); + if (isFiltered(Level.CONFIG, null, msg)) { + return; } + logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg); } @Override public void config(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.config(msgSupplier); - } else { - logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.CONFIG, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, toLog4jSupplier(msgSupplier), null); } @Override public void fine(final String msg) { - if (hasFilter()) { - super.fine(msg); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, msg); + if (isFiltered(Level.FINE, null, msg)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, msg); } @Override public void fine(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.fine(msgSupplier); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.FINE, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, toLog4jSupplier(msgSupplier), null); } @Override public void finer(final String msg) { - if (hasFilter()) { - super.finer(msg); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, msg); + if (isFiltered(Level.FINER, null, msg)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, msg); } @Override public void finer(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.finer(msgSupplier); - } else { - logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.FINER, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, toLog4jSupplier(msgSupplier), null); } @Override public void finest(final String msg) { - if (hasFilter()) { - super.finest(msg); - } else { - logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg); + if (isFiltered(Level.FINEST, null, msg)) { + return; } + logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg); } @Override public void finest(Supplier<String> msgSupplier) { - if (hasFilter()) { - super.finest(msgSupplier); - } else { - logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, toMessageSupplier(msgSupplier), null); + if (isFiltered(Level.FINEST, null, msgSupplier)) { + return; } + logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, toLog4jSupplier(msgSupplier), null); } private boolean hasFilter() { 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 ebc06985a8..0000000000 --- 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. <strong>Note that this implementation does - * <em>not</em> use the {@link java.util.logging.Handler} class.</strong> 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. <strong>Note that the - * Log4j version of JDK Loggers do <em>not</em> use Handlers.</strong> - * - * @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 4ca968f197..0000000000 --- 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 d5c44ecdc7..2f7a0f82a5 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.log4j.spi.ExtendedLogger; import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.ServiceLoaderUtil; /** - * Bridge from JUL to log4j2.<br> - * 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.<br><br> - * - * Installation/usage:<ul> - * <li> Declaratively inside JUL's <code>logging.properties</code>:<br> - * <code>handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler</code><br> - * (and typically also: <code>org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true</code> )<br> - * Note: in a webapp running on Tomcat, you may create a <code>WEB-INF/classes/logging.properties</code> - * file to configure JUL for this webapp only: configured handlers and log levels affect your webapp only! - * This file is then the <i>complete</i> JUL configuration, so JUL's defaults (e.g. log level INFO) apply - * for stuff not explicitly defined therein. - * <li> Programmatically by calling <code>install()</code> method, - * e.g. inside ServletContextListener static-class-init. or contextInitialized() - * </ul> - * Configuration (in JUL's <code>logging.properties</code>):<ul> - * <li> Log4jBridgeHandler.<code>suffixToAppend</code><br> - * 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<br> - * Example: <code>Log4jBridgeHandler.suffixToAppend = _JUL</code><br> - * 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). - * <li> Log4jBridgeHandler.<code>propagateLevels</code> boolean, "true" to automatically propagate log4j log levels to JUL. - * <li> Log4jBridgeHandler.<code>sysoutDebug</code> boolean, perform some (developer) debug output to sysout - * </ul> - * - * Log levels are translated with {@link LevelTranslator}, see also - * <a href="https://logging.apache.org/log4j/2.x/log4j-jul/index.html#Default_Level_Conversions">log4j doc</a>.<br><br> + * Bridge from JUL to Log4j API + * <p> + * This is an alternative to {@link LogManager} (running as complete JUL replacement), especially useful for + * webapps running on a container for which the LogManager cannot or should not be used. + * </p> * - * Restrictions:<ul> - * <li> 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. - * <li> 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. - * <li> 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 <code>.level = SEVERE</code> only error logs will be seen by this handler and thus log4j - * - even if the corresponding log4j log level is ALL.<br> - * On the other side, you should NOT set <code>.level = FINER or FINEST</code> 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.<br> - * Note: JUL's default log level (i.e. none specified in logger.properties) is INFO. + * <p> + * Installation/usage: + * </p> + * <ul> + * <li> + * <p>Declaratively inside JUL's {@code logging.properties}):</p> + * <pre> + * 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 + * </pre> + * <p><strong>Note:</strong> 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.</p> + * </li> + * <li> + * Programmatically by calling {@link #install(boolean, String, boolean)}, e.g., inside a + * <a href="https://jakarta.ee/specifications/platform/11/apidocs/jakarta/servlet/servletcontextlistener">ServletContextListener</a>. + * </li> * </ul> * - * (Credits: idea and concept originate from org.slf4j.bridge.SLF4JBridgeHandler; - * level propagation idea originates from logback/LevelChangePropagator; - * but no source code has been copied) - * + * <p> + * <strong>Credits</strong>: 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. + * </p> + * @see <a href="https://logging.apache.org/log4j/3.x/log4j-jul.html">Log4j documentation site</a> * @since 2.15.0 */ -public class Log4jBridgeHandler extends java.util.logging.Handler implements Consumer<Configuration> { - private static final org.apache.logging.log4j.Logger SLOGGER = StatusLogger.getLogger(); +@ServiceConsumer(value = Log4jBridgeHandler.LevelPropagator.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 static final String DOC_URL = + "https://logging.apache.org/log4j/3.x/log4j-jul.html##bridge-handler-propagator"; private boolean doDebugOutput = false; private String julSuffixToAppend = null; + private LevelPropagator levelPropagator; private volatile boolean installAsLevelPropagator = false; /** * Adds a new Log4jBridgeHandler instance to JUL's root logger. - * This is a programmatic alternative to specify - * <code>handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler</code> - * and its configuration in logging.properties.<br> - * @param removeHandlersForRootLogger true to remove all other installed handlers on JUL root level + * <p> + * This is a programmatic alternative for a {@code logging.properties} file. + * </p> + * @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 class Log4jBridgeHandler extends java.util.logging.Handler implements Con 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 @@ public class Log4jBridgeHandler extends java.util.logging.Handler implements Con 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,23 @@ public class Log4jBridgeHandler extends java.util.logging.Handler implements Con } this.julSuffixToAppend = suffixToAppend; - this.installAsLevelPropagator = propagateLevels; + installAsLevelPropagator = propagateLevels; + if (installAsLevelPropagator) { + levelPropagator = ServiceLoaderUtil.safeStream( + LevelPropagator.class, + ServiceLoader.load(LevelPropagator.class, getClass().getClassLoader()), + LOGGER) + .findAny() + .orElse(null); + if (levelPropagator == null) { + LOGGER.warn( + "A JUL level propagator was requested, but no implementation was found.\nSee {} for details.", + DOC_URL); + installAsLevelPropagator = false; + } + } - SLOGGER.debug( + LOGGER.debug( "Log4jBridgeHandler init. with: suffix='{}', lvlProp={}, instance={}", suffixToAppend, propagateLevels, @@ -163,11 +157,8 @@ public class Log4jBridgeHandler extends java.util.logging.Handler implements Con @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 +169,18 @@ public class Log4jBridgeHandler extends java.util.logging.Handler implements Con } // 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; + 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 = "<null log 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) { @@ -237,89 +217,29 @@ public class Log4jBridgeHandler extends java.util.logging.Handler implements Con 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<java.util.logging.Logger> 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(); - } + /** + * Propagates level configuration from the Log4j API implementation used + * <p> + * 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. + * </p> + * <p> + * 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. + * </p> + * @since 3.0.0 + */ + public interface LevelPropagator { - // if (DEVTEST) debugPrintJulLoggers("Start of propagation"); - // walk through all log4j configured loggers and set JUL level accordingly - final Map<String, LoggerConfig> log4jLoggers = config.getLoggers(); - // java.util.List<String> 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"); + /** + * Start propagating log levels. + */ + void start(); - // 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<String> 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"); + /** + * Stop propagating log levels. + */ + void stop(); } - - /* 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<String> txt = new java.util.ArrayList<>(); - int n = 1; - for (Enumeration<String> 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 4ef6009985..0000000000 --- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java +++ /dev/null @@ -1,75 +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.Marker; -import org.apache.logging.log4j.message.EntryMessage; -import org.apache.logging.log4j.message.Message; -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 extends Throwable> T throwing(final T t) { - return throwing(FQCN, LevelTranslator.toLevel(java.util.logging.Level.FINER), t); - } - - @Override - protected void log( - Level level, Marker marker, String fqcn, StackTraceElement location, Message message, Throwable throwable) { - logger.logMessage(level, marker, fqcn, location, message, throwable); - } -} 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 dd22747306..dd534ee454 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/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java index 303ff62db3..4bc75c3d5a 100644 --- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java +++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/AbstractLoggerTest.java @@ -19,6 +19,7 @@ package org.apache.logging.log4j.jul.test; import static org.assertj.core.api.Assertions.assertThat; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.core.LogEvent; @@ -96,19 +97,21 @@ public abstract class AbstractLoggerTest { } @Test - public void testAlteringLogFilter() throws Exception { + public void testCountingLogFilter() throws Exception { + AtomicInteger counter = new AtomicInteger(); logger.setFilter(record -> { - record.setMessage("This is not the message you are looking for."); + counter.incrementAndGet(); return true; }); logger.info("Informative message here."); + assertThat(counter).hasValue(1); final List<LogEvent> 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.getMessage().getFormattedMessage()).isEqualTo("Informative message here."); assertThat(event.getLoggerFqcn()).isEqualTo(ApiLogger.class.getName()); } 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 index ce8c981f06..be15806615 100644 --- 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 @@ -18,17 +18,26 @@ package org.apache.logging.log4j.jul.test; import static org.junit.Assert.assertEquals; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +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.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.Before; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +@RunWith(Parameterized.class) public class CallerInformationTest { private static final String PARAM_1 = "PARAM_1"; @@ -49,17 +58,58 @@ public class CallerInformationTest { System.clearProperty("java.util.logging.manager"); } + @Parameterized.Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + private final boolean withFilter; + private CountingFilter filter; + + public CallerInformationTest(final boolean withFilter) { + this.withFilter = withFilter; + } + + @Before + public void setup() { + filter = withFilter ? new CountingFilter() : null; + Logger.getLogger("ClassLogger").setFilter(filter); + Logger.getLogger("MethodLogger").setFilter(filter); + } + @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!"); + // 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<String> 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); + assertMessageCount(19, messages); + 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 @@ -70,10 +120,17 @@ public class CallerInformationTest { 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.", 6, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller class name.", SOURCE_CLASS, message); + assertMessageCount(12, messages); + for (int i = 0; i < messages.size(); i++) { + String message = messages.get(i); + assertEquals("Incorrect caller class name for message " + i, SOURCE_CLASS, message); } } @@ -81,15 +138,32 @@ public class CallerInformationTest { 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."); + // 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<String> messages = app.getMessages(); - assertEquals("Incorrect number of messages.", 5, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller method name.", "testMethodLogger", message); + assertMessageCount(19, messages); + 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 @@ -100,10 +174,35 @@ public class CallerInformationTest { 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.", 6, messages.size()); - for (final String message : messages) { - assertEquals("Incorrect caller method name.", SOURCE_METHOD, message); + assertMessageCount(12, messages); + for (int i = 0; i < messages.size(); i++) { + String message = messages.get(i); + assertEquals("Incorrect caller class name for message " + i, SOURCE_METHOD, message); + } + } + + private void assertMessageCount(int expectedCount, List<String> messages) { + assertEquals("Incorrect number of messages.", expectedCount, messages.size()); + if (filter != null) { + assertEquals("Incorrect number of filtered messages.", expectedCount, filter.count.getAndSet(0)); + } + } + + private static final class CountingFilter implements Filter { + + AtomicInteger count = new AtomicInteger(); + + @Override + public boolean isLoggable(LogRecord record) { + count.incrementAndGet(); + return true; } } } 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 57d63b9372..0000000000 --- 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/CustomLoggerAdapterTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CustomLoggerAdapterTest.java new file mode 100644 index 0000000000..b1d11aa4b7 --- /dev/null +++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/test/CustomLoggerAdapterTest.java @@ -0,0 +1,69 @@ +/* + * 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.apache.logging.log4j.jul.test.JulTestProperties.JUL_LOGGER_ADAPTER; +import static org.junit.Assert.assertTrue; + +import java.util.logging.Logger; +import org.apache.logging.log4j.jul.AbstractLoggerAdapter; +import org.apache.logging.log4j.jul.ApiLogger; +import org.apache.logging.log4j.jul.LogManager; +import org.apache.logging.log4j.spi.ExtendedLogger; +import org.apache.logging.log4j.spi.LoggerContext; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Tests if the logger adapter can be customized. + */ +public class CustomLoggerAdapterTest { + + @BeforeClass + public static void setUpClass() { + System.setProperty("java.util.logging.manager", LogManager.class.getName()); + System.setProperty(JUL_LOGGER_ADAPTER, CustomLoggerAdapter.class.getName()); + } + + @AfterClass + public static void tearDownClass() { + System.clearProperty("java.util.logging.manager"); + System.clearProperty(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 + protected Logger newLogger(String name, LoggerContext context) { + return new CustomLogger(context.getLogger(name)); + } + } + + private static class CustomLogger extends ApiLogger { + + CustomLogger(ExtendedLogger logger) { + super(logger); + } + } +} 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 index 86425c2412..46eb50f944 100644 --- 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 @@ -18,7 +18,6 @@ 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; @@ -29,6 +28,7 @@ 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.assertj.core.api.Assertions; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -112,7 +112,7 @@ public class Log4jBridgeHandlerTest { 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)); + Assertions.assertThat(logOutput).matches(regex); } /** Get regex for a JUL console output. Must match JUL-Console-Formatter! */ @@ -307,62 +307,6 @@ public class Log4jBridgeHandlerTest { 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(); diff --git a/log4j-jul/src/test/resources/CallerInformationTest.xml b/log4j-jul/src/test/resources/CallerInformationTest.xml index 4a73dddcc7..087587ea5d 100644 --- a/log4j-jul/src/test/resources/CallerInformationTest.xml +++ b/log4j-jul/src/test/resources/CallerInformationTest.xml @@ -31,10 +31,10 @@ </List> </Appenders> <Loggers> - <Logger name="ClassLogger" level="INFO"> + <Logger name="ClassLogger" level="ALL"> <AppenderRef ref="Class"/> </Logger> - <Logger name="MethodLogger" level="INFO"> + <Logger name="MethodLogger" level="ALL"> <AppenderRef ref="Method"/> </Logger> <Root level="OFF"/> diff --git a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml index 18fb678c56..15cd5a5d4c 100644 --- a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml +++ b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml @@ -15,25 +15,20 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<Configuration status="info"> +<Configuration xmlns="https://logging.apache.org/xml/ns" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + https://logging.apache.org/xml/ns + https://logging.apache.org/xml/ns/log4j-config-3.xsd"> <Appenders> <Console name="STDOUT" target="SYSTEM_ERR" follow="true"> <!-- syserr + follow !! --> - <PatternLayout pattern="log4j2: %d{HH:mm:ss.SSS} %5level - [%thread][%logger: %class/%method/%line] - %message%n" /> + <PatternLayout + pattern="log4j2: %d{HH:mm:ss.SSS} %5level - [%thread][%logger: %class/%method/%line] - %message%n"/> </Console> </Appenders> - <Loggers> - <Root level="DEBUG"> - <AppenderRef ref="STDOUT" /> + <Root level="TRACE"> + <AppenderRef ref="STDOUT"/> </Root> - - <!-- needs to be set to a lower level: --> - <Logger name="org.apache.logging.log4j.jul.test.Log4jBridgeHandlerTest" level="TRACE" /> - <!-- some test configs: --> - <Logger name="log4j.Log4jBridgeHandlerTest.propagate1" level="DEBUG" /> - <Logger name="log4j.Log4jBridgeHandlerTest.propagate1.nested1" level="TRACE" /> - <Logger name="log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested" level="WARN" /> - <Logger name="log4j.Log4jBridgeHandlerTest.propagate2" level="ALL" /> - <Logger name="log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested" level="INFO" /> </Loggers> </Configuration> diff --git a/log4j-jul/src/test/resources/logging-test.properties b/log4j-jul/src/test/resources/logging-test.properties index 9a0a4756c7..4b1755391a 100644 --- a/log4j-jul/src/test/resources/logging-test.properties +++ b/log4j-jul/src/test/resources/logging-test.properties @@ -29,19 +29,10 @@ 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 +org.apache.logging.log4j.jul.test.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 a4b354f9b2..f1c1ce0ec5 100644 --- a/pom.xml +++ b/pom.xml @@ -264,6 +264,7 @@ <module>log4j-spring-cloud-config-client</module> <module>log4j-to-jul</module> <module>log4j-to-slf4j</module> + <module>log4j-jul-propagator</module> </modules> diff --git a/src/site/antora/modules/ROOT/pages/log4j-jul.adoc b/src/site/antora/modules/ROOT/pages/log4j-jul.adoc index 854b102c38..c5b7d482ac 100644 --- a/src/site/antora/modules/ROOT/pages/log4j-jul.adoc +++ b/src/site/antora/modules/ROOT/pages/log4j-jul.adoc @@ -15,116 +15,175 @@ Licensed to the Apache Software Foundation (ASF) under one or more limitations under the License. //// -= Log4j JDK Logging Adapter += JUL-to-Log4j bridge -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. +The JUL-to-Log4j bridge provides components that allow application and library that use +https://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html[`java.util.logging.Logger`] +(JUL) to log to the Log4j API instead. -== Requirements +[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]. +==== -The JDK Logging Adapter is dependent on the Log4j API and optionally Log4j Core. +[#configuration] +== Configuration -== Usage +.Struggling with the logging API, implementation, and bridge concepts? Click for an introduction. +[%collapsible] +==== +include::partial$concepts.adoc[tag=!software-type] +==== -To use the JDK Logging Adapter, you must set the system property `java.util.logging.manager` to `org.apache.logging.log4j.jul.LogManager`. +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/javase/8/docs/api/java/util/logging/LogManager.html[`LogManager`] +implementation, but unlike other APIs, it has two big limitations: -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`. +* 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. -== Compatibility +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 <<bridge-logmanager>> 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 <<bridge-handler>> 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.log4j.jul.LogManager +---- + +This property must be set very early in an application initialization process, e.g. using the `-D<property>=<value>` 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.log4j.jul.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 a -https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Filter.html[`java.util.logging.Filter`] +https://docs.oracle.com/javase/{java-target-version}/docs/api/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`] +https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Logger.html[per-`Logger`] basis. -However, it is recommended to use the standard xref:manual/filters.adoc[filters] feature in Log4j instead. +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] -|=== -| Java Level | Log4j Level +https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Handler.html[`java.util.logging.Handler`] +classes is **not** supported. +Custom handlers should be replaced with the appropriate +xref:manual/appenders.adoc[Log4j Appender]. -| 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`] +[#bridge-handler] +== Using `Log4jBridgeHandler` -| 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`] +.Are you a Spring Boot user? +[TIP] +==== +Spring Boot will automatically configure `Log4jBridgeHandler`. +==== -| 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`] +If setting the `java.util.logging.manager` system property is not possible, the JUL-to-Log4j bridge offers an implementation of JUL's +https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Handler.html[`Handler`] +abstract class, which redirects all log events to Log4j Core: +`org.apache.logging.log4j.jul.Log4jBridgeHandler`. -| 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`] +[WARNING] +==== +The `Log4jBridgeHandler` requires Log4j Core as logging implementation and will fail with other Log4j API implementations. +==== -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#CONFIG[`CONFIG`] -| custom `CONFIG` level +In order to use `Log4jBridgeHandler` you can either: -| 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`] +* 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/javase/8/docs/api/java/util/logging/LogManager.html[JRE documentation] +for details about the format and location of the `logging.properties` file. -| 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`] +* or call the `Log4jBridgeHandler.install()` method in your code. -| https://docs.oracle.com/en/java/javase/{java-target-version}/docs/api/java.logging/java/util/logging/Level.html#FINEST[`FINEST`] -| custom `FINEST` level +[IMPORTANT] +==== +Usage of `Log4jBridgeHandler` introduces a considerably higher overhead that the usage of `LogManager`, since logging events need to traverse the entire JUL logging pipeline followed by the logging pipeline of the Log4j API implementation. -| 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`] -|=== +Consider setting <<#bridge-handler-propagateLevels>> to `true` to reduce the overhead. +==== + +[#bridge-handler-propagator] +=== Level propagators + +[#bridge-handler-config] +=== `Log4jBridgeHandler` configuration options -== Log4j JDK Logging Bridge Handler +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: -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. +[#bridge-handler-appendSuffix] +=== `appendSuffix` -The `Log4jBridgeHandler` is an alternative that can be declaratively used via `logging.properties`. +[cols="2h,5"] +|=== +| Property name | `org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix` +| `install()` parameter | `suffixToAppend` +| Type | `String` +| Default value | `null` +|=== -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. +Specifies the suffix to append to the name of all JUL loggers, which allows to differentiate JUL log messages from native Log4j API messages. -== Usage +[#bridge-handler-propagateLevels] +=== `propagateLevels` -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/partials/manual/systemproperties/properties-log4j-jul.adoc b/src/site/antora/modules/ROOT/partials/manual/systemproperties/properties-log4j-jul.adoc index d7949a889b..08b22a2811 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 @@ -26,7 +26,41 @@ Fully qualified name of an alternative `org.apache.logging.log4j.jul.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/javase/{java-target-version}/docs/api/java/util/logging/Level.html#OFF[OFF] +| `OFF` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#SEVERE[SEVERE] +| `ERROR` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#WARNING[WARNING] +| `WARN` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#INFO[INFO] +| `INFO` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#CONFIG[CONFIG] +| custom `CONFIG` level with a numeric value of `450` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#FINE[FINE] +| `DEBUG` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#FINER[FINER] +| `TRACE` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/java/util/logging/Level.html#FINEST[FINEST] +| custom `FINEST` level with a numeric value of `700` + +| https://docs.oracle.com/javase/{java-target-version}/docs/api/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<? extends AbstractLoggerAdapter>` -| Default value | _depends on classpath_ +| Default value | `org.apache.logging.log4j.jul.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.log4j.jul.AbstractLoggerAdapter` implementation to use, which decide how to handle the level mutator methods in the JUL `Logger` interface. -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.log4j.jul.ApiLoggerAdapter` is used and the mutator methods are disabled. \ No newline at end of file
