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 support for Socket Logging Handler with basic ECS format logging #43232

Merged
merged 1 commit into from
Nov 8, 2024
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
@@ -0,0 +1,36 @@
package io.quarkus.deployment.builditem;

import java.util.Optional;
import java.util.logging.Formatter;

import org.wildfly.common.Assert;

import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.runtime.RuntimeValue;

/**
* The socket format build item. Producing this item will cause the logging subsystem to disregard its
* socket logging formatting configuration and use the formatter provided instead. If multiple formatters
* are enabled at runtime, a warning message is printed and only one is used.
*/
public final class LogSocketFormatBuildItem extends MultiBuildItem {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this break if we have more than one socket handler configured for some reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean that we should be able to work with "named" handlers? To allow writing logs to 2 different socket appenders? (Logstash and Splunk for instance)

I must admit I don't know. This is just a copy of LogFileFormatBuildItem for instance

private final RuntimeValue<Optional<Formatter>> formatterValue;

/**
* Construct a new instance.
*
* @param formatterValue the optional formatter runtime value to use (must not be {@code null})
*/
public LogSocketFormatBuildItem(final RuntimeValue<Optional<Formatter>> formatterValue) {
this.formatterValue = Assert.checkNotNullParam("formatterValue", formatterValue);
}

/**
* Get the formatter value.
*
* @return the formatter value
*/
public RuntimeValue<Optional<Formatter>> getFormatterValue() {
return formatterValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import io.quarkus.deployment.builditem.LogConsoleFormatBuildItem;
import io.quarkus.deployment.builditem.LogFileFormatBuildItem;
import io.quarkus.deployment.builditem.LogHandlerBuildItem;
import io.quarkus.deployment.builditem.LogSocketFormatBuildItem;
import io.quarkus.deployment.builditem.LogSyslogFormatBuildItem;
import io.quarkus.deployment.builditem.NamedLogHandlersBuildItem;
import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
Expand Down Expand Up @@ -249,6 +250,7 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(
final List<LogConsoleFormatBuildItem> consoleFormatItems,
final List<LogFileFormatBuildItem> fileFormatItems,
final List<LogSyslogFormatBuildItem> syslogFormatItems,
final List<LogSocketFormatBuildItem> socketFormatItems,
final Optional<ConsoleFormatterBannerBuildItem> possibleBannerBuildItem,
final List<LogStreamBuildItem> logStreamBuildItems,
final BuildProducer<ShutdownListenerBuildItem> shutdownListenerBuildItemBuildProducer,
Expand Down Expand Up @@ -290,6 +292,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(
.map(LogFileFormatBuildItem::getFormatterValue).collect(Collectors.toList());
List<RuntimeValue<Optional<Formatter>>> possibleSyslogFormatters = syslogFormatItems.stream()
.map(LogSyslogFormatBuildItem::getFormatterValue).collect(Collectors.toList());
List<RuntimeValue<Optional<Formatter>>> possibleSocketFormatters = socketFormatItems.stream()
.map(LogSocketFormatBuildItem::getFormatterValue).collect(Collectors.toList());

context.registerSubstitution(InheritableLevel.ActualLevel.class, String.class, InheritableLevel.Substitution.class);
context.registerSubstitution(InheritableLevel.Inherited.class, String.class, InheritableLevel.Substitution.class);
Expand All @@ -308,6 +312,7 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(
categoryMinLevelDefaults.content, alwaysEnableLogStream,
streamingDevUiLogHandler, handlers, namedHandlers,
possibleConsoleFormatters, possibleFileFormatters, possibleSyslogFormatters,
possibleSocketFormatters,
possibleSupplier, launchModeBuildItem.getLaunchMode(), true)));

List<LogCleanupFilterElement> additionalLogCleanupFilters = new ArrayList<>(logCleanupFilters.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.logging.Level;

import org.jboss.logmanager.handlers.AsyncHandler.OverflowAction;
import org.jboss.logmanager.handlers.SocketHandler;
import org.jboss.logmanager.handlers.SyslogHandler.Facility;
import org.jboss.logmanager.handlers.SyslogHandler.Protocol;
import org.jboss.logmanager.handlers.SyslogHandler.SyslogType;
Expand Down Expand Up @@ -77,6 +78,14 @@ public interface LogRuntimeConfig {
@ConfigDocSection
SyslogConfig syslog();

/**
* Socket logging.
* <p>
* Logging to a socket is also supported but not enabled by default.
*/
@ConfigDocSection
SocketConfig socket();

/**
* Logging categories.
* <p>
Expand Down Expand Up @@ -115,6 +124,15 @@ public interface LogRuntimeConfig {
@ConfigDocSection
Map<String, SyslogConfig> syslogHandlers();

/**
* Socket handlers.
* <p>
* The named socket handlers configured here can be linked to one or more categories.
*/
@WithName("handler.socket")
@ConfigDocSection
Map<String, SocketConfig> socketHandlers();

/**
* Log cleanup filters - internal use.
*/
Expand Down Expand Up @@ -393,6 +411,59 @@ interface SyslogConfig {
AsyncConfig async();
}

interface SocketConfig {

/**
* If socket logging should be enabled
*/
@WithDefault("false")
boolean enable();

/**
*
* The IP address and port of the server receiving the logs
*/
@WithDefault("localhost:4560")
@WithConverter(InetSocketAddressConverter.class)
InetSocketAddress endpoint();

/**
* Sets the protocol used to connect to the syslog server
*/
@WithDefault("tcp")
SocketHandler.Protocol protocol();

/**
* Enables or disables blocking when attempting to reconnect a
* {@link Protocol#TCP
* TCP} or {@link Protocol#SSL_TCP SSL TCP} protocol
*/
@WithDefault("false")
boolean blockOnReconnect();

/**
* The log message format
*/
@WithDefault("%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n")
String format();

/**
* The log level specifying, which message levels will be logged by socket logger
*/
@WithDefault("ALL")
Level level();

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

/**
* Socket async logging config
*/
AsyncConfig async();
}

interface CleanupFilterConfig {
/**
* The message prefix to match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.jboss.logmanager.handlers.FileHandler;
import org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler;
import org.jboss.logmanager.handlers.SizeRotatingFileHandler;
import org.jboss.logmanager.handlers.SocketHandler;
import org.jboss.logmanager.handlers.SyslogHandler;

import io.quarkus.bootstrap.logging.InitialConfigurator;
Expand All @@ -63,6 +64,7 @@
import io.quarkus.runtime.logging.LogRuntimeConfig.CleanupFilterConfig;
import io.quarkus.runtime.logging.LogRuntimeConfig.ConsoleConfig;
import io.quarkus.runtime.logging.LogRuntimeConfig.FileConfig;
import io.quarkus.runtime.logging.LogRuntimeConfig.SocketConfig;
import io.quarkus.runtime.shutdown.ShutdownListener;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
Expand Down Expand Up @@ -116,7 +118,7 @@ public String getName() {
new LoggingSetupRecorder(new RuntimeValue<>(consoleRuntimeConfig)).initializeLogging(logRuntimeConfig,
logBuildTimeConfig,
DiscoveredLogComponents.ofEmpty(), emptyMap(), false, null, emptyList(), emptyList(), emptyList(), emptyList(),
emptyList(), banner, LaunchMode.DEVELOPMENT, false);
emptyList(), emptyList(), banner, LaunchMode.DEVELOPMENT, false);
}

public ShutdownListener initializeLogging(
Expand All @@ -131,6 +133,7 @@ public ShutdownListener initializeLogging(
final List<RuntimeValue<Optional<Formatter>>> possibleConsoleFormatters,
final List<RuntimeValue<Optional<Formatter>>> possibleFileFormatters,
final List<RuntimeValue<Optional<Formatter>>> possibleSyslogFormatters,
final List<RuntimeValue<Optional<Formatter>>> possibleSocketFormatters,
final RuntimeValue<Optional<Supplier<String>>> possibleBannerSupplier,
final LaunchMode launchMode,
final boolean includeFilters) {
Expand Down Expand Up @@ -211,6 +214,14 @@ public void close() throws SecurityException {
}
}

if (config.socket().enable()) {
final Handler socketHandler = configureSocketHandler(config.socket(), errorManager, cleanupFiler,
namedFilters, possibleSocketFormatters, includeFilters);
if (socketHandler != null) {
handlers.add(socketHandler);
}
}

if ((launchMode.isDevOrTest() || enableWebStream)
&& streamingDevUiConsoleHandler != null
&& streamingDevUiConsoleHandler.getValue().isPresent()) {
Expand All @@ -229,7 +240,7 @@ public void close() throws SecurityException {

Map<String, Handler> namedHandlers = shouldCreateNamedHandlers(config, additionalNamedHandlers)
? createNamedHandlers(config, consoleRuntimeConfig.getValue(), additionalNamedHandlers,
possibleConsoleFormatters, possibleFileFormatters, possibleSyslogFormatters,
possibleConsoleFormatters, possibleFileFormatters, possibleSyslogFormatters, possibleSocketFormatters,
errorManager, cleanupFiler, namedFilters, launchMode,
shutdownNotifier, includeFilters)
: emptyMap();
Expand Down Expand Up @@ -328,7 +339,7 @@ public static void initializeBuildTimeLogging(
}

Map<String, Handler> namedHandlers = createNamedHandlers(config, consoleConfig, emptyList(),
emptyList(), emptyList(), emptyList(), errorManager, logCleanupFilter,
emptyList(), emptyList(), emptyList(), emptyList(), errorManager, logCleanupFilter,
emptyMap(), launchMode, dummy, false);

setUpCategoryLoggers(buildConfig, categoryDefaultMinLevels, categories, logContext, errorManager, namedHandlers);
Expand Down Expand Up @@ -388,6 +399,7 @@ private static Map<String, Handler> createNamedHandlers(
List<RuntimeValue<Optional<Formatter>>> possibleConsoleFormatters,
List<RuntimeValue<Optional<Formatter>>> possibleFileFormatters,
List<RuntimeValue<Optional<Formatter>>> possibleSyslogFormatters,
List<RuntimeValue<Optional<Formatter>>> possibleSocketFormatters,
ErrorManager errorManager, LogCleanupFilter cleanupFilter,
Map<String, Filter> namedFilters, LaunchMode launchMode,
ShutdownNotifier shutdownHandler, boolean includeFilters) {
Expand Down Expand Up @@ -422,6 +434,17 @@ private static Map<String, Handler> createNamedHandlers(
addToNamedHandlers(namedHandlers, syslogHandler, sysLogConfigEntry.getKey());
}
}
for (Entry<String, SocketConfig> socketConfigEntry : config.socketHandlers().entrySet()) {
SocketConfig namedSocketConfig = socketConfigEntry.getValue();
if (!namedSocketConfig.enable()) {
continue;
}
final Handler socketHandler = configureSocketHandler(namedSocketConfig, errorManager, cleanupFilter,
namedFilters, possibleSocketFormatters, includeFilters);
if (socketHandler != null) {
addToNamedHandlers(namedHandlers, socketHandler, socketConfigEntry.getKey());
}
}

Map<String, Handler> additionalNamedHandlersMap;
if (additionalNamedHandlers.isEmpty()) {
Expand Down Expand Up @@ -770,6 +793,53 @@ private static Handler configureSyslogHandler(final LogRuntimeConfig.SyslogConfi
}
}

private static Handler configureSocketHandler(final LogRuntimeConfig.SocketConfig config,
final ErrorManager errorManager,
final LogCleanupFilter logCleanupFilter,
final Map<String, Filter> namedFilters,
final List<RuntimeValue<Optional<Formatter>>> possibleSocketFormatters,
final boolean includeFilters) {
try {
final SocketHandler handler = new SocketHandler(config.endpoint().getHostString(), config.endpoint().getPort());
handler.setProtocol(config.protocol());
handler.setBlockOnReconnect(config.blockOnReconnect());
handler.setLevel(config.level());

Formatter formatter = null;
boolean formatterWarning = false;
for (RuntimeValue<Optional<Formatter>> value : possibleSocketFormatters) {
if (formatter != null) {
formatterWarning = true;
}
final Optional<Formatter> val = value.getValue();
if (val.isPresent()) {
formatter = val.get();
}
}
if (formatter == null) {
formatter = new PatternFormatter(config.format());
}
handler.setFormatter(formatter);

handler.setErrorManager(errorManager);
handler.setFilter(logCleanupFilter);
applyFilter(includeFilters, errorManager, logCleanupFilter, config.filter(), namedFilters, handler);

if (formatterWarning) {
handler.getErrorManager().error("Multiple socket formatters were activated", null,
ErrorManager.GENERIC_FAILURE);
}

if (config.async().enable()) {
return createAsyncHandler(config.async(), config.level(), handler);
}
return handler;
} catch (IOException e) {
errorManager.error("Failed to create socket handler", e, ErrorManager.OPEN_FAILURE);
return null;
}
}

private static AsyncHandler createAsyncHandler(LogRuntimeConfig.AsyncConfig asyncConfig, Level level, Handler handler) {
final AsyncHandler asyncHandler = new AsyncHandler(asyncConfig.queueLength());
asyncHandler.setOverflowAction(asyncConfig.overflow());
Expand Down
Loading
Loading