Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable the registration of @LoggingFilter classes via CDI #27917

Merged
merged 1 commit into from
Sep 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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