Skip to content

Commit

Permalink
add socket log appender, enable json format for it
Browse files Browse the repository at this point in the history
  • Loading branch information
troosan committed Sep 11, 2024
1 parent 155093d commit 110a2e4
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 2 deletions.
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 {
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 @@ -72,6 +72,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 @@ -244,6 +245,7 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe
List<LogConsoleFormatBuildItem> consoleFormatItems,
List<LogFileFormatBuildItem> fileFormatItems,
List<LogSyslogFormatBuildItem> syslogFormatItems,
List<LogSocketFormatBuildItem> socketFormatItems,
Optional<ConsoleFormatterBannerBuildItem> possibleBannerBuildItem,
List<LogStreamBuildItem> logStreamBuildItems,
BuildProducer<ShutdownListenerBuildItem> shutdownListenerBuildItemBuildProducer,
Expand Down Expand Up @@ -285,6 +287,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe
.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 @@ -303,6 +307,7 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe
categoryMinLevelDefaults.content, alwaysEnableLogStream,
streamingDevUiLogHandler, handlers, namedHandlers,
possibleConsoleFormatters, possibleFileFormatters, possibleSyslogFormatters,
possibleSocketFormatters,
possibleSupplier, launchModeBuildItem.getLaunchMode(), true)));
LogConfig logConfig = new LogConfig();
ConfigInstantiator.handleObject(logConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ public final class LogConfig {
@ConfigDocSection
public SyslogConfig syslog;

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

/**
* Logging categories.
* <p>
Expand Down Expand Up @@ -97,6 +105,15 @@ public final class LogConfig {
@ConfigDocSection
public Map<String, SyslogConfig> syslogHandlers;

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

/**
* Log cleanup filters - internal use.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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 Down Expand Up @@ -86,6 +87,7 @@ public static void handleFailedStart(RuntimeValue<Optional<Supplier<String>>> ba
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(),
Collections.emptyList(), banner, LaunchMode.DEVELOPMENT, false);
}

Expand All @@ -99,6 +101,7 @@ public ShutdownListener initializeLogging(LogConfig config, LogBuildTimeConfig b
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,
LaunchMode launchMode,
boolean includeFilters) {
Expand Down Expand Up @@ -183,6 +186,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 @@ -201,7 +212,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)
: Collections.emptyMap();
Expand Down Expand Up @@ -312,7 +323,8 @@ public static void initializeBuildTimeLogging(LogConfig config, LogBuildTimeConf
}

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

for (Map.Entry<String, CategoryConfig> entry : categories.entrySet()) {
Expand Down Expand Up @@ -399,6 +411,7 @@ private static Map<String, Handler> createNamedHandlers(LogConfig config, Consol
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 @@ -433,6 +446,17 @@ private static Map<String, Handler> createNamedHandlers(LogConfig config, Consol
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 @@ -742,6 +766,53 @@ private static Handler configureSyslogHandler(final SyslogConfig config, final E
}
}

private static Handler configureSocketHandler(final 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(AsyncConfig asyncConfig, Level level, Handler handler) {
final AsyncHandler asyncHandler = new AsyncHandler(asyncConfig.queueLength);
asyncHandler.setOverflowAction(asyncConfig.overflow);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.quarkus.runtime.logging;

import java.net.InetSocketAddress;
import java.util.Optional;
import java.util.logging.Level;

import org.jboss.logmanager.handlers.SocketHandler.Protocol;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class SocketConfig {

/**
* If socket logging should be enabled
*/
@ConfigItem
boolean enable;

/**
*
* The IP address and port of the server receiving the logs
*/
@ConfigItem(defaultValue = "localhost:4560")
InetSocketAddress endpoint;

/**
* Sets the protocol used to connect to the syslog server
*/
@ConfigItem(defaultValue = "tcp")
Protocol protocol;

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

/**
* The log message format
*/
@ConfigItem(defaultValue = "%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
*/
@ConfigItem(defaultValue = "ALL")
Level level;

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

/**
* Syslog async logging config
*/
AsyncConfig async;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.LogConsoleFormatBuildItem;
import io.quarkus.deployment.builditem.LogFileFormatBuildItem;
import io.quarkus.deployment.builditem.LogSocketFormatBuildItem;
import io.quarkus.deployment.builditem.LogSyslogFormatBuildItem;
import io.quarkus.logging.json.runtime.JsonLogConfig;
import io.quarkus.logging.json.runtime.LoggingJsonRecorder;
Expand All @@ -28,4 +29,10 @@ public LogFileFormatBuildItem setUpFileFormatter(LoggingJsonRecorder recorder, J
public LogSyslogFormatBuildItem setUpSyslogFormatter(LoggingJsonRecorder recorder, JsonLogConfig config) {
return new LogSyslogFormatBuildItem(recorder.initializeSyslogJsonLogging(config));
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
public LogSocketFormatBuildItem setUpSocketFormatter(LoggingJsonRecorder recorder, JsonLogConfig config) {
return new LogSocketFormatBuildItem(recorder.initializeSocketJsonLogging(config));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.quarkus.logging.json;

import static io.quarkus.logging.json.SocketJsonFormatterDefaultConfigTest.getJsonFormatter;
import static org.assertj.core.api.Assertions.assertThat;

import java.time.ZoneId;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import org.assertj.core.api.Assertions;
import org.jboss.logmanager.formatters.StructuredFormatter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.logging.json.runtime.AdditionalFieldConfig;
import io.quarkus.logging.json.runtime.JsonFormatter;
import io.quarkus.test.QuarkusUnitTest;

public class SocketJsonFormatterCustomConfigTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar.addClasses(SocketJsonFormatterDefaultConfigTest.class))
.withConfigurationResource("application-socket-json-formatter-custom.properties");

@Test
public void jsonFormatterCustomConfigurationTest() {
JsonFormatter jsonFormatter = getJsonFormatter();
assertThat(jsonFormatter.isPrettyPrint()).isTrue();
assertThat(jsonFormatter.getDateTimeFormatter().toString())
.isEqualTo("Value(DayOfMonth)' 'Text(MonthOfYear,SHORT)' 'Value(Year,4,19,EXCEEDS_PAD)");
assertThat(jsonFormatter.getDateTimeFormatter().getZone()).isEqualTo(ZoneId.of("UTC+05:00"));
assertThat(jsonFormatter.getExceptionOutputType())
.isEqualTo(StructuredFormatter.ExceptionOutputType.DETAILED_AND_FORMATTED);
assertThat(jsonFormatter.getRecordDelimiter()).isEqualTo("\n;");
assertThat(jsonFormatter.isPrintDetails()).isTrue();
assertThat(jsonFormatter.getExcludedKeys()).containsExactly("timestamp", "sequence");
assertThat(jsonFormatter.getAdditionalFields().size()).isEqualTo(2);
assertThat(jsonFormatter.getAdditionalFields().containsKey("foo")).isTrue();
assertThat(jsonFormatter.getAdditionalFields().get("foo").type).isEqualTo(AdditionalFieldConfig.Type.INT);
assertThat(jsonFormatter.getAdditionalFields().get("foo").value).isEqualTo("42");
assertThat(jsonFormatter.getAdditionalFields().containsKey("bar")).isTrue();
assertThat(jsonFormatter.getAdditionalFields().get("bar").type).isEqualTo(AdditionalFieldConfig.Type.STRING);
assertThat(jsonFormatter.getAdditionalFields().get("bar").value).isEqualTo("baz");
}

@Test
public void jsonFormatterOutputTest() throws Exception {
JsonFormatter jsonFormatter = getJsonFormatter();
String line = jsonFormatter.format(new LogRecord(Level.INFO, "Hello, World!"));

JsonNode node = new ObjectMapper().readTree(line);
// "level" has been renamed to HEY
Assertions.assertThat(node.has("level")).isFalse();
Assertions.assertThat(node.has("HEY")).isTrue();
Assertions.assertThat(node.get("HEY").asText()).isEqualTo("INFO");

// excluded fields
Assertions.assertThat(node.has("timestamp")).isFalse();
Assertions.assertThat(node.has("sequence")).isFalse();

// additional fields
Assertions.assertThat(node.has("foo")).isTrue();
Assertions.assertThat(node.get("foo").asInt()).isEqualTo(42);
Assertions.assertThat(node.has("bar")).isTrue();
Assertions.assertThat(node.get("bar").asText()).isEqualTo("baz");
Assertions.assertThat(node.get("message").asText()).isEqualTo("Hello, World!");
}
}
Loading

0 comments on commit 110a2e4

Please sign in to comment.