From a50e292a954792031daa2988fb53871a4a2e1c97 Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Fri, 27 Nov 2020 14:09:10 +0100 Subject: [PATCH] Support injectable loggers - resolves #13510 --- docs/src/main/asciidoc/logging.adoc | 26 ++++++ .../quarkus/arc/deployment/ArcProcessor.java | 6 ++ .../arc/test/log/InjectedLoggerTest.java | 82 +++++++++++++++++++ .../java/io/quarkus/arc/log/LoggerName.java | 48 +++++++++++ .../quarkus/arc/runtime/LoggerProducer.java | 49 +++++++++++ 5 files changed, 211 insertions(+) create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/log/InjectedLoggerTest.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/log/LoggerName.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/LoggerProducer.java diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index 2c37091b3d040..335fdcf18c171 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -51,6 +51,32 @@ public class ExampleResource { NOTE: If you use JBoss Logging but one of your libraries uses a different logging API, you may need to configure a link:#logging-adapters[Logging Adapter]. +You can also inject a configured `org.jboss.logging.Logger` instance in your beans and resource classes. + +[source, java] +---- +import org.jboss.logging.Logger; + +@ApplicationScoped +class SimpleBean { + + @Inject + Logger log; <1> + + @LoggerName("foo") + Logger fooLog; <2> + + public void ping() { + log.info("Simple!"); + fooLog.info("Goes to _foo_ logger!"); + } +} +---- +<1> The FQCN of the declaring class is used as a logger name, i.e. `org.jboss.logging.Logger.getLogger(SimpleBean.class)` will be used. +<2> In this case, the name _foo_ is used as a logger name, i.e. `org.jboss.logging.Logger.getLogger("foo")` will be used. + +NOTE: The logger instances are cached internally. Therefore, a logger injected e.g. into a `@RequestScoped` bean is shared for all bean instances to avoid possible performance penalty associated with logger instantiation. + === What about Apache Log4j ? link:https://logging.apache.org/log4j/2.x/[Log4j] is a logging implementation: it contains a logging backend and a logging facade. diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 50920b7e73249..19641428ba1c1 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -54,6 +54,7 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.LaunchModeProducer; import io.quarkus.arc.runtime.LifecycleEventRunner; +import io.quarkus.arc.runtime.LoggerProducer; import io.quarkus.bootstrap.BootstrapDebug; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; @@ -509,6 +510,11 @@ AdditionalBeanBuildItem launchMode() { return new AdditionalBeanBuildItem(LaunchModeProducer.class); } + @BuildStep + AdditionalBeanBuildItem loggerProducer() { + return new AdditionalBeanBuildItem(LoggerProducer.class); + } + @BuildStep CustomScopeAnnotationsBuildItem exposeCustomScopeNames(List contextBuildItems) { Set names = new HashSet<>(); diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/log/InjectedLoggerTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/log/InjectedLoggerTest.java new file mode 100644 index 0000000000000..24e0bcb3a9039 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/log/InjectedLoggerTest.java @@ -0,0 +1,82 @@ +package io.quarkus.arc.test.log; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; + +import org.jboss.logging.Logger; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.log.LoggerName; +import io.quarkus.test.QuarkusUnitTest; + +public class InjectedLoggerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SimpleBean.class)); + + @Inject + SimpleBean simpleBean; + + @Inject + AnotherSimpleBean anotherSimpleBean; + + @Test + public void testInjectedLogger() { + assertEquals(SimpleBean.class.getName(), simpleBean.getLog().getName()); + assertEquals("shared", simpleBean.getSharedLog().getName()); + assertEquals(AnotherSimpleBean.class.getName(), anotherSimpleBean.getLog().getName()); + assertEquals(simpleBean.getSharedLog(), anotherSimpleBean.getSharedLog()); + } + + @ApplicationScoped + static class SimpleBean { + + @Inject + Logger log; + + @LoggerName("shared") + Logger sharedLog; + + public Logger getLog() { + log.info("Someone is here!"); + return log; + } + + public Logger getSharedLog() { + return sharedLog; + } + + } + + @Dependent + static class AnotherSimpleBean { + + private final Logger log; + + @LoggerName("shared") + Logger sharedLog; + + public AnotherSimpleBean(Logger log) { + this.log = log; + } + + public Logger getLog() { + return log; + } + + public Logger getSharedLog() { + sharedLog.info("Yet another someone is here!"); + return sharedLog; + } + + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/log/LoggerName.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/log/LoggerName.java new file mode 100644 index 0000000000000..81644222f3b80 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/log/LoggerName.java @@ -0,0 +1,48 @@ +package io.quarkus.arc.log; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; +import javax.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ FIELD, PARAMETER, METHOD }) +public @interface LoggerName { + + /** + * The logger name must not be empty. + * + * @return the logger name + */ + @Nonbinding + String value(); + + /** + * Supports inline instantiation of this qualifier. + */ + public static final class Literal extends AnnotationLiteral implements LoggerName { + + private static final long serialVersionUID = 1L; + + private final String value; + + public Literal(String value) { + this.value = value; + } + + @Override + public String value() { + return value; + } + + } + +} \ No newline at end of file diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/LoggerProducer.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/LoggerProducer.java new file mode 100644 index 0000000000000..5a8b929bc3e95 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/LoggerProducer.java @@ -0,0 +1,49 @@ +package io.quarkus.arc.runtime; + +import java.lang.annotation.Annotation; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.annotation.PreDestroy; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Singleton; + +import org.jboss.logging.Logger; + +import io.quarkus.arc.log.LoggerName; + +@Singleton +public class LoggerProducer { + + private final ConcurrentMap loggers = new ConcurrentHashMap<>(); + + @Dependent + @Produces + Logger getSimpleLogger(InjectionPoint injectionPoint) { + return loggers.computeIfAbsent(injectionPoint.getMember().getDeclaringClass().getName(), Logger::getLogger); + } + + @LoggerName("") + @Dependent + @Produces + Logger getLoggerWithCustomName(InjectionPoint injectionPoint) { + String name = null; + for (Annotation qualifier : injectionPoint.getQualifiers()) { + if (qualifier.annotationType().equals(LoggerName.class)) { + name = ((LoggerName) qualifier).value(); + } + } + if (name == null || name.isEmpty()) { + throw new IllegalStateException("Unable to derive the logger name at " + injectionPoint); + } + return loggers.computeIfAbsent(name, Logger::getLogger); + } + + @PreDestroy + void destroy() { + loggers.clear(); + } + +}