Skip to content

Commit

Permalink
Support named handlers #156
Browse files Browse the repository at this point in the history
  • Loading branch information
treivize committed Oct 6, 2023
1 parent bd7c8ba commit 327ce54
Show file tree
Hide file tree
Showing 8 changed files with 758 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.LogHandlerBuildItem;
import io.quarkus.deployment.builditem.NamedLogHandlersBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;

class LoggingSplunkProcessor {
Expand All @@ -29,6 +30,12 @@ LogHandlerBuildItem logHandler(SplunkLogHandlerRecorder recorder, SplunkConfig c
return new LogHandlerBuildItem(recorder.initializeHandler(config));
}

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
NamedLogHandlersBuildItem logNamedHandlers(SplunkLogHandlerRecorder recorder, SplunkConfig config) {
return new NamedLogHandlersBuildItem(recorder.initializeHandlers(config));
}

@BuildStep
ExtensionSslNativeSupportBuildItem enableSSL() {
// Enable SSL support by default
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright (c) 2023 Amadeus s.a.s.
Contributor(s): Kevin Viet, Romain Quinio, Yohann Puyhaubert (Amadeus s.a.s.)
*/
package io.quarkiverse.logging.splunk;

import io.quarkus.test.QuarkusUnitTest;
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.mockserver.model.JsonBody.json;

class LoggingSplunkNamedHandlerConfigTest extends AbstractMockServerTest {

@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.withConfigurationResource("application-splunk-logging-named-handler.properties")
.withConfigurationResource("mock-server.properties")
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));

static final Logger logger = Logger.getLogger(LoggingSplunkNamedHandlerConfigTest.class);

static final Logger monitoringLogger = Logger.getLogger("monitoring");

@Test
void indexWithDefaultLoggerAndNamedLogger() {
logger.warn("hello splunk");
monitoringLogger.info("{\"key\":\"value\"}");
awaitMockServer();
httpServer.verify(requestToJsonEndpoint()
.withBody(json("{ index: 'mylogindex'}"))
.withHeader("Authorization", "Splunk 12345678-1234-1234-1234-1234567890AB"));
httpServer.verify(requestToJsonEndpoint()
.withBody(json("{ index: 'mystatsindex'}"))
.withHeader("Authorization", "Splunk 12345678-0000-0000-0000-1234567890AB"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
quarkus.log.handler.splunk.token=12345678-1234-1234-1234-1234567890AB
quarkus.log.handler.splunk.level=WARN
quarkus.log.handler.splunk.format=%s%e
quarkus.log.handler.splunk.metadata-index=mylogindex
quarkus.log.handler.splunk."MONITORING".url=http://localhost:8088
quarkus.log.handler.splunk."MONITORING".token=12345678-0000-0000-0000-1234567890AB
quarkus.log.handler.splunk."MONITORING".level=INFO
quarkus.log.handler.splunk."MONITORING".metadata-index=mystatsindex
quarkus.log.category."monitoring".handlers=MONITORING
quarkus.log.category."monitoring".use-parent-handlers=false
412 changes: 412 additions & 0 deletions docs/modules/ROOT/pages/includes/quarkus-log-handler-splunk.adoc

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions integration-test/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
quarkus.log.handler.splunk.url=https://localhost:8099
quarkus.log.handler.splunk.index=main
quarkus.log.handler.splunk.metadata-index=main
quarkus.log.handler.splunk.token=29fe2838-cab6-4d17-a392-37b7b8f41f75
quarkus.log.handler.splunk.batch-interval=1s
quarkus.log.handler.splunk.disable-certificate-validation=true
quarkus.log.console.enable=true
quarkus.log.handler.splunk.serialization=nested
quarkus.log.handler.splunk.serialization=nested
211 changes: 8 additions & 203 deletions runtime/src/main/java/io/quarkiverse/logging/splunk/SplunkConfig.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
/*
Copyright (c) 2021 Amadeus s.a.s.
Contributor(s): Kevin Viet, Romain Quinio (Amadeus s.a.s.)
Copyright (c) 2023 Amadeus s.a.s.
Contributor(s): Kevin Viet, Romain Quinio, Yohann Puyhaubert (Amadeus s.a.s.)
*/
package io.quarkiverse.logging.splunk;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
Expand All @@ -21,205 +17,14 @@
public class SplunkConfig {

/**
* Determine whether to enable the handler
* Configuration for Splunk HEC logging for the root level.
*/
@ConfigItem(defaultValue = "true")
public boolean enabled;

/**
* The splunk handler log level. By default, it is no more strict than the root handler level.
*/
@ConfigItem(defaultValue = "ALL")
public Level level;

/**
* Splunk HEC endpoint base url.
* <p>
* With raw events, the endpoint targeted is /services/collector/raw.
* With flat or nested JSON events, the endpoint targeted is /services/collector/event/1.0.
*/
@ConfigItem(defaultValue = "https://localhost:8088/")
public String url;

/**
* Disable TLS certificate validation with HEC endpoint
*/
@ConfigItem(defaultValue = "false")
public boolean disableCertificateValidation;

/**
* The application token to authenticate with HEC, the token is mandatory if the extension is enabled
* https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector#HEC_token
*/
@ConfigItem
public Optional<String> token;

/**
* The strategy to send events to HEC.
* <p>
* In sequential mode, there is only one HTTP connection to HEC and the order of events is preserved, but performance is
* lower.
* In parallel mode, event batches are sent asynchronously over multiple HTTP connections, and events with the same
* timestamp
* (that has 1 millisecond resolution) may be indexed out of order by Splunk.
*/
@ConfigItem(defaultValue = "sequential")
public SendMode sendMode;

/**
* A GUID to identify an HEC client and guarantee isolation at HEC level in case of slow clients.
* https://docs.splunk.com/Documentation/Splunk/latest/Data/AboutHECIDXAck#About_channels_and_sending_data
*/
@ConfigItem
public Optional<String> channel;

/**
* Batching delay before sending a group of events.
* If 0, the events are sent immediately.
*/
@ConfigItem(defaultValue = "10s")
public Duration batchInterval;

/**
* Maximum number of events in a batch. By default 10, if 0 no batching.
*/
@ConfigItem(defaultValue = "10")
public long batchSizeCount;

/**
* Maximum total size in bytes of events in a batch. By default 10KB, if 0 no batching.
*/
@ConfigItem(defaultValue = "10")
public long batchSizeBytes;

/**
* Maximum number of retries in case of I/O exceptions with HEC connection.
*/
@ConfigItem(defaultValue = "0")
public long maxRetries;

/**
* The log format, defining which metadata are inlined inside the log main payload.
* <p>
* Specific metadata (hostname, category, thread name, ...), as well as MDC key/value map, can also be sent in a structured
* way.
*/
@ConfigItem(defaultValue = "%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n")
public String format;

/**
* Whether to send the thrown exception message as a structured metadata of the log event (as opposed to %e in a formatted
* message, it does not include the exception name or stacktrace).
* Only applicable to 'nested' serialization.
*/
@ConfigItem(defaultValue = "false")
public boolean includeException;

/**
* Whether to send the logger name as a structured metadata of the log event (equivalent of %c in a formatted message).
* Only applicable to 'nested' serialization.
*/
@ConfigItem(defaultValue = "false")
public boolean includeLoggerName;

/**
* Whether to send the thread name as a structured metadata of the log event (equivalent of %t in a formatted message).
* Only applicable to 'nested' serialization.
*/
@ConfigItem(defaultValue = "false")
public boolean includeThreadName;

/**
* Overrides the host name metadata value.
*/
@ConfigItem(defaultValueDocumentation = "The equivalent of %h in a formatted message")
public Optional<String> metadataHost;

/**
* The source value to assign to the event data. For example, if you're sending data from an app you're developing,
* you could set this key to the name of the app.
* https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector#Event_metadata
*/
@ConfigItem
public Optional<String> metadataSource;

/**
* The optional format of the events, to enable some parsing on Splunk side.
* https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector#Event_metadata
* <p>
* A given source type may have indexed fields extraction enabled, which is the case of the built-in _json used for nested
* serialization.
*/
@ConfigItem(defaultValueDocumentation = "_json for nested serialization, not set otherwise")
public Optional<String> metadataSourceType;

/**
* The optional name of the index by which the event data is to be stored. If set, it must be within the
* list of allowed indexes of the token (if it has the indexes parameter set).
* https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector#Event_metadata
*/
@ConfigItem
public Optional<String> metadataIndex;

/**
* Optional static key/value pairs to populate the "fields" key of event metadata. This isn't
* applicable to raw serialization.
* https://docs.splunk.com/Documentation/Splunk/latest/Data/FormateventsforHTTPEventCollector#Event_metadata
*/
@ConfigItem
public Map<String, String> metadataFields = new HashMap<>();

/**
* The name of the key used to convey the severity / log level in the metadata fields.
* Only applicable to 'flat' serialization.
* With 'nested' serialization, there is already a 'severity' field.
*/
@ConfigItem(defaultValue = "severity")
public String metadataSeverityFieldName;

/**
* Determines whether the events are sent in raw mode. In case the raw event (i.e. the actual log message)
* is not a JSON object you need to explicitly set a source type or Splunk will reject the event (the
* default source type, _json, assumes that the incoming event can be parsed as JSON)
*
* @deprecated Use {@link #serialization}
*/
@Deprecated(forRemoval = true)
@ConfigItem(defaultValue = "false")
public boolean raw;

/**
* The format of the payload.
* <ul>
* <li>With raw serialization, the log message is sent 'as is' in the HTTP body. Metadata can only be common to a whole
* batch and are sent via HTTP parameters.
* <li>With nested serialization, the log message is sent into a 'message' field of a JSON structure which also contains
* dynamic metadata.
* <li>With flat serialization, the log message is sent into the root 'event' field. Dynamic metadata is sent via the
* 'fields' root object.
* </ul>
*/
@ConfigItem(defaultValue = "nested")
public SerializationFormat serialization;

/**
* AsyncHandler config
* <p>
* This is independent of the SendMode, i.e. whether the HTTP client is async or not.
*/
AsyncConfig async;

@ConfigItem(name = ConfigItem.PARENT)
public SplunkHandlerConfig config;
/**
* Mirrors com.splunk.logging.HttpEventCollectorSender.SendMode
* Map of all the custom/named handlers configuration using Splunk implementation.
*/
public enum SendMode {
SEQUENTIAL,
PARALLEL
}
@ConfigItem(name = ConfigItem.PARENT)
public Map<String, SplunkHandlerConfig> namedHandlers;

public enum SerializationFormat {
RAW,
NESTED,
FLAT
}
}
Loading

0 comments on commit 327ce54

Please sign in to comment.