Skip to content

Commit

Permalink
Merge pull request quarkusio#27917 from geoand/quarkusio#27864-followup
Browse files Browse the repository at this point in the history
Enable the registration of @LoggingFilter classes via CDI
  • Loading branch information
geoand authored Sep 14, 2022
2 parents 7a932c6 + a03cda3 commit 9a5bc36
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 9a5bc36

Please sign in to comment.