Allow structured logging with relocated or disabled context elements
Update `StructuredLoggingJsonProperties` to support context properties that allows MDC data to not be logged, or to be logged in a different location. Closes gh-45218
This commit is contained in:
parent
fdd4abc895
commit
0c32011626
|
@ -16,7 +16,6 @@
|
|||
|
||||
package org.springframework.boot.logging.log4j2;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
@ -31,7 +30,8 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
|||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaPairs;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.ContextPairs.Pairs;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
|
@ -49,12 +49,12 @@ import org.springframework.util.ObjectUtils;
|
|||
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
|
||||
|
||||
ElasticCommonSchemaStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, members), customizer);
|
||||
ContextPairs contextPairs, StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, members), customizer);
|
||||
}
|
||||
|
||||
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
JsonWriter.Members<LogEvent> members) {
|
||||
ContextPairs contextPairs, JsonWriter.Members<LogEvent> members) {
|
||||
Extractor extractor = new Extractor(stackTracePrinter);
|
||||
members.add("@timestamp", LogEvent::getInstant).as(ElasticCommonSchemaStructuredLogFormatter::asTimestamp);
|
||||
members.add("log").usingMembers((log) -> {
|
||||
|
@ -68,9 +68,7 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
|
|||
ElasticCommonSchemaProperties.get(environment).jsonMembers(members);
|
||||
members.add("message", LogEvent::getMessage).as(StructuredMessage::get);
|
||||
members.from(LogEvent::getContextData)
|
||||
.whenNot(ReadOnlyStringMap::isEmpty)
|
||||
.as((contextData) -> ElasticCommonSchemaPairs.nested((nested) -> contextData.forEach(nested::accept)))
|
||||
.usingPairs(Map::forEach);
|
||||
.usingPairs(contextPairs.nested(ElasticCommonSchemaStructuredLogFormatter::addContextDataPairs));
|
||||
members.from(LogEvent::getThrownProxy).whenNotNull().usingMembers((thrownProxyMembers) -> {
|
||||
thrownProxyMembers.add("error").usingMembers((error) -> {
|
||||
error.add("type", ThrowableProxy::getThrowable).whenNotNull().as(ObjectUtils::nullSafeClassName);
|
||||
|
@ -85,6 +83,10 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
|
|||
members.add("ecs").usingMembers((ecs) -> ecs.add("version", "8.11"));
|
||||
}
|
||||
|
||||
private static void addContextDataPairs(Pairs<ReadOnlyStringMap> contextPairs) {
|
||||
contextPairs.add((contextData, pairs) -> contextData.forEach(pairs::accept));
|
||||
}
|
||||
|
||||
private static java.time.Instant asTimestamp(Instant instant) {
|
||||
return java.time.Instant.ofEpochMilli(instant.getEpochMillisecond()).plusNanos(instant.getNanoOfMillisecond());
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.springframework.boot.logging.log4j2;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -36,13 +36,13 @@ import org.springframework.boot.json.JsonWriter.Members;
|
|||
import org.springframework.boot.json.WritableJson;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
@ -72,12 +72,12 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
|
|||
private static final Set<String> ADDITIONAL_FIELD_ILLEGAL_KEYS = Set.of("id", "_id");
|
||||
|
||||
GraylogExtendedLogFormatStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, members), customizer);
|
||||
ContextPairs contextPairs, StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, members), customizer);
|
||||
}
|
||||
|
||||
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
JsonWriter.Members<LogEvent> members) {
|
||||
ContextPairs contextPairs, JsonWriter.Members<LogEvent> members) {
|
||||
Extractor extractor = new Extractor(stackTracePrinter);
|
||||
members.add("version", "1.1");
|
||||
members.add("short_message", LogEvent::getMessage)
|
||||
|
@ -93,7 +93,8 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
|
|||
members.add("_log_logger", LogEvent::getLoggerName);
|
||||
members.from(LogEvent::getContextData)
|
||||
.whenNot(ReadOnlyStringMap::isEmpty)
|
||||
.usingPairs(GraylogExtendedLogFormatStructuredLogFormatter::createAdditionalFields);
|
||||
.usingPairs(contextPairs.flat(additionalFieldJoiner(),
|
||||
GraylogExtendedLogFormatStructuredLogFormatter::addContextDataPairs));
|
||||
members.add()
|
||||
.whenNotNull(LogEvent::getThrownProxy)
|
||||
.usingMembers((thrownProxyMembers) -> throwableMembers(thrownProxyMembers, extractor));
|
||||
|
@ -135,25 +136,23 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
|
|||
members.add("_error_message", (event) -> event.getThrownProxy().getMessage());
|
||||
}
|
||||
|
||||
private static void createAdditionalFields(ReadOnlyStringMap contextData, BiConsumer<Object, Object> pairs) {
|
||||
contextData.forEach((name, value) -> createAdditionalField(name, value, pairs));
|
||||
private static void addContextDataPairs(ContextPairs.Pairs<ReadOnlyStringMap> contextPairs) {
|
||||
contextPairs.add((contextData, pairs) -> contextData.forEach(pairs::accept));
|
||||
}
|
||||
|
||||
private static void createAdditionalField(String name, Object value, BiConsumer<Object, Object> pairs) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
if (!FIELD_NAME_VALID_PATTERN.matcher(name).matches()) {
|
||||
logger.warn(LogMessage.format("'%s' is not a valid field name according to GELF standard", name));
|
||||
return;
|
||||
}
|
||||
if (ADDITIONAL_FIELD_ILLEGAL_KEYS.contains(name)) {
|
||||
logger.warn(LogMessage.format("'%s' is an illegal field name according to GELF standard", name));
|
||||
return;
|
||||
}
|
||||
pairs.accept(asAdditionalFieldName(name), value);
|
||||
}
|
||||
|
||||
private static Object asAdditionalFieldName(String name) {
|
||||
return (!name.startsWith("_")) ? "_" + name : name;
|
||||
private static BinaryOperator<String> additionalFieldJoiner() {
|
||||
return (prefix, name) -> {
|
||||
name = prefix + name;
|
||||
if (!FIELD_NAME_VALID_PATTERN.matcher(name).matches()) {
|
||||
logger.warn(LogMessage.format("'%s' is not a valid field name according to GELF standard", name));
|
||||
return null;
|
||||
}
|
||||
if (ADDITIONAL_FIELD_ILLEGAL_KEYS.contains(name)) {
|
||||
logger.warn(LogMessage.format("'%s' is an illegal field name according to GELF standard", name));
|
||||
return null;
|
||||
}
|
||||
return (!name.startsWith("_")) ? "_" + name : name;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.logging.log4j.util.ReadOnlyStringMap;
|
|||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
|
||||
|
@ -44,12 +45,13 @@ import org.springframework.util.CollectionUtils;
|
|||
*/
|
||||
class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
|
||||
|
||||
LogstashStructuredLogFormatter(StackTracePrinter stackTracePrinter,
|
||||
LogstashStructuredLogFormatter(StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
|
||||
StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(stackTracePrinter, members), customizer);
|
||||
super((members) -> jsonMembers(stackTracePrinter, contextPairs, members), customizer);
|
||||
}
|
||||
|
||||
private static void jsonMembers(StackTracePrinter stackTracePrinter, JsonWriter.Members<LogEvent> members) {
|
||||
private static void jsonMembers(StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
|
||||
JsonWriter.Members<LogEvent> members) {
|
||||
Extractor extractor = new Extractor(stackTracePrinter);
|
||||
members.add("@timestamp", LogEvent::getInstant).as(LogstashStructuredLogFormatter::asTimestamp);
|
||||
members.add("@version", "1");
|
||||
|
@ -60,7 +62,7 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<Lo
|
|||
members.add("level_value", LogEvent::getLevel).as(Level::intLevel);
|
||||
members.from(LogEvent::getContextData)
|
||||
.whenNot(ReadOnlyStringMap::isEmpty)
|
||||
.usingPairs((contextData, pairs) -> contextData.forEach(pairs::accept));
|
||||
.usingPairs(contextPairs.flat("_", LogstashStructuredLogFormatter::addContextDataPairs));
|
||||
members.add("tags", LogEvent::getMarker)
|
||||
.whenNotNull()
|
||||
.as(LogstashStructuredLogFormatter::getMarkers)
|
||||
|
@ -75,6 +77,10 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<Lo
|
|||
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(offsetDateTime);
|
||||
}
|
||||
|
||||
private static void addContextDataPairs(ContextPairs.Pairs<ReadOnlyStringMap> contextPairs) {
|
||||
contextPairs.add((contextData, pairs) -> contextData.forEach(pairs::accept));
|
||||
}
|
||||
|
||||
private static Set<String> getMarkers(Marker marker) {
|
||||
Set<String> result = new TreeSet<>();
|
||||
addMarkers(result, marker);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.logging.log4j.core.layout.AbstractStringLayout;
|
|||
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
|
||||
|
@ -113,25 +114,29 @@ final class StructuredLogLayout extends AbstractStringLayout {
|
|||
private ElasticCommonSchemaStructuredLogFormatter createEcsFormatter(Instantiator<?> instantiator) {
|
||||
Environment environment = instantiator.getArg(Environment.class);
|
||||
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
|
||||
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
|
||||
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
|
||||
.getArg(StructuredLoggingJsonMembersCustomizer.class);
|
||||
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, jsonMembersCustomizer);
|
||||
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, contextPairs,
|
||||
jsonMembersCustomizer);
|
||||
}
|
||||
|
||||
private GraylogExtendedLogFormatStructuredLogFormatter createGraylogFormatter(Instantiator<?> instantiator) {
|
||||
Environment environment = instantiator.getArg(Environment.class);
|
||||
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
|
||||
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
|
||||
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
|
||||
.getArg(StructuredLoggingJsonMembersCustomizer.class);
|
||||
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter,
|
||||
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter, contextPairs,
|
||||
jsonMembersCustomizer);
|
||||
}
|
||||
|
||||
private LogstashStructuredLogFormatter createLogstashFormatter(Instantiator<?> instantiator) {
|
||||
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
|
||||
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
|
||||
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
|
||||
.getArg(StructuredLoggingJsonMembersCustomizer.class);
|
||||
return new LogstashStructuredLogFormatter(stackTracePrinter, jsonMembersCustomizer);
|
||||
return new LogstashStructuredLogFormatter(stackTracePrinter, contextPairs, jsonMembersCustomizer);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package org.springframework.boot.logging.logback;
|
|||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
@ -30,9 +29,10 @@ import org.slf4j.Marker;
|
|||
import org.slf4j.event.KeyValuePair;
|
||||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.json.JsonWriter.PairExtractor;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaPairs;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
|
@ -48,13 +48,19 @@ import org.springframework.core.env.Environment;
|
|||
*/
|
||||
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<ILoggingEvent> {
|
||||
|
||||
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
|
||||
(pair) -> pair.value);
|
||||
|
||||
ElasticCommonSchemaStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
ThrowableProxyConverter throwableProxyConverter, StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, throwableProxyConverter, members), customizer);
|
||||
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
|
||||
StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, throwableProxyConverter, members),
|
||||
customizer);
|
||||
}
|
||||
|
||||
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
|
||||
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
|
||||
JsonWriter.Members<ILoggingEvent> members) {
|
||||
Extractor extractor = new Extractor(stackTracePrinter, throwableProxyConverter);
|
||||
members.add("@timestamp", ILoggingEvent::getInstant);
|
||||
members.add("log").usingMembers((log) -> {
|
||||
|
@ -67,14 +73,10 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
|
|||
});
|
||||
ElasticCommonSchemaProperties.get(environment).jsonMembers(members);
|
||||
members.add("message", ILoggingEvent::getFormattedMessage);
|
||||
members.from(ILoggingEvent::getMDCPropertyMap)
|
||||
.whenNotEmpty()
|
||||
.as(ElasticCommonSchemaPairs::nested)
|
||||
.usingPairs(Map::forEach);
|
||||
members.from(ILoggingEvent::getKeyValuePairs)
|
||||
.whenNotEmpty()
|
||||
.as(ElasticCommonSchemaStructuredLogFormatter::nested)
|
||||
.usingPairs(Map::forEach);
|
||||
members.add().usingPairs(contextPairs.nested((pairs) -> {
|
||||
pairs.addMapEntries(ILoggingEvent::getMDCPropertyMap);
|
||||
pairs.add(ILoggingEvent::getKeyValuePairs, keyValuePairExtractor);
|
||||
}));
|
||||
members.add().whenNotNull(ILoggingEvent::getThrowableProxy).usingMembers((throwableMembers) -> {
|
||||
throwableMembers.add("error").usingMembers((error) -> {
|
||||
error.add("type", ILoggingEvent::getThrowableProxy).as(IThrowableProxy::getClassName);
|
||||
|
@ -89,11 +91,6 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
|
|||
members.add("ecs").usingMembers((ecs) -> ecs.add("version", "8.11"));
|
||||
}
|
||||
|
||||
private static Map<String, Object> nested(List<KeyValuePair> keyValuePairs) {
|
||||
return ElasticCommonSchemaPairs.nested((nested) -> keyValuePairs
|
||||
.forEach((keyValuePair) -> nested.accept(keyValuePair.key, keyValuePair.value)));
|
||||
}
|
||||
|
||||
private static Set<String> getMarkers(List<Marker> markers) {
|
||||
Set<String> result = new TreeSet<>();
|
||||
addMarkers(result, markers.iterator());
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
package org.springframework.boot.logging.logback;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
|
||||
|
@ -33,17 +32,17 @@ import org.slf4j.event.KeyValuePair;
|
|||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.json.JsonWriter.Members;
|
||||
import org.springframework.boot.json.JsonWriter.PairExtractor;
|
||||
import org.springframework.boot.json.WritableJson;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -57,6 +56,9 @@ import org.springframework.util.StringUtils;
|
|||
*/
|
||||
class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructuredLogFormatter<ILoggingEvent> {
|
||||
|
||||
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
|
||||
(pair) -> pair.value);
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GraylogExtendedLogFormatStructuredLogFormatter.class);
|
||||
|
||||
/**
|
||||
|
@ -72,12 +74,15 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
|
|||
private static final Set<String> ADDITIONAL_FIELD_ILLEGAL_KEYS = Set.of("id", "_id");
|
||||
|
||||
GraylogExtendedLogFormatStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
ThrowableProxyConverter throwableProxyConverter, StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, throwableProxyConverter, members), customizer);
|
||||
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
|
||||
StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, throwableProxyConverter, members),
|
||||
customizer);
|
||||
}
|
||||
|
||||
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
|
||||
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
|
||||
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
|
||||
JsonWriter.Members<ILoggingEvent> members) {
|
||||
Extractor extractor = new Extractor(stackTracePrinter, throwableProxyConverter);
|
||||
members.add("version", "1.1");
|
||||
members.add("short_message", ILoggingEvent::getFormattedMessage)
|
||||
|
@ -91,12 +96,10 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
|
|||
members.add("_process_thread_name", ILoggingEvent::getThreadName);
|
||||
GraylogExtendedLogFormatProperties.get(environment).jsonMembers(members);
|
||||
members.add("_log_logger", ILoggingEvent::getLoggerName);
|
||||
members.from(ILoggingEvent::getMDCPropertyMap)
|
||||
.when((mdc) -> !CollectionUtils.isEmpty(mdc))
|
||||
.usingPairs((mdc, pairs) -> mdc.forEach((key, value) -> createAdditionalField(key, value, pairs)));
|
||||
members.from(ILoggingEvent::getKeyValuePairs)
|
||||
.when((keyValuePairs) -> !CollectionUtils.isEmpty(keyValuePairs))
|
||||
.usingPairs(GraylogExtendedLogFormatStructuredLogFormatter::createAdditionalField);
|
||||
members.add().usingPairs(contextPairs.flat(additionalFieldJoiner(), (pairs) -> {
|
||||
pairs.addMapEntries(ILoggingEvent::getMDCPropertyMap);
|
||||
pairs.add(ILoggingEvent::getKeyValuePairs, keyValuePairExtractor);
|
||||
}));
|
||||
members.add()
|
||||
.whenNotNull(ILoggingEvent::getThrowableProxy)
|
||||
.usingMembers((throwableMembers) -> throwableMembers(throwableMembers, extractor));
|
||||
|
@ -125,25 +128,19 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
|
|||
members.add("_error_message", ILoggingEvent::getThrowableProxy).as(IThrowableProxy::getMessage);
|
||||
}
|
||||
|
||||
private static void createAdditionalField(List<KeyValuePair> keyValuePairs, BiConsumer<Object, Object> pairs) {
|
||||
keyValuePairs.forEach((keyValuePair) -> createAdditionalField(keyValuePair.key, keyValuePair.value, pairs));
|
||||
}
|
||||
|
||||
private static void createAdditionalField(String name, Object value, BiConsumer<Object, Object> pairs) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
if (!FIELD_NAME_VALID_PATTERN.matcher(name).matches()) {
|
||||
logger.warn(LogMessage.format("'%s' is not a valid field name according to GELF standard", name));
|
||||
return;
|
||||
}
|
||||
if (ADDITIONAL_FIELD_ILLEGAL_KEYS.contains(name)) {
|
||||
logger.warn(LogMessage.format("'%s' is an illegal field name according to GELF standard", name));
|
||||
return;
|
||||
}
|
||||
pairs.accept(asAdditionalFieldName(name), value);
|
||||
}
|
||||
|
||||
private static Object asAdditionalFieldName(String name) {
|
||||
return (!name.startsWith("_")) ? "_" + name : name;
|
||||
private static BinaryOperator<String> additionalFieldJoiner() {
|
||||
return (prefix, name) -> {
|
||||
name = prefix + name;
|
||||
if (!FIELD_NAME_VALID_PATTERN.matcher(name).matches()) {
|
||||
logger.warn(LogMessage.format("'%s' is not a valid field name according to GELF standard", name));
|
||||
return null;
|
||||
}
|
||||
if (ADDITIONAL_FIELD_ILLEGAL_KEYS.contains(name)) {
|
||||
logger.warn(LogMessage.format("'%s' is an illegal field name according to GELF standard", name));
|
||||
return null;
|
||||
}
|
||||
return (!name.startsWith("_")) ? "_" + name : name;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.springframework.boot.json.JsonWriter;
|
|||
import org.springframework.boot.json.JsonWriter.PairExtractor;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
|
||||
|
@ -50,12 +51,12 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<IL
|
|||
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
|
||||
(pair) -> pair.value);
|
||||
|
||||
LogstashStructuredLogFormatter(StackTracePrinter stackTracePrinter, ThrowableProxyConverter throwableProxyConverter,
|
||||
StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(stackTracePrinter, throwableProxyConverter, members), customizer);
|
||||
LogstashStructuredLogFormatter(StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
|
||||
ThrowableProxyConverter throwableProxyConverter, StructuredLoggingJsonMembersCustomizer<?> customizer) {
|
||||
super((members) -> jsonMembers(stackTracePrinter, contextPairs, throwableProxyConverter, members), customizer);
|
||||
}
|
||||
|
||||
private static void jsonMembers(StackTracePrinter stackTracePrinter,
|
||||
private static void jsonMembers(StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
|
||||
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
|
||||
Extractor extractor = new Extractor(stackTracePrinter, throwableProxyConverter);
|
||||
members.add("@timestamp", ILoggingEvent::getInstant).as(LogstashStructuredLogFormatter::asTimestamp);
|
||||
|
@ -65,10 +66,10 @@ class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<IL
|
|||
members.add("thread_name", ILoggingEvent::getThreadName);
|
||||
members.add("level", ILoggingEvent::getLevel);
|
||||
members.add("level_value", ILoggingEvent::getLevel).as(Level::toInt);
|
||||
members.addMapEntries(ILoggingEvent::getMDCPropertyMap);
|
||||
members.from(ILoggingEvent::getKeyValuePairs)
|
||||
.whenNotEmpty()
|
||||
.usingExtractedPairs(Iterable::forEach, keyValuePairExtractor);
|
||||
members.add().usingPairs(contextPairs.flat("_", (pairs) -> {
|
||||
pairs.addMapEntries(ILoggingEvent::getMDCPropertyMap);
|
||||
pairs.add(ILoggingEvent::getKeyValuePairs, keyValuePairExtractor);
|
||||
}));
|
||||
members.add("tags", ILoggingEvent::getMarkerList)
|
||||
.whenNotNull()
|
||||
.as(LogstashStructuredLogFormatter::getMarkers)
|
||||
|
|
|
@ -26,6 +26,7 @@ import ch.qos.logback.core.encoder.EncoderBase;
|
|||
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
|
||||
import org.springframework.boot.logging.structured.ContextPairs;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatter;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
|
||||
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
|
||||
|
@ -90,29 +91,33 @@ public class StructuredLogEncoder extends EncoderBase<ILoggingEvent> {
|
|||
private StructuredLogFormatter<ILoggingEvent> createEcsFormatter(Instantiator<?> instantiator) {
|
||||
Environment environment = instantiator.getArg(Environment.class);
|
||||
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
|
||||
ContextPairs contextParis = instantiator.getArg(ContextPairs.class);
|
||||
ThrowableProxyConverter throwableProxyConverter = instantiator.getArg(ThrowableProxyConverter.class);
|
||||
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
|
||||
.getArg(StructuredLoggingJsonMembersCustomizer.class);
|
||||
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, throwableProxyConverter,
|
||||
jsonMembersCustomizer);
|
||||
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, contextParis,
|
||||
throwableProxyConverter, jsonMembersCustomizer);
|
||||
}
|
||||
|
||||
private StructuredLogFormatter<ILoggingEvent> createGraylogFormatter(Instantiator<?> instantiator) {
|
||||
Environment environment = instantiator.getArg(Environment.class);
|
||||
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
|
||||
ContextPairs contextParis = instantiator.getArg(ContextPairs.class);
|
||||
ThrowableProxyConverter throwableProxyConverter = instantiator.getArg(ThrowableProxyConverter.class);
|
||||
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
|
||||
.getArg(StructuredLoggingJsonMembersCustomizer.class);
|
||||
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter,
|
||||
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter, contextParis,
|
||||
throwableProxyConverter, jsonMembersCustomizer);
|
||||
}
|
||||
|
||||
private StructuredLogFormatter<ILoggingEvent> createLogstashFormatter(Instantiator<?> instantiator) {
|
||||
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
|
||||
ContextPairs contextParis = instantiator.getArg(ContextPairs.class);
|
||||
ThrowableProxyConverter throwableProxyConverter = instantiator.getArg(ThrowableProxyConverter.class);
|
||||
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
|
||||
.getArg(StructuredLoggingJsonMembersCustomizer.class);
|
||||
return new LogstashStructuredLogFormatter(stackTracePrinter, throwableProxyConverter, jsonMembersCustomizer);
|
||||
return new LogstashStructuredLogFormatter(stackTracePrinter, contextParis, throwableProxyConverter,
|
||||
jsonMembersCustomizer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* Copyright 2012-2025 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.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.boot.json.JsonWriter;
|
||||
import org.springframework.boot.json.JsonWriter.PairExtractor;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Helper that can be used to add JSON pairs from context data (typically the logger MDC)
|
||||
* in the correct location (or drop them altogether).
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public class ContextPairs {
|
||||
|
||||
private final boolean include;
|
||||
|
||||
private final String prefix;
|
||||
|
||||
ContextPairs(boolean include, String prefix) {
|
||||
this.include = include;
|
||||
this.prefix = (prefix != null) ? prefix : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs using flat naming.
|
||||
* @param <T> the item type
|
||||
* @param delimeter the delimiter used if there is a prefix
|
||||
* @param pairs callback to add all the pairs
|
||||
* @return a {@link BiConsumer} for use with the {@link JsonWriter}
|
||||
*/
|
||||
public <T> BiConsumer<T, BiConsumer<String, Object>> flat(String delimeter, Consumer<Pairs<T>> pairs) {
|
||||
return flat(joinWith(delimeter), pairs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs using flat naming.
|
||||
* @param <T> the item type
|
||||
* @param joiner the function used to join the prefix and name
|
||||
* @param pairs callback to add all the pairs
|
||||
* @return a {@link BiConsumer} for use with the {@link JsonWriter}
|
||||
*/
|
||||
public <T> BiConsumer<T, BiConsumer<String, Object>> flat(BinaryOperator<String> joiner, Consumer<Pairs<T>> pairs) {
|
||||
return (!this.include) ? none() : new Pairs<>(joiner, pairs)::flat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs using nested naming (for example as used in ECS).
|
||||
* @param <T> the item type
|
||||
* @param pairs callback to add all the pairs
|
||||
* @return a {@link BiConsumer} for use with the {@link JsonWriter}
|
||||
*/
|
||||
public <T> BiConsumer<T, BiConsumer<String, Object>> nested(Consumer<Pairs<T>> pairs) {
|
||||
return (!this.include) ? none() : new Pairs<>(joinWith("."), pairs)::nested;
|
||||
}
|
||||
|
||||
private <T, V> BiConsumer<T, BiConsumer<String, V>> none() {
|
||||
return (item, pairs) -> {
|
||||
};
|
||||
}
|
||||
|
||||
private BinaryOperator<String> joinWith(String delimeter) {
|
||||
return (prefix, name) -> {
|
||||
StringBuilder joined = new StringBuilder(prefix.length() + delimeter.length() + name.length());
|
||||
joined.append(prefix);
|
||||
if (!prefix.isEmpty() && !prefix.endsWith(delimeter) && !name.startsWith(delimeter)) {
|
||||
joined.append(delimeter);
|
||||
}
|
||||
joined.append(name);
|
||||
return joined.toString();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to add pairs.
|
||||
*
|
||||
* @param <T> the item type
|
||||
*/
|
||||
public class Pairs<T> {
|
||||
|
||||
private final BinaryOperator<String> joiner;
|
||||
|
||||
private final List<BiConsumer<T, BiConsumer<String, ?>>> addedPairs;
|
||||
|
||||
Pairs(BinaryOperator<String> joiner, Consumer<Pairs<T>> pairs) {
|
||||
this.joiner = joiner;
|
||||
this.addedPairs = new ArrayList<>();
|
||||
pairs.accept(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs from map entries.
|
||||
* @param <K> the map key type
|
||||
* @param <V> the map value type
|
||||
* @param extractor the extractor used to provide the map
|
||||
*/
|
||||
public <K, V> void addMapEntries(Function<T, Map<String, V>> extractor) {
|
||||
add(extractor.andThen(Map::entrySet), Map.Entry::getKey, Map.Entry::getValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs from an iterable.
|
||||
* @param elementsExtractor the extractor used to provide the iterable
|
||||
* @param pairExtractor the extractor used to provide the name and value
|
||||
* @param <E> the element type
|
||||
* @param <V> the value type
|
||||
*/
|
||||
public <E, V> void add(Function<T, Iterable<E>> elementsExtractor, PairExtractor<E> pairExtractor) {
|
||||
add(elementsExtractor, pairExtractor::getName, pairExtractor::getValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs from an iterable.
|
||||
* @param elementsExtractor the extractor used to provide the iterable
|
||||
* @param <E> the element type
|
||||
* @param <V> the value type
|
||||
* @param nameExtractor the extractor used to provide the name
|
||||
* @param valueExtractor the extractor used to provide the value
|
||||
*/
|
||||
public <E, V> void add(Function<T, Iterable<E>> elementsExtractor, Function<E, String> nameExtractor,
|
||||
Function<E, V> valueExtractor) {
|
||||
add((item, pairs) -> {
|
||||
Iterable<E> elements = elementsExtractor.apply(item);
|
||||
if (elements != null) {
|
||||
elements.forEach((element) -> {
|
||||
String name = nameExtractor.apply(element);
|
||||
V value = valueExtractor.apply(element);
|
||||
pairs.accept(name, value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add pairs using the given callback.
|
||||
* @param <V> the value type
|
||||
* @param pairs callback provided with the item and consumer that can be called to
|
||||
* actually add the pairs
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public <V> void add(BiConsumer<T, BiConsumer<String, V>> pairs) {
|
||||
this.addedPairs.add((BiConsumer) pairs);
|
||||
}
|
||||
|
||||
void flat(T item, BiConsumer<String, Object> pairs) {
|
||||
this.addedPairs.forEach((action) -> action.accept(item, joining(pairs)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void nested(T item, BiConsumer<String, Object> pairs) {
|
||||
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
|
||||
this.addedPairs.forEach((addedPair) -> {
|
||||
addedPair.accept(item, joining((name, value) -> {
|
||||
List<String> nameParts = List.of(name.split("\\."));
|
||||
Map<String, Object> destination = result;
|
||||
for (int i = 0; i < nameParts.size() - 1; i++) {
|
||||
Object existing = destination.computeIfAbsent(nameParts.get(i), (key) -> new LinkedHashMap<>());
|
||||
if (!(existing instanceof Map)) {
|
||||
String common = nameParts.subList(0, i + 1).stream().collect(Collectors.joining("."));
|
||||
throw new IllegalStateException(
|
||||
"Duplicate nested pairs added under '%s'".formatted(common));
|
||||
}
|
||||
destination = (Map<String, Object>) existing;
|
||||
}
|
||||
Object previous = destination.put(nameParts.get(nameParts.size() - 1), value);
|
||||
Assert.state(previous == null, () -> "Duplicate nested pairs added under '%s'".formatted(name));
|
||||
}));
|
||||
});
|
||||
result.forEach(pairs::accept);
|
||||
}
|
||||
|
||||
private <V> BiConsumer<String, V> joining(BiConsumer<String, V> pairs) {
|
||||
return (name, value) -> {
|
||||
name = this.joiner.apply(ContextPairs.this.prefix, (name != null) ? name : "");
|
||||
if (StringUtils.hasLength(name)) {
|
||||
pairs.accept(name, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2025 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.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Utility to help with writing ElasticCommonSchema pairs in their nested form.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public final class ElasticCommonSchemaPairs {
|
||||
|
||||
private ElasticCommonSchemaPairs() {
|
||||
}
|
||||
|
||||
public static Map<String, Object> nested(Map<String, String> map) {
|
||||
return nested(map::forEach);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, V> Map<String, Object> nested(Consumer<BiConsumer<K, V>> nested) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
nested.accept((name, value) -> {
|
||||
List<String> nameParts = List.of(name.toString().split("\\."));
|
||||
Map<String, Object> destination = result;
|
||||
for (int i = 0; i < nameParts.size() - 1; i++) {
|
||||
Object existing = destination.computeIfAbsent(nameParts.get(i), (key) -> new LinkedHashMap<>());
|
||||
if (!(existing instanceof Map)) {
|
||||
String common = nameParts.subList(0, i + 1).stream().collect(Collectors.joining("."));
|
||||
throw new IllegalStateException("Duplicate ECS pairs added under '%s'".formatted(common));
|
||||
}
|
||||
destination = (Map<String, Object>) existing;
|
||||
}
|
||||
Object previous = destination.put(nameParts.get(nameParts.size() - 1), value);
|
||||
Assert.state(previous == null, () -> "Duplicate ECS pairs added under '%s'".formatted(name));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import org.springframework.core.env.Environment;
|
|||
* <li>{@link Environment}</li>
|
||||
* <li>{@link StructuredLoggingJsonMembersCustomizer}</li>
|
||||
* <li>{@link StackTracePrinter} (may be {@code null})</li>
|
||||
* <li>{@link ContextPairs}</li>
|
||||
* </ul>
|
||||
* When using Logback, implementing classes can also use the following parameter types in
|
||||
* the constructor:
|
||||
|
|
|
@ -25,6 +25,7 @@ import java.util.function.Consumer;
|
|||
|
||||
import org.springframework.boot.json.JsonWriter.Members;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.Context;
|
||||
import org.springframework.boot.util.Instantiator;
|
||||
import org.springframework.boot.util.Instantiator.AvailableParameters;
|
||||
import org.springframework.boot.util.Instantiator.FailureHandler;
|
||||
|
@ -86,6 +87,7 @@ public class StructuredLogFormatterFactory<E> {
|
|||
allAvailableParameters.add(StructuredLoggingJsonMembersCustomizer.class,
|
||||
(type) -> getStructuredLoggingJsonMembersCustomizer(properties));
|
||||
allAvailableParameters.add(StackTracePrinter.class, (type) -> getStackTracePrinter(properties));
|
||||
allAvailableParameters.add(ContextPairs.class, (type) -> getContextPairs(properties));
|
||||
if (availableParameters != null) {
|
||||
availableParameters.accept(allAvailableParameters);
|
||||
}
|
||||
|
@ -122,6 +124,12 @@ public class StructuredLogFormatterFactory<E> {
|
|||
return (properties != null && properties.stackTrace() != null) ? properties.stackTrace().createPrinter() : null;
|
||||
}
|
||||
|
||||
private ContextPairs getContextPairs(StructuredLoggingJsonProperties properties) {
|
||||
Context contextProperties = (properties != null) ? properties.context() : null;
|
||||
contextProperties = (contextProperties != null) ? contextProperties : new Context(true, null);
|
||||
return new ContextPairs(contextProperties.include(), contextProperties.prefix());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new {@link StructuredLogFormatter} instance for the specified format.
|
||||
* @param format the format requested (either a {@link CommonStructuredLogFormat} ID
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
|||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.bind.DefaultValue;
|
||||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.StandardStackTracePrinter;
|
||||
import org.springframework.boot.util.Instantiator;
|
||||
|
@ -47,11 +48,12 @@ import org.springframework.core.env.Environment;
|
|||
* @param stackTrace stack trace properties
|
||||
* @param customizer the fully qualified names of
|
||||
* {@link StructuredLoggingJsonMembersCustomizer} implementations
|
||||
* @param context context specific properties
|
||||
* @author Phillip Webb
|
||||
* @author Yanming Zhou
|
||||
*/
|
||||
record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude, Map<String, String> rename,
|
||||
Map<String, String> add, StackTrace stackTrace,
|
||||
Map<String, String> add, StackTrace stackTrace, Context context,
|
||||
Set<Class<? extends StructuredLoggingJsonMembersCustomizer<?>>> customizer) {
|
||||
|
||||
StructuredLoggingJsonProperties {
|
||||
|
@ -148,6 +150,18 @@ record StructuredLoggingJsonProperties(Set<String> include, Set<String> exclude,
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties that influence context values (usually elements propagated from the
|
||||
* logging MDC).
|
||||
*
|
||||
* @param include if context elements should be included
|
||||
* @param prefix the prefix to use for context elements
|
||||
* @since 3.5.0
|
||||
*/
|
||||
record Context(@DefaultValue("true") boolean include, String prefix) {
|
||||
|
||||
}
|
||||
|
||||
static class StructuredLoggingJsonPropertiesRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -266,6 +266,16 @@
|
|||
"type": "java.util.Map<java.lang.String,java.lang.String>",
|
||||
"description": "Additional members that should be added to structured logging JSON"
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.json.context.include",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether context data should be included in the JSON."
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.json.context.prefix",
|
||||
"type": "java.lang.String",
|
||||
"description": "The prefix to use when inserting context data."
|
||||
},
|
||||
{
|
||||
"name": "logging.structured.json.customizer",
|
||||
"type": "java.util.Set<java.lang.Class<? extends org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer<?>>>",
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.apache.logging.log4j.message.MapMessage;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.structured.TestContextPairs;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -54,7 +55,8 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL
|
|||
this.environment.setProperty("logging.structured.ecs.service.environment", "test");
|
||||
this.environment.setProperty("logging.structured.ecs.service.node-name", "node-1");
|
||||
this.environment.setProperty("spring.application.pid", "1");
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(this.environment, null, this.customizer);
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(this.environment, null,
|
||||
TestContextPairs.include(), this.customizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -107,7 +109,7 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL
|
|||
@SuppressWarnings("unchecked")
|
||||
void shouldFormatExceptionUsingStackTracePrinter() {
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(this.environment, new SimpleStackTracePrinter(),
|
||||
this.customizer);
|
||||
TestContextPairs.include(), this.customizer);
|
||||
MutableLogEvent event = createEvent();
|
||||
event.setThrown(new RuntimeException("Boom"));
|
||||
Map<String, Object> deserialized = deserialize(this.formatter.format(event));
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.logging.structured.TestContextPairs;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
@ -52,7 +53,8 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
|
|||
this.environment.setProperty("logging.structured.gelf.host", "name");
|
||||
this.environment.setProperty("logging.structured.gelf.service.version", "1.0.0");
|
||||
this.environment.setProperty("spring.application.pid", "1");
|
||||
this.formatter = new GraylogExtendedLogFormatStructuredLogFormatter(this.environment, null, this.customizer);
|
||||
this.formatter = new GraylogExtendedLogFormatStructuredLogFormatter(this.environment, null,
|
||||
TestContextPairs.include(), this.customizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -154,7 +156,7 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
|
|||
@Test
|
||||
void shouldFormatExceptionUsingStackTracePrinter() {
|
||||
this.formatter = new GraylogExtendedLogFormatStructuredLogFormatter(this.environment,
|
||||
new SimpleStackTracePrinter(), this.customizer);
|
||||
new SimpleStackTracePrinter(), TestContextPairs.include(), this.customizer);
|
||||
MutableLogEvent event = createEvent();
|
||||
event.setThrown(new RuntimeException("Boom"));
|
||||
String json = this.formatter.format(event);
|
||||
|
|
|
@ -29,6 +29,8 @@ import org.apache.logging.log4j.message.MapMessage;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.logging.structured.TestContextPairs;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
@ -45,7 +47,7 @@ class LogstashStructuredLogFormatterTests extends AbstractStructuredLoggingTests
|
|||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
this.formatter = new LogstashStructuredLogFormatter(null, this.customizer);
|
||||
this.formatter = new LogstashStructuredLogFormatter(null, TestContextPairs.include(), this.customizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -88,7 +90,8 @@ class LogstashStructuredLogFormatterTests extends AbstractStructuredLoggingTests
|
|||
|
||||
@Test
|
||||
void shouldFormatExceptionWithStackTracePrinter() {
|
||||
this.formatter = new LogstashStructuredLogFormatter(new SimpleStackTracePrinter(), this.customizer);
|
||||
this.formatter = new LogstashStructuredLogFormatter(new SimpleStackTracePrinter(), TestContextPairs.include(),
|
||||
this.customizer);
|
||||
MutableLogEvent event = createEvent();
|
||||
event.setThrown(new RuntimeException("Boom"));
|
||||
String json = this.formatter.format(event);
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
|
||||
import org.springframework.boot.logging.structured.TestContextPairs;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -57,7 +58,7 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL
|
|||
this.environment.setProperty("logging.structured.ecs.service.node-name", "node-1");
|
||||
this.environment.setProperty("spring.application.pid", "1");
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(this.environment, null,
|
||||
getThrowableProxyConverter(), this.customizer);
|
||||
TestContextPairs.include(), getThrowableProxyConverter(), this.customizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -114,7 +115,7 @@ class ElasticCommonSchemaStructuredLogFormatterTests extends AbstractStructuredL
|
|||
@SuppressWarnings("unchecked")
|
||||
void shouldFormatExceptionUsingStackTracePrinter() {
|
||||
this.formatter = new ElasticCommonSchemaStructuredLogFormatter(this.environment, new SimpleStackTracePrinter(),
|
||||
getThrowableProxyConverter(), this.customizer);
|
||||
TestContextPairs.include(), getThrowableProxyConverter(), this.customizer);
|
||||
LoggingEvent event = createEvent();
|
||||
event.setMDCPropertyMap(Collections.emptyMap());
|
||||
event.setThrowableProxy(new ThrowableProxy(new RuntimeException("Boom")));
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.boot.logging.structured.TestContextPairs;
|
||||
import org.springframework.boot.testsupport.system.CapturedOutput;
|
||||
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
@ -56,7 +57,7 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
|
|||
this.environment.setProperty("logging.structured.gelf.service.version", "1.0.0");
|
||||
this.environment.setProperty("spring.application.pid", "1");
|
||||
this.formatter = new GraylogExtendedLogFormatStructuredLogFormatter(this.environment, null,
|
||||
getThrowableProxyConverter(), this.customizer);
|
||||
TestContextPairs.include(), getThrowableProxyConverter(), this.customizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -158,7 +159,8 @@ class GraylogExtendedLogFormatStructuredLogFormatterTests extends AbstractStruct
|
|||
@Test
|
||||
void shouldFormatExceptionUsingStackTracePrinter() {
|
||||
this.formatter = new GraylogExtendedLogFormatStructuredLogFormatter(this.environment,
|
||||
new SimpleStackTracePrinter(), getThrowableProxyConverter(), this.customizer);
|
||||
new SimpleStackTracePrinter(), TestContextPairs.include(), getThrowableProxyConverter(),
|
||||
this.customizer);
|
||||
LoggingEvent event = createEvent();
|
||||
event.setMDCPropertyMap(Collections.emptyMap());
|
||||
event.setThrowableProxy(new ThrowableProxy(new RuntimeException("Boom")));
|
||||
|
|
|
@ -29,6 +29,8 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Marker;
|
||||
|
||||
import org.springframework.boot.logging.structured.TestContextPairs;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
@ -46,7 +48,8 @@ class LogstashStructuredLogFormatterTests extends AbstractStructuredLoggingTests
|
|||
@BeforeEach
|
||||
void setUp() {
|
||||
super.setUp();
|
||||
this.formatter = new LogstashStructuredLogFormatter(null, getThrowableProxyConverter(), this.customizer);
|
||||
this.formatter = new LogstashStructuredLogFormatter(null, TestContextPairs.include(),
|
||||
getThrowableProxyConverter(), this.customizer);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -92,8 +95,8 @@ class LogstashStructuredLogFormatterTests extends AbstractStructuredLoggingTests
|
|||
|
||||
@Test
|
||||
void shouldFormatExceptionWithStackTracePrinter() {
|
||||
this.formatter = new LogstashStructuredLogFormatter(new SimpleStackTracePrinter(), getThrowableProxyConverter(),
|
||||
this.customizer);
|
||||
this.formatter = new LogstashStructuredLogFormatter(new SimpleStackTracePrinter(), TestContextPairs.include(),
|
||||
getThrowableProxyConverter(), this.customizer);
|
||||
LoggingEvent event = createEvent();
|
||||
event.setThrowableProxy(new ThrowableProxy(new RuntimeException("Boom")));
|
||||
event.setMDCPropertyMap(Collections.emptyMap());
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright 2012-2025 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.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
/**
|
||||
* Tests for {@link ContextPairs}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ContextPairsTests {
|
||||
|
||||
@Test
|
||||
void flatWhenIncludeFalseDoesNothing() {
|
||||
ContextPairs contextPairs = new ContextPairs(false, null);
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(contextPairs.flat(".", (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatIncludesName() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, null);
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(contextPairs.flat(".", (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual).containsExactlyEntriesOf(map);
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatWhenPrefixAppliesPrefix() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, "the");
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(contextPairs.flat("_", (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual).containsOnly(entry("the_spring", "boot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatWhenPrefixEndingWithDelimeterAppliesPrefix() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, "the_");
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(contextPairs.flat("_", (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual).containsOnly(entry("the_spring", "boot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatWhenPrefixAndNameStartingWithDelimeterAppliesPrefix() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, "the");
|
||||
Map<String, String> map = Map.of("_spring", "boot");
|
||||
Map<String, Object> actual = apply(contextPairs.flat("_", (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual).containsOnly(entry("the_spring", "boot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatWhenJoinerJoins() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, "the");
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(
|
||||
contextPairs.flat((prefix, name) -> prefix + name, (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual).containsOnly(entry("thespring", "boot"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void flatWhenJoinerReturnsNullFilters() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, "the");
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(
|
||||
contextPairs.flat((prefix, name) -> null, (pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedWhenIncludeFalseDoesNothing() {
|
||||
ContextPairs contextPairs = new ContextPairs(false, null);
|
||||
Map<String, String> map = Map.of("spring", "boot");
|
||||
Map<String, Object> actual = apply(contextPairs.nested((pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
assertThat(actual.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedExpandsNames() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, null);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("a1.b1.c1", "A1B1C1");
|
||||
map.put("a1.b2.c1", "A1B2C1");
|
||||
map.put("a1.b1.c2", "A1B1C2");
|
||||
Map<String, Object> actual = apply(contextPairs.nested((pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
Map<String, Object> a1 = new LinkedHashMap<>();
|
||||
Map<String, Object> b1 = new LinkedHashMap<>();
|
||||
Map<String, Object> b2 = new LinkedHashMap<>();
|
||||
expected.put("a1", a1);
|
||||
a1.put("b1", b1);
|
||||
a1.put("b2", b2);
|
||||
b1.put("c1", "A1B1C1");
|
||||
b1.put("c2", "A1B1C2");
|
||||
b2.put("c1", "A1B2C1");
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedWhenDuplicateInParentThrowsException() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, null);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("a1.b1.c1", "A1B1C1");
|
||||
map.put("a1.b1", "A1B1");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> apply(contextPairs.nested((pairs) -> pairs.addMapEntries((item) -> map))))
|
||||
.withMessage("Duplicate nested pairs added under 'a1.b1'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedWhenDuplicateInLeafThrowsException() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, null);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("a1.b1", "A1B1");
|
||||
map.put("a1.b1.c1", "A1B1C1");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> apply(contextPairs.nested((pairs) -> pairs.addMapEntries((item) -> map))))
|
||||
.withMessage("Duplicate nested pairs added under 'a1.b1'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedWhenPrefixAppliesPrefix() {
|
||||
ContextPairs contextPairs = new ContextPairs(true, "a1");
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("b1.c1", "A1B1C1");
|
||||
map.put("b2.c1", "A1B2C1");
|
||||
map.put("b1.c2", "A1B1C2");
|
||||
Map<String, Object> actual = apply(contextPairs.nested((pairs) -> pairs.addMapEntries((item) -> map)));
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
Map<String, Object> a1 = new LinkedHashMap<>();
|
||||
Map<String, Object> b1 = new LinkedHashMap<>();
|
||||
Map<String, Object> b2 = new LinkedHashMap<>();
|
||||
expected.put("a1", a1);
|
||||
a1.put("b1", b1);
|
||||
a1.put("b2", b2);
|
||||
b1.put("c1", "A1B1C1");
|
||||
b1.put("c2", "A1B1C2");
|
||||
b2.put("c1", "A1B2C1");
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
}
|
||||
|
||||
Map<String, Object> apply(BiConsumer<?, BiConsumer<String, Object>> action) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
action.accept(null, result::put);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2025 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.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ElasticCommonSchemaPairs}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ElasticCommonSchemaPairsTests {
|
||||
|
||||
@Test
|
||||
void nestedExpandsNames() {
|
||||
Map<String, String> map = Map.of("a1.b1.c1", "A1B1C1", "a1.b2.c1", "A1B2C1", "a1.b1.c2", "A1B1C2");
|
||||
Map<String, Object> expected = new LinkedHashMap<>();
|
||||
Map<String, Object> a1 = new LinkedHashMap<>();
|
||||
Map<String, Object> b1 = new LinkedHashMap<>();
|
||||
Map<String, Object> b2 = new LinkedHashMap<>();
|
||||
expected.put("a1", a1);
|
||||
a1.put("b1", b1);
|
||||
a1.put("b2", b2);
|
||||
b1.put("c1", "A1B1C1");
|
||||
b1.put("c2", "A1B1C2");
|
||||
b2.put("c1", "A1B2C1");
|
||||
assertThat(ElasticCommonSchemaPairs.nested(map)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedWhenDuplicateInParentThrowsException() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("a1.b1.c1", "A1B1C1");
|
||||
map.put("a1.b1", "A1B1");
|
||||
assertThatIllegalStateException().isThrownBy(() -> ElasticCommonSchemaPairs.nested(map))
|
||||
.withMessage("Duplicate ECS pairs added under 'a1.b1'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedWhenDuplicateInLeafThrowsException() {
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("a1.b1", "A1B1");
|
||||
map.put("a1.b1.c1", "A1B1C1");
|
||||
assertThatIllegalStateException().isThrownBy(() -> ElasticCommonSchemaPairs.nested(map))
|
||||
.withMessage("Duplicate ECS pairs added under 'a1.b1'");
|
||||
}
|
||||
|
||||
}
|
|
@ -43,12 +43,13 @@ import static org.mockito.BDDMockito.given;
|
|||
class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
||||
|
||||
@Mock
|
||||
private Instantiator<?> instantiator;
|
||||
@SuppressWarnings("rawtypes")
|
||||
private Instantiator instantiator;
|
||||
|
||||
@Test
|
||||
void customizeWhenHasExcludeFiltersMember() {
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
|
||||
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null, null);
|
||||
Set.of("a"), Collections.emptyMap(), Collections.emptyMap(), null, null, null);
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
assertThat(writeSampleJson(customizer)).doesNotContain("a").contains("b");
|
||||
|
@ -57,7 +58,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
|||
@Test
|
||||
void customizeWhenHasIncludeFiltersOtherMembers() {
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a"),
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null);
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null, null);
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
assertThat(writeSampleJson(customizer)).contains("a")
|
||||
|
@ -69,7 +70,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
|||
@Test
|
||||
void customizeWhenHasIncludeAndExcludeFiltersMembers() {
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("b"),
|
||||
Collections.emptyMap(), Collections.emptyMap(), null, null);
|
||||
Collections.emptyMap(), Collections.emptyMap(), null, null, null);
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
assertThat(writeSampleJson(customizer)).contains("a")
|
||||
|
@ -81,7 +82,7 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
|||
@Test
|
||||
void customizeWhenHasRenameRenamesMember() {
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
|
||||
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null, null);
|
||||
Collections.emptySet(), Map.of("a", "z"), Collections.emptyMap(), null, null, null);
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
assertThat(writeSampleJson(customizer)).contains("\"z\":\"a\"");
|
||||
|
@ -90,20 +91,20 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
|||
@Test
|
||||
void customizeWhenHasAddAddsMemeber() {
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
|
||||
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null, null);
|
||||
Collections.emptySet(), Collections.emptyMap(), Map.of("z", "z"), null, null, null);
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
assertThat(writeSampleJson(customizer)).contains("\"z\":\"z\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@SuppressWarnings("unchecked")
|
||||
void customizeWhenHasCustomizerCustomizesMember() {
|
||||
StructuredLoggingJsonMembersCustomizer<?> uppercaseCustomizer = (members) -> members
|
||||
.applyingNameProcessor(NameProcessor.of(String::toUpperCase));
|
||||
given(((Instantiator) this.instantiator).instantiateType(TestCustomizer.class)).willReturn(uppercaseCustomizer);
|
||||
given(this.instantiator.instantiateType(TestCustomizer.class)).willReturn(uppercaseCustomizer);
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null,
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null,
|
||||
Set.of(TestCustomizer.class));
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
|
@ -111,12 +112,12 @@ class StructuredLoggingJsonPropertiesJsonMembersCustomizerTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@SuppressWarnings("unchecked")
|
||||
void customizeWhenHasCustomizersCustomizesMember() {
|
||||
given(((Instantiator) this.instantiator).instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
|
||||
given(((Instantiator) this.instantiator).instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
|
||||
given(this.instantiator.instantiateType(FooCustomizer.class)).willReturn(new FooCustomizer());
|
||||
given(this.instantiator.instantiateType(BarCustomizer.class)).willReturn(new BarCustomizer());
|
||||
StructuredLoggingJsonProperties properties = new StructuredLoggingJsonProperties(Collections.emptySet(),
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null,
|
||||
Collections.emptySet(), Collections.emptyMap(), Collections.emptyMap(), null, null,
|
||||
Set.of(FooCustomizer.class, BarCustomizer.class));
|
||||
StructuredLoggingJsonPropertiesJsonMembersCustomizer customizer = new StructuredLoggingJsonPropertiesJsonMembersCustomizer(
|
||||
this.instantiator, properties);
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.springframework.boot.json.JsonWriter.Members;
|
|||
import org.springframework.boot.logging.StackTracePrinter;
|
||||
import org.springframework.boot.logging.StandardStackTracePrinter;
|
||||
import org.springframework.boot.logging.TestException;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.Context;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.StackTrace;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.StackTrace.Root;
|
||||
import org.springframework.boot.logging.structured.StructuredLoggingJsonProperties.StructuredLoggingJsonPropertiesRuntimeHints;
|
||||
|
@ -52,7 +53,7 @@ class StructuredLoggingJsonPropertiesTests {
|
|||
setupJsonProperties(environment);
|
||||
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
|
||||
assertThat(properties).isEqualTo(new StructuredLoggingJsonProperties(Set.of("a", "b"), Set.of("c", "d"),
|
||||
Map.of("e", "f"), Map.of("g", "h"), null, Set.of(TestCustomizer.class)));
|
||||
Map.of("e", "f"), Map.of("g", "h"), null, null, Set.of(TestCustomizer.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -91,12 +92,15 @@ class StructuredLoggingJsonPropertiesTests {
|
|||
assertThat(RuntimeHintsPredicates.reflection().onType(StructuredLoggingJsonProperties.class)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onConstructor(StructuredLoggingJsonProperties.class.getDeclaredConstructor(Set.class, Set.class, Map.class,
|
||||
Map.class, StackTrace.class, Set.class))
|
||||
Map.class, StackTrace.class, Context.class, Set.class))
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onConstructor(StackTrace.class.getDeclaredConstructor(String.class, Root.class, Integer.class,
|
||||
Integer.class, Boolean.class, Boolean.class))
|
||||
.invoke()).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onConstructor(Context.class.getDeclaredConstructor(boolean.class, String.class))
|
||||
.invoke()).accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
|
||||
/**
|
||||
* Test access to {@link ContextPairs}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public final class TestContextPairs {
|
||||
|
||||
private TestContextPairs() {
|
||||
}
|
||||
|
||||
public static ContextPairs include() {
|
||||
return of(true, null);
|
||||
}
|
||||
|
||||
public static ContextPairs of(boolean include, String prefix) {
|
||||
return new ContextPairs(include, prefix);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue