From f0ff8d3813f7a4a3bb267a4aa1e6686e0323cc10 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 11 Jul 2022 15:15:42 +0300 Subject: [PATCH] Add the ability to add custom log handlers to the root logger Resolves: #17480 Co-authored-by: David M. Lloyd --- .../runtime/logging/LogCleanupFilter.java | 6 +- .../io/quarkus/runtime/logging/LogConfig.java | 9 +++ .../runtime/logging/LoggingSetupRecorder.java | 67 ++++++++++++++++--- docs/src/main/asciidoc/logging.adoc | 27 ++++++++ 4 files changed, 98 insertions(+), 11 deletions(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java index 927956fd7ccce..56801b342ba1b 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java @@ -1,7 +1,7 @@ package io.quarkus.runtime.logging; +import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.logging.Filter; import java.util.logging.Level; @@ -11,9 +11,9 @@ public class LogCleanupFilter implements Filter { - private Map filterElements = new HashMap<>(); + final Map filterElements = new HashMap<>(); - public LogCleanupFilter(List filterElements) { + public LogCleanupFilter(Collection filterElements) { for (LogCleanupFilterElement element : filterElements) { this.filterElements.put(element.getLoggerName(), element); } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java index a03c7eacb375f..95029f7c82be2 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogConfig.java @@ -1,6 +1,8 @@ package io.quarkus.runtime.logging; +import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.logging.Level; import io.quarkus.runtime.annotations.ConfigDocSection; @@ -101,4 +103,11 @@ public final class LogConfig { @ConfigItem(name = "filter") @ConfigDocSection public Map filters; + + /** + * The names of additional handlers to link to the root category. + * These handlers are defined in consoleHandlers, fileHandlers or syslogHandlers. + */ + @ConfigItem + Optional> handlers; } 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 8d741ef7f4b69..b0e0659fc2c20 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 @@ -6,6 +6,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -125,7 +126,8 @@ public void accept(String loggerName, CleanupFilterConfig config) { handler.setFilter(cleanupFiler); } - final ArrayList handlers = new ArrayList<>(3 + additionalHandlers.size()); + final ArrayList handlers = new ArrayList<>( + 3 + additionalHandlers.size() + (config.handlers.isPresent() ? config.handlers.get().size() : 0)); if (config.console.enable) { final Handler consoleHandler = configureConsoleHandler(config.console, consoleRuntimeConfig.getValue(), @@ -180,11 +182,11 @@ public void close() throws SecurityException { handlers.add(handler); } + Map namedHandlers = shouldCreateNamedHandlers(config, additionalNamedHandlers) + ? createNamedHandlers(config, consoleRuntimeConfig.getValue(), additionalNamedHandlers, + possibleConsoleFormatters, possibleFileFormatters, errorManager, cleanupFiler, launchMode) + : Collections.emptyMap(); if (!categories.isEmpty()) { - Map namedHandlers = createNamedHandlers(config, consoleRuntimeConfig.getValue(), - possibleConsoleFormatters, possibleFileFormatters, errorManager, - cleanupFiler, launchMode); - Map additionalNamedHandlersMap; if (additionalNamedHandlers.isEmpty()) { additionalNamedHandlersMap = Collections.emptyMap(); @@ -227,7 +229,7 @@ public void accept(String categoryName, CategoryConfig config) { handlers.add(handler); } } - + addNamedHandlersToRootHandlers(config.handlers, namedHandlers, handlers, errorManager); InitialConfigurator.DELAYED_HANDLER.setAutoFlush(false); InitialConfigurator.DELAYED_HANDLER.setHandlers(handlers.toArray(EmbeddedConfigurator.NO_HANDLERS)); } @@ -263,6 +265,7 @@ public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConf } Map namedHandlers = createNamedHandlers(config, consoleConfig, Collections.emptyList(), + Collections.emptyList(), possibleFileFormatters, errorManager, logCleanupFilter, launchMode); for (Map.Entry entry : categories.entrySet()) { @@ -292,10 +295,22 @@ public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConf addNamedHandlersToCategory(categoryConfig, namedHandlers, categoryLogger, errorManager); } } + addNamedHandlersToRootHandlers(config.handlers, namedHandlers, handlers, errorManager); InitialConfigurator.DELAYED_HANDLER.setAutoFlush(false); InitialConfigurator.DELAYED_HANDLER.setBuildTimeHandlers(handlers.toArray(EmbeddedConfigurator.NO_HANDLERS)); } + private boolean shouldCreateNamedHandlers(LogConfig logConfig, + List>> additionalNamedHandlers) { + if (!logConfig.categories.isEmpty()) { + return true; + } + if (logConfig.handlers.isPresent()) { + return !logConfig.handlers.get().isEmpty(); + } + return !additionalNamedHandlers.isEmpty(); + } + public static Level getLogLevel(String categoryName, Map categories, Function levelExtractor, Map categoryDefaults, Level rootMinLevel) { while (true) { @@ -329,6 +344,7 @@ public static InheritableLevel getLogLevelNoInheritance(String categoryName, } private static Map createNamedHandlers(LogConfig config, ConsoleRuntimeConfig consoleRuntimeConfig, + List>> additionalNamedHandlers, List>> possibleConsoleFormatters, List>> possibleFileFormatters, ErrorManager errorManager, @@ -363,6 +379,21 @@ private static Map createNamedHandlers(LogConfig config, Consol addToNamedHandlers(namedHandlers, syslogHandler, sysLogConfigEntry.getKey()); } } + + Map additionalNamedHandlersMap; + if (additionalNamedHandlers.isEmpty()) { + additionalNamedHandlersMap = Collections.emptyMap(); + } else { + additionalNamedHandlersMap = new HashMap<>(); + for (RuntimeValue> runtimeValue : additionalNamedHandlers) { + runtimeValue.getValue().forEach( + new AdditionalNamedHandlersConsumer(additionalNamedHandlersMap, errorManager, + cleanupFilter.filterElements.values())); + } + } + + namedHandlers.putAll(additionalNamedHandlersMap); + return namedHandlers; } @@ -400,6 +431,26 @@ public void run() { } } + private static void addNamedHandlersToRootHandlers(Optional> handlerNames, Map namedHandlers, + ArrayList effectiveHandlers, + ErrorManager errorManager) { + if (handlerNames.isEmpty()) { + return; + } + if (handlerNames.get().isEmpty()) { + return; + } + for (String namedHandler : handlerNames.get()) { + Handler handler = namedHandlers.get(namedHandler); + if (handler != null) { + effectiveHandlers.add(handler); + } else { + errorManager.error(String.format("Handler with name '%s' is linked to a category but not configured.", + namedHandler), null, ErrorManager.GENERIC_FAILURE); + } + } + } + public void initializeLoggingForImageBuild() { if (ImageInfo.inImageBuildtimeCode()) { final ConsoleHandler handler = new ConsoleHandler(new PatternFormatter( @@ -609,10 +660,10 @@ public void accept(String name, CategoryConfig categoryConfig) { private static class AdditionalNamedHandlersConsumer implements BiConsumer { private final Map additionalNamedHandlersMap; private final ErrorManager errorManager; - private final List filterElements; + private final Collection filterElements; public AdditionalNamedHandlersConsumer(Map additionalNamedHandlersMap, ErrorManager errorManager, - List filterElements) { + Collection filterElements) { this.additionalNamedHandlersMap = additionalNamedHandlersMap; this.errorManager = errorManager; this.filterElements = filterElements; diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index b8b2b0039cac9..044e213479fd0 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -331,6 +331,22 @@ It is disabled by default. For details of its configuration options, see link:#quarkus-log-logging-log-config_quarkus.log.syslog-syslog-logging[the Syslog Logging configuration reference]. +[NOTE] +==== +Although the root logger's handlers are usually configured directly via `quarkus.log.console`, `quarkus.log.file` and `quarkus.log.syslog`, it +can nonetheless have additional named handlers attached to it using the `quarkus.log.handlers` property. + +[source,properties] +---- +quarkus.http.auth.policy.user-policy.roles-allowed=user +quarkus.http.auth.permission.roles.paths=/api/* +quarkus.http.auth.permission.roles.policy=user-policy + +quarkus.http.auth.permission.public.paths=/api/noauth/* +quarkus.http.auth.permission.public.policy=permit +---- +==== + == Examples .Console DEBUG Logging except for Quarkus logs (INFO), No color, Shortened Time, Shortened Category Prefixes @@ -380,6 +396,17 @@ quarkus.log.category."io.quarkus.category".level=INFO quarkus.log.category."io.quarkus.category".handlers=STRUCTURED_LOGGING,STRUCTURED_LOGGING_FILE ---- +[#root-category-named-handlers-example] +.Named handlers attached to the root logger +[source, properties] +---- +# configure a named file handler that sends the output to 'quarkus.log' +quarkus.log.handler.file.CONSOLE_MIRROR.enable=true +quarkus.log.handler.file.CONSOLE_MIRROR.path=quarkus.log +# attach the handler to the root logger +quarkus.log.handlers=CONSOLE_MIRROR +---- + == Centralized Log Management If you want to send your logs to a centralized tool like Graylog, Logstash or Fluentd, you can follow the xref:centralized-log-management.adoc[Centralized log management guide].