From 90ab3a2685969ec5d5f70f1bed08c8a665418eb2 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 14 Sep 2022 11:18:07 +0300 Subject: [PATCH] Enable the registration of @LoggingFilter classes via CDI This allows for users to configure their filters via the usual Quarkus configuration approach. Follows up on: #27864 (cherry picked from commit a03cda3e01d8b8d7ce0735a5ec98bf2c0c6e356a) --- .../logging/LoggingResourceProcessor.java | 19 ++----- .../io/quarkus/logging/LoggingFilter.java | 2 - .../runtime/logging/LogFilterFactory.java | 57 +++++++++++++++++++ .../runtime/logging/LoggingSetupRecorder.java | 5 +- docs/src/main/asciidoc/logging.adoc | 20 ++++++- .../LoggingBeanSupportProcessor.java | 16 ++++++ .../runtime/logging/ArcLogFilterFactory.java | 29 ++++++++++ ...o.quarkus.runtime.logging.LogFilterFactory | 1 + .../minlevel/set/filter/TestFilter.java | 12 +++- .../src/main/resources/application.properties | 1 + 10 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java create mode 100644 extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index e1fa11ba794b9..acaf966faf7b0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.deployment.logging; -import java.lang.reflect.Modifier; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; @@ -38,7 +37,6 @@ import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; import org.jboss.logmanager.EmbeddedConfigurator; import org.jboss.logmanager.LogManager; @@ -112,6 +110,7 @@ import io.quarkus.runtime.logging.LogBuildTimeConfig; import io.quarkus.runtime.logging.LogCleanupFilterElement; import io.quarkus.runtime.logging.LogConfig; +import io.quarkus.runtime.logging.LogFilterFactory; import io.quarkus.runtime.logging.LogMetricsHandlerRecorder; import io.quarkus.runtime.logging.LoggingSetupRecorder; @@ -126,7 +125,7 @@ public final class LoggingResourceProcessor { "isMinLevelEnabled", boolean.class, int.class, String.class); - private static final DotName LOGGING_FILTER = DotName.createSimple(LoggingFilter.class.getName()); + public static final DotName LOGGING_FILTER = DotName.createSimple(LoggingFilter.class.getName()); private static final DotName FILTER = DotName.createSimple(Filter.class.getName()); private static final String ILLEGAL_LOGGING_FILTER_USE_MESSAGE = "'@" + LoggingFilter.class.getName() + "' can only be used on classes that implement '" @@ -235,7 +234,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe BuildProducer shutdownListenerBuildItemBuildProducer, LaunchModeBuildItem launchModeBuildItem, List logCleanupFilters, - BuildProducer reflectiveClassBuildItemBuildProducer) { + BuildProducer reflectiveClassBuildItemBuildProducer, + BuildProducer serviceProviderBuildItemBuildProducer) { if (!launchModeBuildItem.isAuxiliaryApplication() || launchModeBuildItem.getAuxiliaryDevModeType().orElse(null) == DevModeType.TEST_ONLY) { final List>> handlers = handlerBuildItems.stream() @@ -272,6 +272,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe reflectiveClassBuildItemBuildProducer.produce(new ReflectiveClassBuildItem(true, false, false, discoveredLogComponents.getNameToFilterClass().values().toArray( EMPTY_STRING_ARRAY))); + serviceProviderBuildItemBuildProducer + .produce(ServiceProviderBuildItem.allProvidersFromClassPath(LogFilterFactory.class.getName())); } shutdownListenerBuildItemBuildProducer.produce(new ShutdownListenerBuildItem( @@ -317,10 +319,6 @@ private DiscoveredLogComponents discoverLogComponents(IndexView index) { throw new IllegalStateException("Unimplemented mode of use of '" + LoggingFilter.class.getName() + "'"); } ClassInfo classInfo = target.asClass(); - if (!Modifier.isFinal(classInfo.flags())) { - throw new RuntimeException( - ILLEGAL_LOGGING_FILTER_USE_MESSAGE + " Offending class is '" + classInfo.name() + "'"); - } boolean isFilterImpl = false; ClassInfo currentClassInfo = classInfo; while ((currentClassInfo != null) && (!JandexUtil.DOTNAME_OBJECT.equals(currentClassInfo.name()))) { @@ -343,11 +341,6 @@ private DiscoveredLogComponents discoverLogComponents(IndexView index) { ILLEGAL_LOGGING_FILTER_USE_MESSAGE + " Offending class is '" + classInfo.name() + "'"); } - MethodInfo ctor = classInfo.method(""); - if ((ctor == null) || (ctor.typeParameters().size() > 0)) { - throw new RuntimeException("Classes annotated with '" + LoggingFilter.class.getName() - + "' must have a no-args constructor. Offending class is '" + classInfo.name() + "'"); - } String filterName = instance.value("name").asString(); if (filtersMap.containsKey(filterName)) { throw new RuntimeException("Filter '" + filterName + "' was defined multiple times."); diff --git a/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java b/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java index bf2adb61f2b4f..4f07aaa0afe46 100644 --- a/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java +++ b/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java @@ -8,8 +8,6 @@ /** * Makes the filter class known to Quarkus by the specified name. * The filter can then be configured for a handler (like the logging handler using {@code quarkus.log.console.filter}). - * - * This class must ONLY be placed on implementations of {@link java.util.logging.Filter} that are marked as {@code final}. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java new file mode 100644 index 0000000000000..d94d67907423d --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java @@ -0,0 +1,57 @@ +package io.quarkus.runtime.logging; + +import java.util.ServiceLoader; +import java.util.logging.Filter; + +/** + * Factory that allows for the creation of {@link Filter} classes annotated with {@link io.quarkus.logging.LoggingFilter}. + * Implementations of this class are loaded via the {@link ServiceLoader} and the implementation selected is the one + * with the lowest value returned from the {@code priority} method. + */ +public interface LogFilterFactory { + + int MIN_PRIORITY = Integer.MAX_VALUE; + int DEFAULT_PRIORITY = 0; + + Filter create(String className) throws Exception; + + default int priority() { + return DEFAULT_PRIORITY; + } + + static LogFilterFactory load() { + LogFilterFactory result = null; + ServiceLoader load = ServiceLoader.load(LogFilterFactory.class); + for (LogFilterFactory next : load) { + if (result == null) { + result = next; + } else { + if (next.priority() < result.priority()) { + result = next; + } + } + } + if (result == null) { + result = new ReflectionLogFilterFactory(); + } + return result; + } + + /** + * The default implementation used when no other implementation is found. + * This simply calls the class' no-arg constructor (and fails if one does not exist). + */ + class ReflectionLogFilterFactory implements LogFilterFactory { + + @Override + public Filter create(String className) throws Exception { + return (Filter) Class.forName(className, true, Thread.currentThread().getContextClassLoader()) + .getConstructor().newInstance(); + } + + @Override + public int priority() { + return LogFilterFactory.MIN_PRIORITY; + } + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index f10f9c3dab731..0a3f419a390b8 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -254,13 +254,12 @@ private static Map createNamedFilters(DiscoveredLogComponents di } Map nameToFilter = new HashMap<>(); + LogFilterFactory logFilterFactory = LogFilterFactory.load(); discoveredLogComponents.getNameToFilterClass().forEach(new BiConsumer<>() { @Override public void accept(String name, String className) { try { - nameToFilter.put(name, - (Filter) Class.forName(className, true, Thread.currentThread().getContextClassLoader()) - .getConstructor().newInstance()); + nameToFilter.put(name, logFilterFactory.create(className)); } catch (Exception e) { throw new RuntimeException("Unable to create instance of Logging Filter '" + className + "'"); } diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index f946ff505ef15..3a5cb4a1cec3f 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -325,7 +325,8 @@ These filters are registered by placing the `@io.quarkus.logging.LoggingFilter` Finally, the filter is attached using the `filter` configuration property of the appropriate handler. -Let's say for example that we wanted to filter out logging records that contained the word `test` from the console logs. +Let's say for example that we wanted to filter out logging records that contained a part of text from the console logs. +The text itself is part of the application configuration and is not hardcoded. We could write a filter like so: [source,java] @@ -336,13 +337,28 @@ import java.util.logging.LogRecord; @LoggingFilter(name = "my-filter") public final class TestFilter implements Filter { + + private final String part; + + public TestFilter(@ConfigProperty(name = "my-filter.part") String part) { + this.part = part; + } + @Override public boolean isLoggable(LogRecord record) { - return !record.getMessage().contains("test"); + return !record.getMessage().contains(part); } } ---- +and configure it in the usual Quarkus way (for example using `application.properties`) like so: + +[source,properties] +---- +my-filter.part=TEST +---- + + And we would register this filter to the console handler like so: [source, properties] diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java new file mode 100644 index 0000000000000..71bdfdb1b8160 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java @@ -0,0 +1,16 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.logging.LoggingResourceProcessor; + +public class LoggingBeanSupportProcessor { + + @BuildStep + public void discoveredComponents(BuildProducer beanDefiningAnnotationProducer) { + beanDefiningAnnotationProducer.produce(new BeanDefiningAnnotationBuildItem( + LoggingResourceProcessor.LOGGING_FILTER, BuiltinScope.SINGLETON.getName(), false)); + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java new file mode 100644 index 0000000000000..f653c300c3ada --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java @@ -0,0 +1,29 @@ +package io.quarkus.arc.runtime.logging; + +import java.util.logging.Filter; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.runtime.logging.LogFilterFactory; + +/** + * Creates the implementation of the class by getting a bean from Arc. + * This class is loaded automatically by the {@link java.util.ServiceLoader}. + */ +public class ArcLogFilterFactory implements LogFilterFactory { + + @Override + public Filter create(String className) throws Exception { + InstanceHandle instance = Arc.container().instance(Class.forName(className, true, Thread.currentThread() + .getContextClassLoader())); + if (!instance.isAvailable()) { + throw new IllegalStateException("Improper integration of '" + LogFilterFactory.class.getName() + "' detected"); + } + return (Filter) instance.get(); + } + + @Override + public int priority() { + return LogFilterFactory.DEFAULT_PRIORITY - 100; + } +} diff --git a/extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory b/extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory new file mode 100644 index 0000000000000..d114e9f1e84d4 --- /dev/null +++ b/extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory @@ -0,0 +1 @@ +io.quarkus.arc.runtime.logging.ArcLogFilterFactory diff --git a/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java b/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java index 30163df431240..964a97ad1850e 100644 --- a/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java +++ b/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java @@ -3,13 +3,21 @@ import java.util.logging.Filter; import java.util.logging.LogRecord; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import io.quarkus.logging.LoggingFilter; @LoggingFilter(name = "my-filter") -public final class TestFilter implements Filter { +public class TestFilter implements Filter { + + private final String part; + + public TestFilter(@ConfigProperty(name = "my-filter.part") String part) { + this.part = part; + } @Override public boolean isLoggable(LogRecord record) { - return !record.getMessage().contains("TEST"); + return !record.getMessage().contains(part); } } diff --git a/integration-tests/logging-min-level-set/src/main/resources/application.properties b/integration-tests/logging-min-level-set/src/main/resources/application.properties index e33158f666c73..89dddefeda7c1 100644 --- a/integration-tests/logging-min-level-set/src/main/resources/application.properties +++ b/integration-tests/logging-min-level-set/src/main/resources/application.properties @@ -4,3 +4,4 @@ quarkus.log.category."io.quarkus.it.logging.minlevel.set.below".min-level=TRACE quarkus.log.category."io.quarkus.it.logging.minlevel.set.below.child".min-level=inherit quarkus.log.category."io.quarkus.it.logging.minlevel.set.promote".min-level=ERROR quarkus.log.console.filter=my-filter +my-filter.part=TEST