diff --git a/jpl-to-log4j-api/pom.xml b/jpl-to-log4j-api/pom.xml new file mode 100644 index 0000000..05f46b2 --- /dev/null +++ b/jpl-to-log4j-api/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j-jdk-parent + ${revision} + ../parent + + + jpl-to-log4j-api + JPL to Log4j API logging bridge + The Apache Log4j implementation of java.lang.System.LoggerFinder + + + + false + + org.apache.logging.jpl.log4j.api + 11 + + + + + + org.apache.logging.log4j + log4j-api + + + + org.apache.logging.log4j + log4j-core + test + + + + org.apache.logging.log4j + log4j-core-test + test + + + + org.hamcrest + hamcrest + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + diff --git a/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLogger.java b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLogger.java new file mode 100644 index 0000000..cc384bd --- /dev/null +++ b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLogger.java @@ -0,0 +1,145 @@ +/* + * 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.jpl; + +import java.lang.System.Logger; +import java.util.MissingResourceException; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFormatMessage; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.spi.ExtendedLogger; + +/** + * JPL {@link Logger logger} implementation that uses Log4j. + * Implement all default {@link Logger} methods to ensure proper class resolution + * + * @since 2.14 + */ +public class Log4jSystemLogger implements Logger { + + private final ExtendedLogger logger; + + private static final String FQCN = Log4jSystemLogger.class.getName(); + + public Log4jSystemLogger(final ExtendedLogger logger) { + this.logger = logger; + } + + @Override + public String getName() { + return logger.getName(); + } + + @Override + public boolean isLoggable(final Level level) { + return logger.isEnabled(getLevel(level)); + } + + @Override + public void log(final Level level, final String msg) { + log(level, (ResourceBundle) null, msg, (Throwable) null); + } + + @Override + public void log(final Level level, final Supplier msgSupplier) { + Objects.requireNonNull(msgSupplier); + if (isLoggable(Objects.requireNonNull(level))) { + log(level, (ResourceBundle) null, msgSupplier.get(), (Throwable) null); + } + } + + @Override + public void log(final Level level, final Object obj) { + Objects.requireNonNull(obj); + if (isLoggable(Objects.requireNonNull(level))) { + log(level, (ResourceBundle) null, obj.toString(), (Throwable) null); + } + } + + @Override + public void log(final Level level, final String msg, final Throwable thrown) { + log(level, null, msg, thrown); + } + + @Override + public void log(final Level level, final Supplier msgSupplier, final Throwable thrown) { + Objects.requireNonNull(msgSupplier); + if (isLoggable(Objects.requireNonNull(level))) { + log(level, null, msgSupplier.get(), thrown); + } + } + + @Override + public void log(final Level level, final String format, final Object... params) { + log(level, null, format, params); + } + + @Override + public void log(final Level level, final ResourceBundle bundle, final String msg, final Throwable thrown) { + logger.logIfEnabled(FQCN, getLevel(level), null, getResource(bundle, msg), thrown); + } + + @Override + public void log(final Level level, final ResourceBundle bundle, final String format, final Object... params) { + final Message message = createMessage(getResource(bundle, format), params); + logger.logIfEnabled(FQCN, getLevel(level), null, message, message.getThrowable()); + } + + private static Message createMessage(final String format, final Object... params) { + if (params == null || params.length == 0) { + return new SimpleMessage(format); + } + return new MessageFormatMessage(format, params); + } + + private static org.apache.logging.log4j.Level getLevel(final Level level) { + switch (level) { + case OFF: + return org.apache.logging.log4j.Level.OFF; + case ERROR: + return org.apache.logging.log4j.Level.ERROR; + case WARNING: + return org.apache.logging.log4j.Level.WARN; + case INFO: + return org.apache.logging.log4j.Level.INFO; + case DEBUG: + return org.apache.logging.log4j.Level.DEBUG; + case TRACE: + return org.apache.logging.log4j.Level.TRACE; + case ALL: + return org.apache.logging.log4j.Level.ALL; + } + return org.apache.logging.log4j.Level.ERROR; + } + + private static String getResource(final ResourceBundle bundle, final String msg) { + if (bundle == null || msg == null) { + return msg; + } + try { + return bundle.getString(msg); + } catch (MissingResourceException e) { + // ignore + return msg; + } catch (ClassCastException ex) { + return bundle.getObject(msg).toString(); + } + } +} diff --git a/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java new file mode 100644 index 0000000..ec41841 --- /dev/null +++ b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.jpl; + +import java.lang.System.Logger; +import java.lang.System.LoggerFinder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.spi.AbstractLoggerAdapter; +import org.apache.logging.log4j.spi.LoggerContext; +import org.apache.logging.log4j.util.StackLocatorUtil; + +/** + * {@link Logger} registry implementation using just log4j-api. + * + * @since 2.14 + */ +public class Log4jSystemLoggerAdapter extends AbstractLoggerAdapter { + + @Override + protected Logger newLogger(final String name, final LoggerContext context) { + return new Log4jSystemLogger(context.getLogger(name)); + } + + @Override + protected LoggerContext getContext() { + return getContext( + LogManager.getFactory().isClassLoaderDependent() + ? StackLocatorUtil.getCallerClass(LoggerFinder.class) + : null); + } +} diff --git a/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerFinder.java b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerFinder.java new file mode 100644 index 0000000..4cc7ae1 --- /dev/null +++ b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerFinder.java @@ -0,0 +1,35 @@ +/* + * 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.jpl; + +import aQute.bnd.annotation.Resolution; +import aQute.bnd.annotation.spi.ServiceProvider; +import java.lang.System.Logger; + +/** + * @since 2.14 + */ +@ServiceProvider(value = System.LoggerFinder.class, resolution = Resolution.OPTIONAL) +public class Log4jSystemLoggerFinder extends System.LoggerFinder { + + private final Log4jSystemLoggerAdapter loggerAdapter = new Log4jSystemLoggerAdapter(); + + @Override + public Logger getLogger(final String name, final Module module) { + return loggerAdapter.getLogger(name); + } +} diff --git a/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/package-info.java b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/package-info.java new file mode 100644 index 0000000..f2db704 --- /dev/null +++ b/jpl-to-log4j-api/src/main/java/org/apache/logging/log4j/jpl/package-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +@Export +@Version("2.20.1") +package org.apache.logging.log4j.jpl; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/jpl-to-log4j-api/src/main/resources/META-INF/services/java.lang.System$LoggerFinder b/jpl-to-log4j-api/src/main/resources/META-INF/services/java.lang.System$LoggerFinder new file mode 100644 index 0000000..0f0f23e --- /dev/null +++ b/jpl-to-log4j-api/src/main/resources/META-INF/services/java.lang.System$LoggerFinder @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +org.apache.logging.log4j.jpl.Log4jSystemLoggerFinder \ No newline at end of file diff --git a/jpl-to-log4j-api/src/test/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerTest.java b/jpl-to-log4j-api/src/test/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerTest.java new file mode 100644 index 0000000..cac01a7 --- /dev/null +++ b/jpl-to-log4j-api/src/test/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerTest.java @@ -0,0 +1,149 @@ +/* + * 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.jpl; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.lang.System.Logger; +import java.util.List; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class Log4jSystemLoggerTest { + + public static final String LOGGER_NAME = "Test"; + protected Logger logger; + protected ListAppender eventAppender; + protected ListAppender stringAppender; + + @BeforeEach + public void setUp() throws Exception { + logger = System.getLogger(LOGGER_NAME); + assertThat(logger, instanceOf(Log4jSystemLogger.class)); + eventAppender = ListAppender.getListAppender("TestAppender"); + stringAppender = ListAppender.getListAppender("StringAppender"); + assertNotNull(eventAppender); + assertNotNull(stringAppender); + } + + @AfterEach + public void tearDown() throws Exception { + if (eventAppender != null) { + eventAppender.clear(); + } + if (stringAppender != null) { + stringAppender.clear(); + } + } + + @Test + public void testGetName() throws Exception { + assertThat(logger.getName(), equalTo(LOGGER_NAME)); + } + + @Test + public void testIsLoggable() throws Exception { + assertThat(logger.isLoggable(Logger.Level.ERROR), equalTo(true)); + } + + @Test + public void testLog() throws Exception { + logger.log(Logger.Level.INFO, "Informative message here."); + final List events = eventAppender.getEvents(); + assertThat(events, hasSize(1)); + final LogEvent event = events.get(0); + assertThat(event, instanceOf(Log4jLogEvent.class)); + assertEquals(Level.INFO, event.getLevel()); + assertEquals(LOGGER_NAME, event.getLoggerName()); + assertEquals("Informative message here.", event.getMessage().getFormattedMessage()); + assertEquals(Log4jSystemLogger.class.getName(), event.getLoggerFqcn()); + } + + @Test + public void testParameterizedLogging() { + logger.log(Logger.Level.INFO, "Hello, {0}!", "World"); + final List events = eventAppender.getEvents(); + assertThat(events, hasSize(1)); + final LogEvent event = events.get(0); + assertThat(event, instanceOf(Log4jLogEvent.class)); + assertEquals(Level.INFO, event.getLevel()); + assertEquals(LOGGER_NAME, event.getLoggerName()); + assertEquals("Hello, World!", event.getMessage().getFormattedMessage()); + assertEquals(Log4jSystemLogger.class.getName(), event.getLoggerFqcn()); + } + + @Test + public void testParameterizedLoggingWithThrowable() { + final Throwable throwable = new RuntimeException(); + logger.log(Logger.Level.INFO, "Hello, {0}!", "World", throwable); + final List events = eventAppender.getEvents(); + assertThat(events, hasSize(1)); + final LogEvent event = events.get(0); + assertThat(event, instanceOf(Log4jLogEvent.class)); + assertEquals(Level.INFO, event.getLevel()); + assertEquals(LOGGER_NAME, event.getLoggerName()); + assertEquals("Hello, World!", event.getMessage().getFormattedMessage()); + assertEquals(Log4jSystemLogger.class.getName(), event.getLoggerFqcn()); + assertSame(throwable, event.getThrown()); + } + + @Test + public void testLogWithCallingClass() throws Exception { + final Logger log = System.getLogger("Test.CallerClass"); + log.log(Logger.Level.INFO, "Calling from LoggerTest"); + final List messages = stringAppender.getMessages(); + assertThat(messages, hasSize(1)); + final String message = messages.get(0); + assertEquals(Log4jSystemLoggerTest.class.getName(), message); + } + + @Test + public void testCurlyBraces() { + testMessage("{message}"); + } + + @Test + public void testPercent() { + testMessage("message%s"); + } + + @Test + public void testPercentAndCurlyBraces() { + testMessage("message{%s}"); + } + + private void testMessage(final String string) { + logger.log(Logger.Level.INFO, "Test info " + string); + final List events = eventAppender.getEvents(); + assertThat(events, hasSize(1)); + for (final LogEvent event : events) { + final String message = event.getMessage().getFormattedMessage(); + assertThat(message, equalTo("Test info " + string)); + } + } +} diff --git a/jpl-to-log4j-api/src/test/resources/log4j2-test.xml b/jpl-to-log4j-api/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000..79c22e7 --- /dev/null +++ b/jpl-to-log4j-api/src/test/resources/log4j2-test.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/parent/pom.xml b/parent/pom.xml index 65886d1..01efcec 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -71,6 +71,12 @@ import + + org.hamcrest + hamcrest + ${hamcrest.version} + + org.junit junit-bom diff --git a/pom.xml b/pom.xml index 65a200b..1b8ff72 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ parent + jpl-to-log4j-api log4j-api-to-jul @@ -100,6 +101,12 @@ + + org.apache.logging.log4j + jpl-to-log4j-api + ${project.version} + + org.apache.logging.log4j log4j-api-to-jul