Refine structured logging
Refine structured logging to support `Environment`, `ApplicationPid` and `ElasticCommonSchemaService` injection. With these updates we are able to remove the `ApplicationMetadata` class and simplify the parameters passed to the layout/encoder classes. Closes gh-41491
This commit is contained in:
parent
50dbaec2d0
commit
de3b14f2b4
|
@ -440,7 +440,7 @@ Handling authenticated request
|
|||
== Structured Logging
|
||||
|
||||
Structured logging is a technique where the log output is written in a well-defined, often machine-readable format.
|
||||
Spring Boot supports structured logging and has support for the following formats out of the box:
|
||||
Spring Boot supports structured logging and has support for the following JSON formats out of the box:
|
||||
|
||||
* xref:#features.logging.structured.ecs[Elastic Common Schema (ECS)]
|
||||
* xref:#features.logging.structured.logstash[Logstash]
|
||||
|
@ -474,6 +474,22 @@ A log line looks like this:
|
|||
This format also adds every key value pair contained in the MDC to the JSON object.
|
||||
You can also use the https://www.slf4j.org/manual.html#fluent[SLF4J fluent logging API] to add key value pairs to the logged JSON object with the https://www.slf4j.org/apidocs/org/slf4j/spi/LoggingEventBuilder.html#addKeyValue(java.lang.String,java.lang.Object)[addKeyValue] method.
|
||||
|
||||
The `service` values can be customized using `logging.structured.ecs.service` properties:
|
||||
|
||||
[configprops,yaml]
|
||||
----
|
||||
logging:
|
||||
structured:
|
||||
ecs:
|
||||
service:
|
||||
name: MyService
|
||||
version: 1.0
|
||||
environment: Production
|
||||
node-name: Primary
|
||||
----
|
||||
|
||||
NOTE: configprop:logging.structured.ecs.service.name[] will default to configprop:spring.application.name[] if not specified.
|
||||
|
||||
|
||||
|
||||
[[features.logging.structured.logstash]]
|
||||
|
|
|
@ -24,9 +24,11 @@ import org.apache.logging.log4j.message.Message;
|
|||
import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
||||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
|
@ -36,23 +38,19 @@ import org.springframework.util.ObjectUtils;
|
|||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ElasticCommonSchemaStructuredLogFormatter implements StructuredLogFormatter<LogEvent> {
|
||||
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
|
||||
|
||||
private final JsonWriter<LogEvent> writer;
|
||||
|
||||
ElasticCommonSchemaStructuredLogFormatter(ApplicationMetadata metadata) {
|
||||
this.writer = JsonWriter.<LogEvent>of((members) -> logEventJson(metadata, members)).withNewLineAtEnd();
|
||||
ElasticCommonSchemaStructuredLogFormatter(ApplicationPid pid, ElasticCommonSchemaService service) {
|
||||
super((members) -> jsonMembers(pid, service, members));
|
||||
}
|
||||
|
||||
private void logEventJson(ApplicationMetadata metadata, JsonWriter.Members<LogEvent> members) {
|
||||
members.add("@timestamp", LogEvent::getInstant).as(this::asTimestamp);
|
||||
private static void jsonMembers(ApplicationPid pid, ElasticCommonSchemaService service,
|
||||
JsonWriter.Members<LogEvent> members) {
|
||||
members.add("@timestamp", LogEvent::getInstant).as(ElasticCommonSchemaStructuredLogFormatter::asTimestamp);
|
||||
members.add("log.level", LogEvent::getLevel).as(Level::name);
|
||||
members.add("process.pid", metadata::pid).whenNotNull();
|
||||
members.add("process.pid", pid).when(ApplicationPid::isAvailable).as(ApplicationPid::toLong);
|
||||
members.add("process.thread.name", LogEvent::getThreadName);
|
||||
members.add("service.name", metadata::name).whenHasLength();
|
||||
members.add("service.version", metadata::version).whenHasLength();
|
||||
members.add("service.environment", metadata::environment).whenHasLength();
|
||||
members.add("service.node.name", metadata::nodeName).whenHasLength();
|
||||
service.jsonMembers(members);
|
||||
members.add("log.logger", LogEvent::getLoggerName);
|
||||
members.add("message", LogEvent::getMessage).as(Message::getFormattedMessage);
|
||||
members.add(LogEvent::getContextData)
|
||||
|
@ -68,13 +66,8 @@ class ElasticCommonSchemaStructuredLogFormatter implements StructuredLogFormatte
|
|||
members.add("ecs.version", "8.11");
|
||||
}
|
||||
|
||||
private java.time.Instant asTimestamp(Instant instant) {
|
||||
private static java.time.Instant asTimestamp(Instant instant) {
|
||||
return java.time.Instant.ofEpochMilli(instant.getEpochMillisecond()).plusNanos(instant.getNanoOfMillisecond());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogEvent event) {
|
||||
return this.writer.writeToString(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
|||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
@ -41,16 +42,14 @@ import org.springframework.util.CollectionUtils;
|
|||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LogstashStructuredLogFormatter implements StructuredLogFormatter<LogEvent> {
|
||||
|
||||
private JsonWriter<LogEvent> writer;
|
||||
class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
|
||||
|
||||
LogstashStructuredLogFormatter() {
|
||||
this.writer = JsonWriter.<LogEvent>of(this::logEventJson).withNewLineAtEnd();
|
||||
super(LogstashStructuredLogFormatter::jsonMembers);
|
||||
}
|
||||
|
||||
private void logEventJson(JsonWriter.Members<LogEvent> members) {
|
||||
members.add("@timestamp", LogEvent::getInstant).as(this::asTimestamp);
|
||||
private static void jsonMembers(JsonWriter.Members<LogEvent> members) {
|
||||
members.add("@timestamp", LogEvent::getInstant).as(LogstashStructuredLogFormatter::asTimestamp);
|
||||
members.add("@version", "1");
|
||||
members.add("message", LogEvent::getMessage).as(Message::getFormattedMessage);
|
||||
members.add("logger_name", LogEvent::getLoggerName);
|
||||
|
@ -60,26 +59,29 @@ class LogstashStructuredLogFormatter implements StructuredLogFormatter<LogEvent>
|
|||
members.add(LogEvent::getContextData)
|
||||
.whenNot(ReadOnlyStringMap::isEmpty)
|
||||
.usingPairs((contextData, pairs) -> contextData.forEach(pairs::accept));
|
||||
members.add("tags", LogEvent::getMarker).whenNotNull().as(this::getMarkers).whenNot(CollectionUtils::isEmpty);
|
||||
members.add("tags", LogEvent::getMarker)
|
||||
.whenNotNull()
|
||||
.as(LogstashStructuredLogFormatter::getMarkers)
|
||||
.whenNot(CollectionUtils::isEmpty);
|
||||
members.add("stack_trace", LogEvent::getThrownProxy)
|
||||
.whenNotNull()
|
||||
.as(ThrowableProxy::getExtendedStackTraceAsString);
|
||||
}
|
||||
|
||||
private String asTimestamp(Instant instant) {
|
||||
private static String asTimestamp(Instant instant) {
|
||||
java.time.Instant javaInstant = java.time.Instant.ofEpochMilli(instant.getEpochMillisecond())
|
||||
.plusNanos(instant.getNanoOfMillisecond());
|
||||
OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(javaInstant, ZoneId.systemDefault());
|
||||
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(offsetDateTime);
|
||||
}
|
||||
|
||||
private Set<String> getMarkers(Marker marker) {
|
||||
private static Set<String> getMarkers(Marker marker) {
|
||||
Set<String> result = new TreeSet<>();
|
||||
addMarkers(result, marker);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addMarkers(Set<String> result, Marker marker) {
|
||||
private static void addMarkers(Set<String> result, Marker marker) {
|
||||
result.add(marker.getName());
|
||||
if (marker.hasParents()) {
|
||||
for (Marker parent : marker.getParents()) {
|
||||
|
@ -88,9 +90,4 @@ class LogstashStructuredLogFormatter implements StructuredLogFormatter<LogEvent>
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogEvent event) {
|
||||
return this.writer.writeToString(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,17 +21,21 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
import org.apache.logging.log4j.core.Layout;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.config.Node;
|
||||
import org.apache.logging.log4j.core.config.plugins.Plugin;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginLoggerContext;
|
||||
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -57,6 +61,11 @@ final class StructuredLogLayout extends AbstractStringLayout {
|
|||
return this.formatter.format(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray(LogEvent event) {
|
||||
return this.formatter.formatAsBytes(event, (getCharset() != null) ? getCharset() : StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@PluginBuilderFactory
|
||||
static StructuredLogLayout.Builder newBuilder() {
|
||||
return new StructuredLogLayout.Builder();
|
||||
|
@ -64,27 +73,15 @@ final class StructuredLogLayout extends AbstractStringLayout {
|
|||
|
||||
static final class Builder implements org.apache.logging.log4j.core.util.Builder<StructuredLogLayout> {
|
||||
|
||||
@PluginLoggerContext
|
||||
private LoggerContext loggerContext;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private String format;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private String charset = StandardCharsets.UTF_8.name();
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private Long pid;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private String serviceName;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private String serviceVersion;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private String serviceNodeName;
|
||||
|
||||
@PluginBuilderAttribute
|
||||
private String serviceEnvironment;
|
||||
|
||||
Builder setFormat(String format) {
|
||||
this.format = format;
|
||||
return this;
|
||||
|
@ -95,38 +92,13 @@ final class StructuredLogLayout extends AbstractStringLayout {
|
|||
return this;
|
||||
}
|
||||
|
||||
Builder setPid(Long pid) {
|
||||
this.pid = pid;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setServiceVersion(String serviceVersion) {
|
||||
this.serviceVersion = serviceVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setServiceNodeName(String serviceNodeName) {
|
||||
this.serviceNodeName = serviceNodeName;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setServiceEnvironment(String serviceEnvironment) {
|
||||
this.serviceEnvironment = serviceEnvironment;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructuredLogLayout build() {
|
||||
ApplicationMetadata applicationMetadata = new ApplicationMetadata(this.pid, this.serviceName,
|
||||
this.serviceVersion, this.serviceEnvironment, this.serviceNodeName);
|
||||
Charset charset = Charset.forName(this.charset);
|
||||
Environment environment = Log4J2LoggingSystem.getEnvironment(this.loggerContext);
|
||||
Assert.state(environment != null, "Unable to find Spring Environment in logger context");
|
||||
StructuredLogFormatter<LogEvent> formatter = new StructuredLogFormatterFactory<>(LogEvent.class,
|
||||
applicationMetadata, null, this::addCommonFormatters)
|
||||
environment, null, this::addCommonFormatters)
|
||||
.get(this.format);
|
||||
return new StructuredLogLayout(charset, formatter);
|
||||
}
|
||||
|
@ -134,7 +106,8 @@ final class StructuredLogLayout extends AbstractStringLayout {
|
|||
private void addCommonFormatters(CommonFormatters<LogEvent> commonFormatters) {
|
||||
commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA,
|
||||
(instantiator) -> new ElasticCommonSchemaStructuredLogFormatter(
|
||||
instantiator.getArg(ApplicationMetadata.class)));
|
||||
instantiator.getArg(ApplicationPid.class),
|
||||
instantiator.getArg(ElasticCommonSchemaService.class)));
|
||||
commonFormatters.add(CommonStructuredLogFormat.LOGSTASH,
|
||||
(instantiator) -> new LogstashStructuredLogFormatter());
|
||||
}
|
||||
|
|
|
@ -174,11 +174,6 @@ class DefaultLogbackConfiguration {
|
|||
private StructuredLogEncoder createStructuredLoggingEncoder(LogbackConfigurator config, String format) {
|
||||
StructuredLogEncoder encoder = new StructuredLogEncoder();
|
||||
encoder.setFormat(format);
|
||||
encoder.setPid(resolveLong(config, "${PID:--1}"));
|
||||
String applicationName = resolve(config, "${APPLICATION_NAME:-}");
|
||||
if (StringUtils.hasLength(applicationName)) {
|
||||
encoder.setServiceName(applicationName);
|
||||
}
|
||||
return encoder;
|
||||
}
|
||||
|
||||
|
@ -205,10 +200,6 @@ class DefaultLogbackConfiguration {
|
|||
return Integer.parseInt(resolve(config, val));
|
||||
}
|
||||
|
||||
private long resolveLong(LogbackConfigurator config, String val) {
|
||||
return Long.parseLong(resolve(config, val));
|
||||
}
|
||||
|
||||
private FileSize resolveFileSize(LogbackConfigurator config, String val) {
|
||||
return FileSize.valueOf(resolve(config, val));
|
||||
}
|
||||
|
|
|
@ -23,9 +23,11 @@ import org.slf4j.event.KeyValuePair;
|
|||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.json.JsonWriter.PairExtractor;
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
|
||||
/**
|
||||
* Logback {@link StructuredLogFormatter} for
|
||||
|
@ -34,30 +36,23 @@ import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
|||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ElasticCommonSchemaStructuredLogFormatter implements StructuredLogFormatter<ILoggingEvent> {
|
||||
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<ILoggingEvent> {
|
||||
|
||||
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
|
||||
(pair) -> pair.value);
|
||||
|
||||
private JsonWriter<ILoggingEvent> writer;
|
||||
|
||||
ElasticCommonSchemaStructuredLogFormatter(ApplicationMetadata metadata,
|
||||
ElasticCommonSchemaStructuredLogFormatter(ApplicationPid pid, ElasticCommonSchemaService service,
|
||||
ThrowableProxyConverter throwableProxyConverter) {
|
||||
this.writer = JsonWriter
|
||||
.<ILoggingEvent>of((members) -> loggingEventJson(metadata, throwableProxyConverter, members))
|
||||
.withNewLineAtEnd();
|
||||
super((members) -> jsonMembers(pid, service, throwableProxyConverter, members));
|
||||
}
|
||||
|
||||
private void loggingEventJson(ApplicationMetadata metadata, ThrowableProxyConverter throwableProxyConverter,
|
||||
JsonWriter.Members<ILoggingEvent> members) {
|
||||
private static void jsonMembers(ApplicationPid pid, ElasticCommonSchemaService service,
|
||||
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
|
||||
members.add("@timestamp", ILoggingEvent::getInstant);
|
||||
members.add("log.level", ILoggingEvent::getLevel);
|
||||
members.add("process.pid", metadata::pid).whenNotNull();
|
||||
members.add("process.pid", pid).when(ApplicationPid::isAvailable).as(ApplicationPid::toLong);
|
||||
members.add("process.thread.name", ILoggingEvent::getThreadName);
|
||||
members.add("service.name", metadata::name).whenHasLength();
|
||||
members.add("service.version", metadata::version).whenHasLength();
|
||||
members.add("service.environment", metadata::environment).whenHasLength();
|
||||
members.add("service.node.name", metadata::nodeName).whenHasLength();
|
||||
service.jsonMembers(members);
|
||||
members.add("log.logger", ILoggingEvent::getLoggerName);
|
||||
members.add("message", ILoggingEvent::getFormattedMessage);
|
||||
members.addMapEntries(ILoggingEvent::getMDCPropertyMap);
|
||||
|
@ -72,9 +67,4 @@ class ElasticCommonSchemaStructuredLogFormatter implements StructuredLogFormatte
|
|||
members.add("ecs.version", "8.11");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(ILoggingEvent event) {
|
||||
return this.writer.writeToString(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -186,13 +186,13 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
|
|||
@Override
|
||||
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
|
||||
LoggerContext loggerContext = getLoggerContext();
|
||||
putInitializationContextObjects(loggerContext, initializationContext);
|
||||
if (isAlreadyInitialized(loggerContext)) {
|
||||
return;
|
||||
}
|
||||
if (!initializeFromAotGeneratedArtifactsIfPossible(initializationContext, logFile)) {
|
||||
super.initialize(initializationContext, configLocation, logFile);
|
||||
}
|
||||
loggerContext.putObject(Environment.class.getName(), initializationContext.getEnvironment());
|
||||
loggerContext.getTurboFilterList().remove(SUPPRESS_ALL_FILTER);
|
||||
markAsInitialized(loggerContext);
|
||||
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
|
||||
|
@ -211,6 +211,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
|
|||
}
|
||||
LoggerContext loggerContext = getLoggerContext();
|
||||
stopAndReset(loggerContext);
|
||||
withLoggingSuppressed(() -> putInitializationContextObjects(loggerContext, initializationContext));
|
||||
SpringBootJoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
|
||||
configurator.setContext(loggerContext);
|
||||
boolean configuredUsingAotGeneratedArtifacts = configurator.configureUsingAotGeneratedArtifacts();
|
||||
|
@ -222,21 +223,23 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
|
|||
|
||||
@Override
|
||||
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
|
||||
LoggerContext context = getLoggerContext();
|
||||
stopAndReset(context);
|
||||
LoggerContext loggerContext = getLoggerContext();
|
||||
stopAndReset(loggerContext);
|
||||
withLoggingSuppressed(() -> {
|
||||
putInitializationContextObjects(loggerContext, initializationContext);
|
||||
boolean debug = Boolean.getBoolean("logback.debug");
|
||||
if (debug) {
|
||||
StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
|
||||
StatusListenerConfigHelper.addOnConsoleListenerInstance(loggerContext, new OnConsoleStatusListener());
|
||||
}
|
||||
Environment environment = initializationContext.getEnvironment();
|
||||
// Apply system properties directly in case the same JVM runs multiple apps
|
||||
new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment), context::putProperty)
|
||||
new LogbackLoggingSystemProperties(environment, getDefaultValueResolver(environment),
|
||||
loggerContext::putProperty)
|
||||
.apply(logFile);
|
||||
LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context)
|
||||
: new LogbackConfigurator(context);
|
||||
LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(loggerContext)
|
||||
: new LogbackConfigurator(loggerContext);
|
||||
new DefaultLogbackConfiguration(logFile).apply(configurator);
|
||||
context.setPackagingDataEnabled(true);
|
||||
loggerContext.setPackagingDataEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -246,6 +249,7 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
|
|||
LoggerContext loggerContext = getLoggerContext();
|
||||
stopAndReset(loggerContext);
|
||||
withLoggingSuppressed(() -> {
|
||||
putInitializationContextObjects(loggerContext, initializationContext);
|
||||
if (initializationContext != null) {
|
||||
applySystemProperties(initializationContext.getEnvironment(), logFile);
|
||||
}
|
||||
|
@ -334,11 +338,18 @@ public class LogbackLoggingSystem extends AbstractLoggingSystem implements BeanF
|
|||
|
||||
@Override
|
||||
protected void reinitialize(LoggingInitializationContext initializationContext) {
|
||||
getLoggerContext().reset();
|
||||
getLoggerContext().getStatusManager().clear();
|
||||
LoggerContext loggerContext = getLoggerContext();
|
||||
loggerContext.reset();
|
||||
loggerContext.getStatusManager().clear();
|
||||
loadConfiguration(initializationContext, getSelfInitializationConfig(), null);
|
||||
}
|
||||
|
||||
private void putInitializationContextObjects(LoggerContext loggerContext,
|
||||
LoggingInitializationContext initializationContext) {
|
||||
withLoggingSuppressed(
|
||||
() -> loggerContext.putObject(Environment.class.getName(), initializationContext.getEnvironment()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoggerConfiguration> getLoggerConfigurations() {
|
||||
List<LoggerConfiguration> result = new ArrayList<>();
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.slf4j.event.KeyValuePair;
|
|||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.json.JsonWriter.PairExtractor;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
|
||||
/**
|
||||
|
@ -42,21 +43,18 @@ import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
|||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LogstashStructuredLogFormatter implements StructuredLogFormatter<ILoggingEvent> {
|
||||
class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<ILoggingEvent> {
|
||||
|
||||
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
|
||||
(pair) -> pair.value);
|
||||
|
||||
private JsonWriter<ILoggingEvent> writer;
|
||||
|
||||
LogstashStructuredLogFormatter(ThrowableProxyConverter throwableProxyConverter) {
|
||||
this.writer = JsonWriter.<ILoggingEvent>of((members) -> loggingEventJson(throwableProxyConverter, members))
|
||||
.withNewLineAtEnd();
|
||||
super((members) -> jsonMembers(throwableProxyConverter, members));
|
||||
}
|
||||
|
||||
private void loggingEventJson(ThrowableProxyConverter throwableProxyConverter,
|
||||
private static void jsonMembers(ThrowableProxyConverter throwableProxyConverter,
|
||||
JsonWriter.Members<ILoggingEvent> members) {
|
||||
members.add("@timestamp", ILoggingEvent::getInstant).as(this::asTimestamp);
|
||||
members.add("@timestamp", ILoggingEvent::getInstant).as(LogstashStructuredLogFormatter::asTimestamp);
|
||||
members.add("@version", "1");
|
||||
members.add("message", ILoggingEvent::getFormattedMessage);
|
||||
members.add("logger_name", ILoggingEvent::getLoggerName);
|
||||
|
@ -67,24 +65,27 @@ class LogstashStructuredLogFormatter implements StructuredLogFormatter<ILoggingE
|
|||
members.add(ILoggingEvent::getKeyValuePairs)
|
||||
.whenNotEmpty()
|
||||
.usingExtractedPairs(Iterable::forEach, keyValuePairExtractor);
|
||||
members.add("tags", ILoggingEvent::getMarkerList).whenNotNull().as(this::getMarkers).whenNotEmpty();
|
||||
members.add("tags", ILoggingEvent::getMarkerList)
|
||||
.whenNotNull()
|
||||
.as(LogstashStructuredLogFormatter::getMarkers)
|
||||
.whenNotEmpty();
|
||||
members.add("stack_trace", (event) -> event)
|
||||
.whenNotNull(ILoggingEvent::getThrowableProxy)
|
||||
.as(throwableProxyConverter::convert);
|
||||
}
|
||||
|
||||
private String asTimestamp(Instant instant) {
|
||||
private static String asTimestamp(Instant instant) {
|
||||
OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(offsetDateTime);
|
||||
}
|
||||
|
||||
private Set<String> getMarkers(List<Marker> markers) {
|
||||
private static Set<String> getMarkers(List<Marker> markers) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
addMarkers(result, markers.iterator());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addMarkers(Set<String> result, Iterator<Marker> iterator) {
|
||||
private static void addMarkers(Set<String> result, Iterator<Marker> iterator) {
|
||||
while (iterator.hasNext()) {
|
||||
Marker marker = iterator.next();
|
||||
result.add(marker.getName());
|
||||
|
@ -94,9 +95,4 @@ class LogstashStructuredLogFormatter implements StructuredLogFormatter<ILoggingE
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(ILoggingEvent event) {
|
||||
return this.writer.writeToString(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,12 +24,14 @@ import ch.qos.logback.classic.spi.ILoggingEvent;
|
|||
import ch.qos.logback.core.encoder.Encoder;
|
||||
import ch.qos.logback.core.encoder.EncoderBase;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.boot.util.Instantiator.AvailableParameters;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -48,42 +50,12 @@ public class StructuredLogEncoder extends EncoderBase<ILoggingEvent> {
|
|||
|
||||
private StructuredLogFormatter<ILoggingEvent> formatter;
|
||||
|
||||
private Long pid;
|
||||
|
||||
private String serviceName;
|
||||
|
||||
private String serviceVersion;
|
||||
|
||||
private String serviceNodeName;
|
||||
|
||||
private String serviceEnvironment;
|
||||
|
||||
private Charset charset = StandardCharsets.UTF_8;
|
||||
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public void setPid(Long pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
public void setServiceVersion(String serviceVersion) {
|
||||
this.serviceVersion = serviceVersion;
|
||||
}
|
||||
|
||||
public void setServiceNodeName(String serviceNodeName) {
|
||||
this.serviceNodeName = serviceNodeName;
|
||||
}
|
||||
|
||||
public void setServiceEnvironment(String serviceEnvironment) {
|
||||
this.serviceEnvironment = serviceEnvironment;
|
||||
}
|
||||
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
@ -97,10 +69,10 @@ public class StructuredLogEncoder extends EncoderBase<ILoggingEvent> {
|
|||
}
|
||||
|
||||
private StructuredLogFormatter<ILoggingEvent> createFormatter(String format) {
|
||||
ApplicationMetadata applicationMetadata = new ApplicationMetadata(this.pid, this.serviceName,
|
||||
this.serviceVersion, this.serviceEnvironment, this.serviceNodeName);
|
||||
return new StructuredLogFormatterFactory<>(ILoggingEvent.class, applicationMetadata,
|
||||
this::addAvailableParameters, this::addCommonFormatters)
|
||||
Environment environment = (Environment) getContext().getObject(Environment.class.getName());
|
||||
Assert.state(environment != null, "Unable to find Spring Environment in logger context");
|
||||
return new StructuredLogFormatterFactory<>(ILoggingEvent.class, environment, this::addAvailableParameters,
|
||||
this::addCommonFormatters)
|
||||
.get(format);
|
||||
}
|
||||
|
||||
|
@ -111,7 +83,8 @@ public class StructuredLogEncoder extends EncoderBase<ILoggingEvent> {
|
|||
private void addCommonFormatters(CommonFormatters<ILoggingEvent> commonFormatters) {
|
||||
commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA,
|
||||
(instantiator) -> new ElasticCommonSchemaStructuredLogFormatter(
|
||||
instantiator.getArg(ApplicationMetadata.class),
|
||||
instantiator.getArg(ApplicationPid.class),
|
||||
instantiator.getArg(ElasticCommonSchemaService.class),
|
||||
instantiator.getArg(ThrowableProxyConverter.class)));
|
||||
commonFormatters.add(CommonStructuredLogFormat.LOGSTASH, (instantiator) -> new LogstashStructuredLogFormatter(
|
||||
instantiator.getArg(ThrowableProxyConverter.class)));
|
||||
|
@ -130,7 +103,7 @@ public class StructuredLogEncoder extends EncoderBase<ILoggingEvent> {
|
|||
|
||||
@Override
|
||||
public byte[] encode(ILoggingEvent event) {
|
||||
return this.formatter.format(event).getBytes(this.charset);
|
||||
return this.formatter.formatAsBytes(event, (this.charset != null) ? this.charset : StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
/**
|
||||
* Metadata about the application.
|
||||
*
|
||||
* @param pid the process ID of the application
|
||||
* @param name the application name
|
||||
* @param version the version of the application
|
||||
* @param environment the name of the environment the application is running in
|
||||
* @param nodeName the name of the node the application is running on
|
||||
* @author Moritz Halbritter
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public record ApplicationMetadata(Long pid, String name, String version, String environment, String nodeName) {
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Service details for Elastic Common Schema structured logging.
|
||||
*
|
||||
* @param name the application name
|
||||
* @param version the version of the application
|
||||
* @param environment the name of the environment the application is running in
|
||||
* @param nodeName the name of the node the application is running on
|
||||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public record ElasticCommonSchemaService(String name, String version, String environment, String nodeName) {
|
||||
|
||||
static final ElasticCommonSchemaService NONE = new ElasticCommonSchemaService(null, null, null, null);
|
||||
|
||||
private ElasticCommonSchemaService withDefaults(Environment environment) {
|
||||
String name = withFallbackProperty(environment, this.name, "spring.application.name");
|
||||
return new ElasticCommonSchemaService(name, this.version, this.environment, this.nodeName);
|
||||
}
|
||||
|
||||
private String withFallbackProperty(Environment environment, String value, String property) {
|
||||
return (!StringUtils.hasLength(value)) ? environment.getProperty(property) : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link JsonWriter} members for the service.
|
||||
* @param members the members to add to
|
||||
*/
|
||||
public void jsonMembers(JsonWriter.Members<?> members) {
|
||||
members.add("service.name", this::name).whenHasLength();
|
||||
members.add("service.version", this::version).whenHasLength();
|
||||
members.add("service.environment", this::environment).whenHasLength();
|
||||
members.add("service.node.name", this::nodeName).whenHasLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link ElasticCommonSchemaService} from bound from properties in the
|
||||
* given {@link Environment}.
|
||||
* @param environment the source environment
|
||||
* @return a new {@link ElasticCommonSchemaService} instance
|
||||
*/
|
||||
public static ElasticCommonSchemaService get(Environment environment) {
|
||||
return Binder.get(environment)
|
||||
.bind("logging.structured.ecs.service", ElasticCommonSchemaService.class)
|
||||
.orElse(NONE)
|
||||
.withDefaults(environment);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.json.JsonWriter.Members;
|
||||
|
||||
/**
|
||||
* Base class for {@link StructuredLogFormatter} implementations that generates JSON using
|
||||
* a {@link JsonWriter}.
|
||||
*
|
||||
* @param <E> the log event type
|
||||
* @author Phillip Webb
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public abstract class JsonWriterStructuredLogFormatter<E> implements StructuredLogFormatter<E> {
|
||||
|
||||
private final JsonWriter<E> jsonWriter;
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonWriterStructuredLogFormatter} instance with the given
|
||||
* members.
|
||||
* @param members a consumer which should configure the members
|
||||
*/
|
||||
protected JsonWriterStructuredLogFormatter(Consumer<Members<E>> members) {
|
||||
this(JsonWriter.of(members).withNewLineAtEnd());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link JsonWriterStructuredLogFormatter} instance with the given
|
||||
* {@link JsonWriter}.
|
||||
* @param jsonWriter the {@link JsonWriter}
|
||||
*/
|
||||
protected JsonWriterStructuredLogFormatter(JsonWriter<E> jsonWriter) {
|
||||
this.jsonWriter = jsonWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(E event) {
|
||||
return this.jsonWriter.writeToString(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] formatAsBytes(E event, Charset charset) {
|
||||
return this.jsonWriter.write(event).toByteArray();
|
||||
}
|
||||
|
||||
}
|
|
@ -16,14 +16,21 @@
|
|||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
|
||||
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* Formats a log event to a structured log message.
|
||||
* <p>
|
||||
* Implementing classes can declare the following parameter types in the constructor:
|
||||
* <ul>
|
||||
* <li>{@link ApplicationMetadata}</li>
|
||||
* <li>{@link Environment}</li>
|
||||
* <li>{@link ApplicationPid}</li>
|
||||
* <li>{@link ElasticCommonSchemaService}</li>
|
||||
* </ul>
|
||||
* When using Logback, implementing classes can also use the following parameter types in
|
||||
* the constructor:
|
||||
|
@ -35,13 +42,24 @@ import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
|
|||
* @author Moritz Halbritter
|
||||
* @since 3.4.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StructuredLogFormatter<E> {
|
||||
|
||||
/**
|
||||
* Formats the given log event.
|
||||
* Formats the given log event to a String.
|
||||
* @param event the log event to write
|
||||
* @return the formatted log event
|
||||
* @return the formatted log event String
|
||||
*/
|
||||
String format(E event);
|
||||
|
||||
/**
|
||||
* Formats the given log event to a byte array.
|
||||
* @param event the log event to write
|
||||
* @param charset the charset
|
||||
* @return the formatted log event bytes
|
||||
*/
|
||||
default byte[] formatAsBytes(E event, Charset charset) {
|
||||
return format(event).getBytes(charset);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,10 +21,12 @@ import java.util.Map;
|
|||
import java.util.TreeMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.boot.util.Instantiator;
|
||||
import org.springframework.boot.util.Instantiator.AvailableParameters;
|
||||
import org.springframework.boot.util.Instantiator.FailureHandler;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
|
@ -56,16 +58,19 @@ public class StructuredLogFormatterFactory<E> {
|
|||
/**
|
||||
* Create a new {@link StructuredLogFormatterFactory} instance.
|
||||
* @param logEventType the log event type
|
||||
* @param applicationMetadata an {@link ApplicationMetadata} instance for injection
|
||||
* @param environment the Spring {@link Environment}
|
||||
* @param availableParameters callback used to configure available parameters for the
|
||||
* specific logging system
|
||||
* @param commonFormatters callback used to define supported common formatters
|
||||
*/
|
||||
public StructuredLogFormatterFactory(Class<E> logEventType, ApplicationMetadata applicationMetadata,
|
||||
public StructuredLogFormatterFactory(Class<E> logEventType, Environment environment,
|
||||
Consumer<AvailableParameters> availableParameters, Consumer<CommonFormatters<E>> commonFormatters) {
|
||||
this.logEventType = logEventType;
|
||||
this.instantiator = new Instantiator<>(StructuredLogFormatter.class, (allAvailableParameters) -> {
|
||||
allAvailableParameters.add(ApplicationMetadata.class, applicationMetadata);
|
||||
allAvailableParameters.add(Environment.class, environment);
|
||||
allAvailableParameters.add(ApplicationPid.class, (type) -> new ApplicationPid());
|
||||
allAvailableParameters.add(ElasticCommonSchemaService.class,
|
||||
(type) -> ElasticCommonSchemaService.get(environment));
|
||||
if (availableParameters != null) {
|
||||
availableParameters.accept(allAvailableParameters);
|
||||
}
|
||||
|
|
|
@ -223,6 +223,26 @@
|
|||
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
|
||||
"defaultValue": true
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.ecs.service.environment",
|
||||
"type": "java.lang.String",
|
||||
"description": "Structured ECS service environment."
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.ecs.service.name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Structured ECS service name (defaults to 'spring.application.name')."
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.ecs.service.node-name",
|
||||
"type": "java.lang.String",
|
||||
"description": "Structured ECS service node name."
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.ecs.service.version",
|
||||
"type": "java.lang.String",
|
||||
"description": "Structured ECS service version."
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.format.console",
|
||||
"type": "java.lang.String",
|
||||
|
@ -597,6 +617,44 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.format.console",
|
||||
"values": [
|
||||
{
|
||||
"value": "ecs"
|
||||
},
|
||||
{
|
||||
"value": "logstash"
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "handle-as",
|
||||
"parameters": {
|
||||
"target": "java.lang.Class"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.format.file",
|
||||
"values": [
|
||||
{
|
||||
"value": "ecs"
|
||||
},
|
||||
{
|
||||
"value": "logstash"
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
{
|
||||
"name": "handle-as",
|
||||
"parameters": {
|
||||
"target": "java.lang.Class"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.config.import",
|
||||
"values": [
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<Select>
|
||||
<SystemPropertyArbiter propertyName="CONSOLE_LOG_STRUCTURED_FORMAT">
|
||||
<StructuredLogLayout format="${sys:CONSOLE_LOG_STRUCTURED_FORMAT}" charset="${sys:CONSOLE_LOG_CHARSET}" pid="${sys:PID:--1}" serviceName="${sys:APPLICATION_NAME:-}"/>
|
||||
<StructuredLogLayout format="${sys:CONSOLE_LOG_STRUCTURED_FORMAT}" charset="${sys:CONSOLE_LOG_CHARSET}"/>
|
||||
</SystemPropertyArbiter>
|
||||
<DefaultArbiter>
|
||||
<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" charset="${sys:CONSOLE_LOG_CHARSET}"/>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="${sys:LOG_PATH}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
|
||||
<Select>
|
||||
<SystemPropertyArbiter propertyName="FILE_LOG_STRUCTURED_FORMAT">
|
||||
<StructuredLogLayout format="${sys:FILE_LOG_STRUCTURED_FORMAT}" charset="${sys:FILE_LOG_CHARSET}" pid="${sys:PID:--1}" serviceName="${sys:APPLICATION_NAME:-}"/>
|
||||
<StructuredLogLayout format="${sys:FILE_LOG_STRUCTURED_FORMAT}" charset="${sys:FILE_LOG_CHARSET}"/>
|
||||
</SystemPropertyArbiter>
|
||||
<DefaultArbiter>
|
||||
<PatternLayout pattern="${sys:FILE_LOG_PATTERN}" charset="${sys:FILE_LOG_CHARSET}"/>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<Select>
|
||||
<SystemPropertyArbiter propertyName="CONSOLE_LOG_STRUCTURED_FORMAT">
|
||||
<StructuredLogLayout format="${sys:CONSOLE_LOG_STRUCTURED_FORMAT}" charset="${sys:CONSOLE_LOG_CHARSET}" pid="${sys:PID:--1}" serviceName="${sys:APPLICATION_NAME:-}"/>
|
||||
<StructuredLogLayout format="${sys:CONSOLE_LOG_STRUCTURED_FORMAT}" charset="${sys:CONSOLE_LOG_CHARSET}"/>
|
||||
</SystemPropertyArbiter>
|
||||
<DefaultArbiter>
|
||||
<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" charset="${sys:CONSOLE_LOG_CHARSET}"/>
|
||||
|
|
|
@ -13,8 +13,6 @@ equivalent to the programmatic initialization performed by Boot
|
|||
<encoder class="org.springframework.boot.logging.logback.StructuredLoggingEncoder">
|
||||
<format>${CONSOLE_LOG_STRUCTURED_FORMAT}</format>
|
||||
<charset>${CONSOLE_LOG_CHARSET}</charset>
|
||||
<pid>${PID:--1}</pid>
|
||||
<serviceName>${APPLICATION_NAME:-}</serviceName>
|
||||
</encoder>
|
||||
</appender>
|
||||
</included>
|
||||
|
|
|
@ -13,8 +13,6 @@ equivalent to the programmatic initialization performed by Boot
|
|||
<encoder class="org.springframework.boot.logging.logback.StructuredLoggingEncoder">
|
||||
<format>${FILE_LOG_STRUCTURED_FORMAT}</format>
|
||||
<charset>${FILE_LOG_CHARSET}</charset>
|
||||
<pid>${PID:--1}</pid>
|
||||
<serviceName>${APPLICATION_NAME:-}</serviceName>
|
||||
</encoder>
|
||||
<file>${LOG_FILE}</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
|
|
|
@ -23,7 +23,9 @@ import org.apache.logging.log4j.core.impl.MutableLogEvent;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.boot.system.MockApplicationPid;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -32,14 +34,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class Log4j2EcsStructuredLoggingFormatterTests extends AbstractStructuredLoggingTests {
|
||||
class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredLoggingTests {
|
||||
|
||||
private ElasticCommonSchemaStructuredLogFormatter formatter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(
|
||||
new ApplicationMetadata(1L, "name", "1.0.0", "test", "node-1"));
|
||||
ApplicationPid pid = MockApplicationPid.of(1L);
|
||||
ElasticCommonSchemaService service = new ElasticCommonSchemaService("name", "1.0.0", "test", "node-1");
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(pid, service);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -67,10 +70,10 @@ class Log4j2EcsStructuredLoggingFormatterTests extends AbstractStructuredLogging
|
|||
assertThat(stackTrace).startsWith(
|
||||
"""
|
||||
java.lang.RuntimeException: Boom
|
||||
\tat org.springframework.boot.logging.log4j2.Log4j2EcsStructuredLoggingFormatterTests.shouldFormatException""");
|
||||
\tat org.springframework.boot.logging.log4j2.ElasticCommonSchemaStructuredLogFormatterTests.shouldFormatException""");
|
||||
assertThat(json).contains(
|
||||
"""
|
||||
java.lang.RuntimeException: Boom\\n\\tat org.springframework.boot.logging.log4j2.Log4j2EcsStructuredLoggingFormatterTests.shouldFormatException""");
|
||||
java.lang.RuntimeException: Boom\\n\\tat org.springframework.boot.logging.log4j2.ElasticCommonSchemaStructuredLogFormatterTests.shouldFormatException""");
|
||||
}
|
||||
|
||||
}
|
|
@ -35,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class Log4j2LogstashStructuredLoggingFormatterTests extends AbstractStructuredLoggingTests {
|
||||
class LogstashStructuredLogFormatterTests extends AbstractStructuredLoggingTests {
|
||||
|
||||
private LogstashStructuredLogFormatter formatter;
|
||||
|
||||
|
@ -71,10 +71,10 @@ class Log4j2LogstashStructuredLoggingFormatterTests extends AbstractStructuredLo
|
|||
assertThat(stackTrace).startsWith(
|
||||
"""
|
||||
java.lang.RuntimeException: Boom
|
||||
\tat org.springframework.boot.logging.log4j2.Log4j2LogstashStructuredLoggingFormatterTests.shouldFormatException""");
|
||||
\tat org.springframework.boot.logging.log4j2.LogstashStructuredLogFormatterTests.shouldFormatException""");
|
||||
assertThat(json).contains(
|
||||
"""
|
||||
java.lang.RuntimeException: Boom\\n\\tat org.springframework.boot.logging.log4j2.Log4j2LogstashStructuredLoggingFormatterTests.shouldFormatException""");
|
||||
java.lang.RuntimeException: Boom\\n\\tat org.springframework.boot.logging.log4j2.LogstashStructuredLogFormatterTests.shouldFormatException""");
|
||||
}
|
||||
|
||||
}
|
|
@ -18,11 +18,18 @@ package org.springframework.boot.logging.log4j2;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.log4j2.StructuredLogLayout.Builder;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
@ -34,9 +41,25 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
|||
*/
|
||||
class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests {
|
||||
|
||||
private MockEnvironment environment;
|
||||
|
||||
private LoggerContext loggerContext;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
this.environment = new MockEnvironment();
|
||||
this.loggerContext = (LoggerContext) LogManager.getContext(false);
|
||||
this.loggerContext.putObject(Log4J2LoggingSystem.ENVIRONMENT_KEY, this.environment);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
this.loggerContext.removeObject(Log4J2LoggingSystem.ENVIRONMENT_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSupportEcsCommonFormat() {
|
||||
StructuredLogLayout layout = StructuredLogLayout.newBuilder().setFormat("ecs").build();
|
||||
StructuredLogLayout layout = newBuilder().setFormat("ecs").build();
|
||||
String json = layout.toSerializable(createEvent());
|
||||
Map<String, Object> deserialized = deserialize(json);
|
||||
assertThat(deserialized).containsKey("ecs.version");
|
||||
|
@ -44,7 +67,7 @@ class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests {
|
|||
|
||||
@Test
|
||||
void shouldSupportLogstashCommonFormat() {
|
||||
StructuredLogLayout layout = StructuredLogLayout.newBuilder().setFormat("logstash").build();
|
||||
StructuredLogLayout layout = newBuilder().setFormat("logstash").build();
|
||||
String json = layout.toSerializable(createEvent());
|
||||
Map<String, Object> deserialized = deserialize(json);
|
||||
assertThat(deserialized).containsKey("@version");
|
||||
|
@ -52,8 +75,7 @@ class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests {
|
|||
|
||||
@Test
|
||||
void shouldSupportCustomFormat() {
|
||||
StructuredLogLayout layout = StructuredLogLayout.newBuilder()
|
||||
.setFormat(CustomLog4j2StructuredLoggingFormatter.class.getName())
|
||||
StructuredLogLayout layout = newBuilder().setFormat(CustomLog4j2StructuredLoggingFormatter.class.getName())
|
||||
.build();
|
||||
String format = layout.toSerializable(createEvent());
|
||||
assertThat(format).isEqualTo("custom-format");
|
||||
|
@ -61,40 +83,41 @@ class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests {
|
|||
|
||||
@Test
|
||||
void shouldInjectCustomFormatConstructorParameters() {
|
||||
StructuredLogLayout layout = StructuredLogLayout.newBuilder()
|
||||
StructuredLogLayout layout = newBuilder()
|
||||
.setFormat(CustomLog4j2StructuredLoggingFormatterWithInjection.class.getName())
|
||||
.setPid(1L)
|
||||
.build();
|
||||
String format = layout.toSerializable(createEvent());
|
||||
assertThat(format).isEqualTo("custom-format-with-injection pid=1");
|
||||
assertThat(format).isEqualTo("custom-format-with-injection pid=" + new ApplicationPid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCheckTypeArgument() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> StructuredLogLayout.newBuilder()
|
||||
.setFormat(CustomLog4j2StructuredLoggingFormatterWrongType.class.getName())
|
||||
.build())
|
||||
assertThatIllegalArgumentException().isThrownBy(
|
||||
() -> newBuilder().setFormat(CustomLog4j2StructuredLoggingFormatterWrongType.class.getName()).build())
|
||||
.withMessageContaining("must be org.apache.logging.log4j.core.LogEvent but was java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCheckTypeArgumentWithRawType() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> StructuredLogLayout.newBuilder()
|
||||
.setFormat(CustomLog4j2StructuredLoggingFormatterRawType.class.getName())
|
||||
.build())
|
||||
.isThrownBy(
|
||||
() -> newBuilder().setFormat(CustomLog4j2StructuredLoggingFormatterRawType.class.getName()).build())
|
||||
.withMessageContaining("must be org.apache.logging.log4j.core.LogEvent but was null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfNoCommonOrCustomFormatIsSet() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> StructuredLogLayout.newBuilder().setFormat("does-not-exist").build())
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> newBuilder().setFormat("does-not-exist").build())
|
||||
.withMessageContaining("Unknown format 'does-not-exist'. "
|
||||
+ "Values can be a valid fully-qualified class name or one of the common formats: [ecs, logstash]");
|
||||
}
|
||||
|
||||
private Builder newBuilder() {
|
||||
Builder builder = StructuredLogLayout.newBuilder();
|
||||
ReflectionTestUtils.setField(builder, "loggerContext", this.loggerContext);
|
||||
return builder;
|
||||
}
|
||||
|
||||
static final class CustomLog4j2StructuredLoggingFormatter implements StructuredLogFormatter<LogEvent> {
|
||||
|
||||
@Override
|
||||
|
@ -106,15 +129,15 @@ class StructuredLoggingLayoutTests extends AbstractStructuredLoggingTests {
|
|||
|
||||
static final class CustomLog4j2StructuredLoggingFormatterWithInjection implements StructuredLogFormatter<LogEvent> {
|
||||
|
||||
private final ApplicationMetadata metadata;
|
||||
private final ApplicationPid pid;
|
||||
|
||||
CustomLog4j2StructuredLoggingFormatterWithInjection(ApplicationMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
CustomLog4j2StructuredLoggingFormatterWithInjection(ApplicationPid pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogEvent event) {
|
||||
return "custom-format-with-injection pid=" + this.metadata.pid();
|
||||
return "custom-format-with-injection pid=" + this.pid;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@ import ch.qos.logback.classic.spi.ThrowableProxy;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaService;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.boot.system.MockApplicationPid;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
@ -33,7 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class LogbackEcsStructuredLoggingFormatterTests extends AbstractStructuredLoggingTests {
|
||||
class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredLoggingTests {
|
||||
|
||||
private ElasticCommonSchemaStructuredLogFormatter formatter;
|
||||
|
||||
|
@ -41,8 +43,9 @@ class LogbackEcsStructuredLoggingFormatterTests extends AbstractStructuredLoggin
|
|||
@BeforeEach
|
||||
void setUp() {
|
||||
super.setUp();
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(
|
||||
new ApplicationMetadata(1L, "name", "1.0.0", "test", "node-1"), getThrowableProxyConverter());
|
||||
ApplicationPid pid = MockApplicationPid.of(1L);
|
||||
ElasticCommonSchemaService service = new ElasticCommonSchemaService("name", "1.0.0", "test", "node-1");
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(pid, service, getThrowableProxyConverter());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -70,10 +73,10 @@ class LogbackEcsStructuredLoggingFormatterTests extends AbstractStructuredLoggin
|
|||
.containsAllEntriesOf(map("error.type", "java.lang.RuntimeException", "error.message", "Boom"));
|
||||
String stackTrace = (String) deserialized.get("error.stack_trace");
|
||||
assertThat(stackTrace).startsWith(
|
||||
"java.lang.RuntimeException: Boom%n\tat org.springframework.boot.logging.logback.LogbackEcsStructuredLoggingFormatterTests.shouldFormatException"
|
||||
"java.lang.RuntimeException: Boom%n\tat org.springframework.boot.logging.logback.ElasticCommonSchemaStructuredLogFormatterTests.shouldFormatException"
|
||||
.formatted());
|
||||
assertThat(json).contains(
|
||||
"java.lang.RuntimeException: Boom%n\\tat org.springframework.boot.logging.logback.LogbackEcsStructuredLoggingFormatterTests.shouldFormatException"
|
||||
"java.lang.RuntimeException: Boom%n\\tat org.springframework.boot.logging.logback.ElasticCommonSchemaStructuredLogFormatterTests.shouldFormatException"
|
||||
.formatted()
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r"));
|
|
@ -65,6 +65,7 @@ import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
|||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
@ -940,6 +941,21 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
|
|||
assertThat(output).doesNotContain("\033[");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEnvironment() {
|
||||
this.loggingSystem.beforeInitialize();
|
||||
initialize(this.initializationContext, null, null);
|
||||
assertThat(this.logger.getLoggerContext().getObject(Environment.class.getName())).isSameAs(this.environment);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEnvironmentWhenUsingFile() {
|
||||
this.loggingSystem.beforeInitialize();
|
||||
LogFile logFile = getLogFile(tmpDir() + "/example.log", null, false);
|
||||
initialize(this.initializationContext, "classpath:logback-nondefault.xml", logFile);
|
||||
assertThat(this.logger.getLoggerContext().getObject(Environment.class.getName())).isSameAs(this.environment);
|
||||
}
|
||||
|
||||
private void initialize(LoggingInitializationContext context, String configLocation, LogFile logFile) {
|
||||
this.loggingSystem.getSystemProperties((ConfigurableEnvironment) context.getEnvironment()).apply(logFile);
|
||||
this.loggingSystem.beforeInitialize();
|
||||
|
|
|
@ -36,7 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class LogbackLogstashStructuredLoggingFormatterTests extends AbstractStructuredLoggingTests {
|
||||
class LogstashStructuredLogFormatterTests extends AbstractStructuredLoggingTests {
|
||||
|
||||
private LogstashStructuredLogFormatter formatter;
|
||||
|
||||
|
@ -74,10 +74,10 @@ class LogbackLogstashStructuredLoggingFormatterTests extends AbstractStructuredL
|
|||
Map<String, Object> deserialized = deserialize(json);
|
||||
String stackTrace = (String) deserialized.get("stack_trace");
|
||||
assertThat(stackTrace).startsWith(
|
||||
"java.lang.RuntimeException: Boom%n\tat org.springframework.boot.logging.logback.LogbackLogstashStructuredLoggingFormatterTests.shouldFormatException"
|
||||
"java.lang.RuntimeException: Boom%n\tat org.springframework.boot.logging.logback.LogstashStructuredLogFormatterTests.shouldFormatException"
|
||||
.formatted());
|
||||
assertThat(json).contains(
|
||||
"java.lang.RuntimeException: Boom%n\\tat org.springframework.boot.logging.logback.LogbackLogstashStructuredLoggingFormatterTests.shouldFormatException"
|
||||
"java.lang.RuntimeException: Boom%n\\tat org.springframework.boot.logging.logback.LogstashStructuredLogFormatterTests.shouldFormatException"
|
||||
.formatted()
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r"));
|
|
@ -23,12 +23,16 @@ import java.util.Map;
|
|||
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.classic.spi.LoggingEvent;
|
||||
import ch.qos.logback.core.Context;
|
||||
import ch.qos.logback.core.ContextBase;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
@ -37,16 +41,25 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
|||
* Tests for {@link StructuredLogEncoder}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class StructuredLoggingEncoderTests extends AbstractStructuredLoggingTests {
|
||||
|
||||
private StructuredLogEncoder encoder;
|
||||
|
||||
private Context loggerContext;
|
||||
|
||||
private MockEnvironment environment;
|
||||
|
||||
@Override
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
super.setUp();
|
||||
this.environment = new MockEnvironment();
|
||||
this.loggerContext = new ContextBase();
|
||||
this.loggerContext.putObject(Environment.class.getName(), this.environment);
|
||||
this.encoder = new StructuredLogEncoder();
|
||||
this.encoder.setContext(this.loggerContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,12 +104,12 @@ class StructuredLoggingEncoderTests extends AbstractStructuredLoggingTests {
|
|||
@Test
|
||||
void shouldInjectCustomFormatConstructorParameters() {
|
||||
this.encoder.setFormat(CustomLogbackStructuredLoggingFormatterWithInjection.class.getName());
|
||||
this.encoder.setPid(1L);
|
||||
this.encoder.start();
|
||||
LoggingEvent event = createEvent();
|
||||
event.setMDCPropertyMap(Collections.emptyMap());
|
||||
String format = encode(event);
|
||||
assertThat(format).isEqualTo("custom-format-with-injection pid=1 hasThrowableProxyConverter=true");
|
||||
assertThat(format)
|
||||
.isEqualTo("custom-format-with-injection pid=" + new ApplicationPid() + " hasThrowableProxyConverter=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -141,20 +154,20 @@ class StructuredLoggingEncoderTests extends AbstractStructuredLoggingTests {
|
|||
static final class CustomLogbackStructuredLoggingFormatterWithInjection
|
||||
implements StructuredLogFormatter<ILoggingEvent> {
|
||||
|
||||
private final ApplicationMetadata metadata;
|
||||
private final ApplicationPid pid;
|
||||
|
||||
private final ThrowableProxyConverter throwableProxyConverter;
|
||||
|
||||
CustomLogbackStructuredLoggingFormatterWithInjection(ApplicationMetadata metadata,
|
||||
CustomLogbackStructuredLoggingFormatterWithInjection(ApplicationPid pid,
|
||||
ThrowableProxyConverter throwableProxyConverter) {
|
||||
this.metadata = metadata;
|
||||
this.pid = pid;
|
||||
this.throwableProxyConverter = throwableProxyConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(ILoggingEvent event) {
|
||||
boolean hasThrowableProxyConverter = this.throwableProxyConverter != null;
|
||||
return "custom-format-with-injection pid=" + this.metadata.pid() + " hasThrowableProxyConverter="
|
||||
return "custom-format-with-injection pid=" + this.pid + " hasThrowableProxyConverter="
|
||||
+ hasThrowableProxyConverter;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.logging.structured;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link ElasticCommonSchemaService}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ElasticCommonSchemaServiceTests {
|
||||
|
||||
@Test
|
||||
void getBindsFromEnvironment() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("logging.structured.ecs.service.name", "spring");
|
||||
environment.setProperty("logging.structured.ecs.service.version", "1.2.3");
|
||||
environment.setProperty("logging.structured.ecs.service.environment", "prod");
|
||||
environment.setProperty("logging.structured.ecs.service.node-name", "boot");
|
||||
ElasticCommonSchemaService service = ElasticCommonSchemaService.get(environment);
|
||||
assertThat(service).isEqualTo(new ElasticCommonSchemaService("spring", "1.2.3", "prod", "boot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenNoServiceNameUsesApplicationName() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
environment.setProperty("spring.application.name", "spring");
|
||||
ElasticCommonSchemaService service = ElasticCommonSchemaService.get(environment);
|
||||
assertThat(service).isEqualTo(new ElasticCommonSchemaService("spring", null, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getWhenNoPropertiesToBind() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
ElasticCommonSchemaService service = ElasticCommonSchemaService.get(environment);
|
||||
assertThat(service).isEqualTo(new ElasticCommonSchemaService(null, null, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addToJsonMembersCreatesValidJson() {
|
||||
ElasticCommonSchemaService service = new ElasticCommonSchemaService("spring", "1.2.3", "prod", "boot");
|
||||
JsonWriter<ElasticCommonSchemaService> writer = JsonWriter.of(service::jsonMembers);
|
||||
assertThat(writer.writeToString(service))
|
||||
.isEqualTo("{\"service.name\":\"spring\",\"service.version\":\"1.2.3\","
|
||||
+ "\"service.environment\":\"prod\",\"service.node.name\":\"boot\"}");
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,8 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
|
||||
import org.springframework.boot.util.Instantiator.AvailableParameters;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
@ -31,13 +33,13 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
|||
*/
|
||||
class StructuredLogFormatterFactoryTests {
|
||||
|
||||
private final ApplicationMetadata applicationMetadata;
|
||||
|
||||
private final StructuredLogFormatterFactory<LogEvent> factory;
|
||||
|
||||
private final MockEnvironment environment = new MockEnvironment();
|
||||
|
||||
StructuredLogFormatterFactoryTests() {
|
||||
this.applicationMetadata = new ApplicationMetadata(123L, "test", "1.2", null, null);
|
||||
this.factory = new StructuredLogFormatterFactory<>(LogEvent.class, this.applicationMetadata,
|
||||
this.environment.setProperty("logging.structured.ecs.service.version", "1.2.3");
|
||||
this.factory = new StructuredLogFormatterFactory<>(LogEvent.class, this.environment,
|
||||
this::addAvailableParameters, this::addCommonFormatters);
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,7 @@ class StructuredLogFormatterFactoryTests {
|
|||
|
||||
private void addCommonFormatters(CommonFormatters<LogEvent> commonFormatters) {
|
||||
commonFormatters.add(CommonStructuredLogFormat.ELASTIC_COMMON_SCHEMA,
|
||||
(instantiator) -> new TestEcsFormatter(instantiator.getArg(ApplicationMetadata.class),
|
||||
(instantiator) -> new TestEcsFormatter(instantiator.getArg(Environment.class),
|
||||
instantiator.getArg(StringBuilder.class)));
|
||||
}
|
||||
|
||||
|
@ -84,7 +86,7 @@ class StructuredLogFormatterFactoryTests {
|
|||
@Test
|
||||
void getUsingClassNameInjectsApplicationMetadata() {
|
||||
TestEcsFormatter formatter = (TestEcsFormatter) this.factory.get(TestEcsFormatter.class.getName());
|
||||
assertThat(formatter.getMetadata()).isSameAs(this.applicationMetadata);
|
||||
assertThat(formatter.getEnvironment()).isSameAs(this.environment);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -103,22 +105,22 @@ class StructuredLogFormatterFactoryTests {
|
|||
|
||||
static class TestEcsFormatter implements StructuredLogFormatter<LogEvent> {
|
||||
|
||||
private ApplicationMetadata metadata;
|
||||
private Environment environment;
|
||||
|
||||
private StringBuilder custom;
|
||||
|
||||
TestEcsFormatter(ApplicationMetadata metadata, StringBuilder custom) {
|
||||
this.metadata = metadata;
|
||||
TestEcsFormatter(Environment environment, StringBuilder custom) {
|
||||
this.environment = environment;
|
||||
this.custom = custom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogEvent event) {
|
||||
return "formatted " + this.metadata.version();
|
||||
return "formatted " + this.environment.getProperty("logging.structured.ecs.service.version");
|
||||
}
|
||||
|
||||
ApplicationMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
StringBuilder getCustom() {
|
||||
|
@ -129,8 +131,8 @@ class StructuredLogFormatterFactoryTests {
|
|||
|
||||
static class ExtendedTestEcsFormatter extends TestEcsFormatter {
|
||||
|
||||
ExtendedTestEcsFormatter(ApplicationMetadata metadata, StringBuilder custom) {
|
||||
super(metadata, custom);
|
||||
ExtendedTestEcsFormatter(Environment environment, StringBuilder custom) {
|
||||
super(environment, custom);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,23 +19,23 @@ package smoketest.structuredlogging.log4j2;
|
|||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.impl.ThrowableProxy;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
|
||||
public class CustomStructuredLogFormatter implements StructuredLogFormatter<LogEvent> {
|
||||
|
||||
private final ApplicationMetadata metadata;
|
||||
private final ApplicationPid pid;
|
||||
|
||||
public CustomStructuredLogFormatter(ApplicationMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
public CustomStructuredLogFormatter(ApplicationPid pid) {
|
||||
this.pid = pid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(LogEvent event) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("epoch=").append(event.getInstant().getEpochMillisecond());
|
||||
if (this.metadata.pid() != null) {
|
||||
result.append(" pid=").append(this.metadata.pid());
|
||||
if (this.pid.isAvailable()) {
|
||||
result.append(" pid=").append(this.pid);
|
||||
}
|
||||
result.append(" msg=\"").append(event.getMessage().getFormattedMessage()).append('"');
|
||||
ThrowableProxy throwable = event.getThrownProxy();
|
||||
|
|
|
@ -20,17 +20,17 @@ import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
|
|||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.classic.spi.IThrowableProxy;
|
||||
|
||||
import org.springframework.boot.logging.structured.ApplicationMetadata;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.system.ApplicationPid;
|
||||
|
||||
public class CustomStructuredLogFormatter implements StructuredLogFormatter<ILoggingEvent> {
|
||||
|
||||
private final ApplicationPid pid;
|
||||
|
||||
private final ThrowableProxyConverter throwableProxyConverter;
|
||||
|
||||
private final ApplicationMetadata metadata;
|
||||
|
||||
public CustomStructuredLogFormatter(ApplicationMetadata metadata, ThrowableProxyConverter throwableProxyConverter) {
|
||||
this.metadata = metadata;
|
||||
public CustomStructuredLogFormatter(ApplicationPid pid, ThrowableProxyConverter throwableProxyConverter) {
|
||||
this.pid = pid;
|
||||
this.throwableProxyConverter = throwableProxyConverter;
|
||||
}
|
||||
|
||||
|
@ -38,8 +38,8 @@ public class CustomStructuredLogFormatter implements StructuredLogFormatter<ILog
|
|||
public String format(ILoggingEvent event) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("epoch=").append(event.getInstant().toEpochMilli());
|
||||
if (this.metadata.pid() != null) {
|
||||
result.append(" pid=").append(this.metadata.pid());
|
||||
if (this.pid.isAvailable()) {
|
||||
result.append(" pid=").append(this.pid);
|
||||
}
|
||||
result.append(" msg=\"").append(event.getFormattedMessage()).append('"');
|
||||
IThrowableProxy throwable = event.getThrowableProxy();
|
||||
|
|
Loading…
Reference in New Issue