Skip to content

Commit

Permalink
Enable the registration of @LoggingFilter classes via CDI
Browse files Browse the repository at this point in the history
This allows for users to configure their filters
via the usual Quarkus configuration approach.

Follows up on: quarkusio#27864

(cherry picked from commit a03cda3)
  • Loading branch information
geoand authored and gsmet committed Sep 20, 2022
1 parent 3106008 commit 90ab3a2
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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 '"
Expand Down Expand Up @@ -235,7 +234,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe
BuildProducer<ShutdownListenerBuildItem> shutdownListenerBuildItemBuildProducer,
LaunchModeBuildItem launchModeBuildItem,
List<LogCleanupFilterBuildItem> logCleanupFilters,
BuildProducer<ReflectiveClassBuildItem> reflectiveClassBuildItemBuildProducer) {
BuildProducer<ReflectiveClassBuildItem> reflectiveClassBuildItemBuildProducer,
BuildProducer<ServiceProviderBuildItem> serviceProviderBuildItemBuildProducer) {
if (!launchModeBuildItem.isAuxiliaryApplication()
|| launchModeBuildItem.getAuxiliaryDevModeType().orElse(null) == DevModeType.TEST_ONLY) {
final List<RuntimeValue<Optional<Handler>>> handlers = handlerBuildItems.stream()
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()))) {
Expand All @@ -343,11 +341,6 @@ private DiscoveredLogComponents discoverLogComponents(IndexView index) {
ILLEGAL_LOGGING_FILTER_USE_MESSAGE + " Offending class is '" + classInfo.name() + "'");
}

MethodInfo ctor = classInfo.method("<init>");
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.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LogFilterFactory> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,12 @@ private static Map<String, Filter> createNamedFilters(DiscoveredLogComponents di
}

Map<String, Filter> 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 + "'");
}
Expand Down
20 changes: 18 additions & 2 deletions docs/src/main/asciidoc/logging.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -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<BeanDefiningAnnotationBuildItem> beanDefiningAnnotationProducer) {
beanDefiningAnnotationProducer.produce(new BeanDefiningAnnotationBuildItem(
LoggingResourceProcessor.LOGGING_FILTER, BuiltinScope.SINGLETON.getName(), false));
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.arc.runtime.logging.ArcLogFilterFactory
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 90ab3a2

Please sign in to comment.