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

Allow setting a custom logging filter on logging handlers #27864

Merged
merged 1 commit into from
Sep 13, 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,7 +1,9 @@
package io.quarkus.deployment.logging;

import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
Expand All @@ -13,6 +15,7 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
Expand All @@ -29,9 +32,13 @@
import org.aesh.command.completer.OptionCompleter;
import org.aesh.command.invocation.CommandInvocation;
import org.aesh.command.option.Option;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
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 All @@ -49,6 +56,7 @@
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ConsoleCommandBuildItem;
import io.quarkus.deployment.builditem.ConsoleFormatterBannerBuildItem;
import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem;
Expand All @@ -65,6 +73,7 @@
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.WebSocketLogHandlerBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
Expand All @@ -79,6 +88,7 @@
import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem;
import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.dev.console.CurrentAppExceptionHighlighter;
import io.quarkus.dev.spi.DevModeType;
import io.quarkus.gizmo.AnnotationCreator;
Expand All @@ -91,11 +101,13 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.logging.LoggingFilter;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.configuration.ConfigInstantiator;
import io.quarkus.runtime.console.ConsoleRuntimeConfig;
import io.quarkus.runtime.logging.CategoryBuildTimeConfig;
import io.quarkus.runtime.logging.CleanupFilterConfig;
import io.quarkus.runtime.logging.DiscoveredLogComponents;
import io.quarkus.runtime.logging.InheritableLevel;
import io.quarkus.runtime.logging.LogBuildTimeConfig;
import io.quarkus.runtime.logging.LogCleanupFilterElement;
Expand All @@ -114,6 +126,13 @@ public final class LoggingResourceProcessor {
"isMinLevelEnabled",
boolean.class, int.class, String.class);

private 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 '"
+ Filter.class.getName() + "' and that are marked as final.";
private static final String[] EMPTY_STRING_ARRAY = new String[0];

