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

Add the ability to add custom log handlers to the root logger #26657

Merged
merged 1 commit into from Jul 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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;
Expand All @@ -11,9 +11,9 @@

public class LogCleanupFilter implements Filter {

private Map<String, LogCleanupFilterElement> filterElements = new HashMap<>();
final Map<String, LogCleanupFilterElement> filterElements = new HashMap<>();

public LogCleanupFilter(List<LogCleanupFilterElement> filterElements) {
public LogCleanupFilter(Collection<LogCleanupFilterElement> filterElements) {
for (LogCleanupFilterElement element : filterElements) {
this.filterElements.put(element.getLoggerName(), element);
}
Expand Down
@@ -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;
Expand Down Expand Up @@ -101,4 +103,11 @@ public final class LogConfig {
@ConfigItem(name = "filter")
@ConfigDocSection
public Map<String, CleanupFilterConfig> filters;

/**
* The names of additional handlers to link to the root category.
* These handlers are defined in consoleHandlers, fileHandlers or syslogHandlers.
*/
@ConfigItem
Optional<List<String>> handlers;
}
Expand Up @@ -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;
Expand Down Expand Up @@ -125,7 +126,8 @@ public void accept(String loggerName, CleanupFilterConfig config) {
handler.setFilter(cleanupFiler);
}

final ArrayList<Handler> handlers = new ArrayList<>(3 + additionalHandlers.size());
final ArrayList<Handler> 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(),
Expand Down Expand Up @@ -180,11 +182,11 @@ public void close() throws SecurityException {
handlers.add(handler);
}

Map<String, Handler> namedHandlers = shouldCreateNamedHandlers(config, additionalNamedHandlers)
? createNamedHandlers(config, consoleRuntimeConfig.getValue(), additionalNamedHandlers,
possibleConsoleFormatters, possibleFileFormatters, errorManager, cleanupFiler, launchMode)
: Collections.emptyMap();
if (!categories.isEmpty()) {
Map<String, Handler> namedHandlers = createNamedHandlers(config, consoleRuntimeConfig.getValue(),
possibleConsoleFormatters, possibleFileFormatters, errorManager,
cleanupFiler, launchMode);

Map<String, Handler> additionalNamedHandlersMap;
if (additionalNamedHandlers.isEmpty()) {
additionalNamedHandlersMap = Collections.emptyMap();
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -263,6 +265,7 @@ public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConf
}

Map<String, Handler> namedHandlers = createNamedHandlers(config, consoleConfig, Collections.emptyList(),
Collections.emptyList(),
possibleFileFormatters, errorManager, logCleanupFilter, launchMode);

for (Map.Entry<String, CategoryConfig> entry : categories.entrySet()) {
Expand Down Expand Up @@ -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<RuntimeValue<Map<String, Handler>>> additionalNamedHandlers) {
if (!logConfig.categories.isEmpty()) {
return true;
}
if (logConfig.handlers.isPresent()) {
return !logConfig.handlers.get().isEmpty();
}
return !additionalNamedHandlers.isEmpty();
}

public static <T> Level getLogLevel(String categoryName, Map<String, T> categories,
Function<T, InheritableLevel> levelExtractor, Map<String, InheritableLevel> categoryDefaults, Level rootMinLevel) {
while (true) {
Expand Down Expand Up @@ -329,6 +344,7 @@ public static <T> InheritableLevel getLogLevelNoInheritance(String categoryName,
}

private static Map<String, Handler> createNamedHandlers(LogConfig config, ConsoleRuntimeConfig consoleRuntimeConfig,
List<RuntimeValue<Map<String, Handler>>> additionalNamedHandlers,
List<RuntimeValue<Optional<Formatter>>> possibleConsoleFormatters,
List<RuntimeValue<Optional<Formatter>>> possibleFileFormatters,
ErrorManager errorManager,
Expand Down Expand Up @@ -363,6 +379,21 @@ private static Map<String, Handler> createNamedHandlers(LogConfig config, Consol
addToNamedHandlers(namedHandlers, syslogHandler, sysLogConfigEntry.getKey());
}
}

Map<String, Handler> additionalNamedHandlersMap;
if (additionalNamedHandlers.isEmpty()) {
additionalNamedHandlersMap = Collections.emptyMap();
} else {
additionalNamedHandlersMap = new HashMap<>();
for (RuntimeValue<Map<String, Handler>> runtimeValue : additionalNamedHandlers) {
runtimeValue.getValue().forEach(
new AdditionalNamedHandlersConsumer(additionalNamedHandlersMap, errorManager,
cleanupFilter.filterElements.values()));
}
}

namedHandlers.putAll(additionalNamedHandlersMap);

return namedHandlers;
}

Expand Down Expand Up @@ -400,6 +431,26 @@ public void run() {
}
}

private static void addNamedHandlersToRootHandlers(Optional<List<String>> handlerNames, Map<String, Handler> namedHandlers,
ArrayList<Handler> 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(
Expand Down Expand Up @@ -609,10 +660,10 @@ public void accept(String name, CategoryConfig categoryConfig) {
private static class AdditionalNamedHandlersConsumer implements BiConsumer<String, Handler> {
private final Map<String, Handler> additionalNamedHandlersMap;
private final ErrorManager errorManager;
private final List<LogCleanupFilterElement> filterElements;
private final Collection<LogCleanupFilterElement> filterElements;

public AdditionalNamedHandlersConsumer(Map<String, Handler> additionalNamedHandlersMap, ErrorManager errorManager,
List<LogCleanupFilterElement> filterElements) {
Collection<LogCleanupFilterElement> filterElements) {
this.additionalNamedHandlersMap = additionalNamedHandlersMap;
this.errorManager = errorManager;
this.filterElements = filterElements;
Expand Down
27 changes: 27 additions & 0 deletions docs/src/main/asciidoc/logging.adoc
Expand Up @@ -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
Expand Down Expand Up @@ -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].
Expand Down