@BuildStep
void setupLogFilters(BuildProducer<LogCleanupFilterBuildItem> filters) {
filters.produce(new LogCleanupFilterBuildItem("org.jboss.threads", "JBoss Threads version"));
Expand Down Expand Up @@ -204,6 +223,7 @@ void miscSetup(
@Record(ExecutionTime.RUNTIME_INIT)
LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSetupRecorder recorder, LogConfig log,
LogBuildTimeConfig buildLog,
CombinedIndexBuildItem combinedIndexBuildItem,
LogCategoryMinLevelDefaultsBuildItem categoryMinLevelDefaults,
Optional<WebSocketLogHandlerBuildItem> logStreamHandlerBuildItem,
List<LogHandlerBuildItem> handlerBuildItems,
Expand All @@ -214,7 +234,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe
List<LogStreamBuildItem> logStreamBuildItems,
BuildProducer<ShutdownListenerBuildItem> shutdownListenerBuildItemBuildProducer,
LaunchModeBuildItem launchModeBuildItem,
List<LogCleanupFilterBuildItem> logCleanupFilters) {
List<LogCleanupFilterBuildItem> logCleanupFilters,
BuildProducer<ReflectiveClassBuildItem> reflectiveClassBuildItemBuildProducer) {
if (!launchModeBuildItem.isAuxiliaryApplication()
|| launchModeBuildItem.getAuxiliaryDevModeType().orElse(null) == DevModeType.TEST_ONLY) {
final List<RuntimeValue<Optional<Handler>>> handlers = handlerBuildItems.stream()
Expand Down Expand Up @@ -245,13 +266,22 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe
.map(LogFileFormatBuildItem::getFormatterValue).collect(Collectors.toList());
context.registerSubstitution(InheritableLevel.ActualLevel.class, String.class, InheritableLevel.Substitution.class);
context.registerSubstitution(InheritableLevel.Inherited.class, String.class, InheritableLevel.Substitution.class);

DiscoveredLogComponents discoveredLogComponents = discoverLogComponents(combinedIndexBuildItem.getIndex());
if (!discoveredLogComponents.getNameToFilterClass().isEmpty()) {
reflectiveClassBuildItemBuildProducer.produce(new ReflectiveClassBuildItem(true, false, false,
discoveredLogComponents.getNameToFilterClass().values().toArray(
EMPTY_STRING_ARRAY)));
}

shutdownListenerBuildItemBuildProducer.produce(new ShutdownListenerBuildItem(
recorder.initializeLogging(log, buildLog, categoryMinLevelDefaults.content, alwaysEnableLogStream,
recorder.initializeLogging(log, buildLog, discoveredLogComponents,
categoryMinLevelDefaults.content, alwaysEnableLogStream,
devUiLogHandler, handlers, namedHandlers,
consoleFormatItems.stream().map(LogConsoleFormatBuildItem::getFormatterValue)
.collect(Collectors.toList()),
possibleFileFormatters,
possibleSupplier, launchModeBuildItem.getLaunchMode())));
possibleSupplier, launchModeBuildItem.getLaunchMode(), true)));
LogConfig logConfig = new LogConfig();
ConfigInstantiator.handleObject(logConfig);
for (LogCleanupFilterBuildItem i : logCleanupFilters) {
Expand All @@ -276,6 +306,61 @@ public void run() {
return new LoggingSetupBuildItem();
}

private DiscoveredLogComponents discoverLogComponents(IndexView index) {
Collection<AnnotationInstance> loggingFilterInstances = index.getAnnotations(LOGGING_FILTER);
DiscoveredLogComponents result = new DiscoveredLogComponents();

Map<String, String> filtersMap = new HashMap<>();
for (AnnotationInstance instance : loggingFilterInstances) {
AnnotationTarget target = instance.target();
if (target.kind() != AnnotationTarget.Kind.CLASS) {
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()))) {
boolean hasFilterInterface = false;
List<DotName> ifaces = currentClassInfo.interfaceNames();
for (DotName iface : ifaces) {
if (FILTER.equals(iface)) {
hasFilterInterface = true;
break;
}
}
if (hasFilterInterface) {
isFilterImpl = true;
break;
}
currentClassInfo = index.getClassByName(currentClassInfo.superName());
}
if (!isFilterImpl) {
throw new RuntimeException(
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.");
}
filtersMap.put(filterName, classInfo.name().toString());
}
if (!filtersMap.isEmpty()) {
result.setNameToFilterClass(filtersMap);
}

return result;
}

@BuildStep(onlyIfNot = IsNormal.class)
@Produce(TestSetupBuildItem.class)
@Produce(LogConsoleFormatBuildItem.class)
Expand Down
23 changes: 23 additions & 0 deletions core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.logging;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 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}.
geoand marked this conversation as resolved.
Show resolved Hide resolved
*/

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface LoggingFilter {

/**
* Name with which the filter is referred to in configuration
*/
String name();
geoand marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public class ConsoleConfig {
@ConfigItem(defaultValue = "0")
int darken;

/**
* The name of the filter to link to the console handler.
*/
@ConfigItem
Optional<String> filter;

/**
* Console async logging config
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.runtime.logging;

import java.util.Collections;
import java.util.Map;

public class DiscoveredLogComponents {

private Map<String, String> nameToFilterClass = Collections.emptyMap();

public Map<String, String> getNameToFilterClass() {
return nameToFilterClass;
}

public void setNameToFilterClass(Map<String, String> nameToFilterClass) {
this.nameToFilterClass = nameToFilterClass;
}

public static DiscoveredLogComponents ofEmpty() {
return new DiscoveredLogComponents();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class FileConfig {
@ConfigItem(defaultValue = DEFAULT_LOG_FILE_NAME)
File path;

/**
* The name of the filter to link to the file handler.
*/
@ConfigItem
Optional<String> filter;

/**
* File async logging config
*/
Expand Down
Loading