Skip to content

Commit 0c32011

Browse files
committed
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
1 parent fdd4abc commit 0c32011

25 files changed

+611
-260
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/ElasticCommonSchemaStructuredLogFormatter.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.logging.log4j2;
1818

19-
import java.util.Map;
2019
import java.util.Objects;
2120
import java.util.Set;
2221
import java.util.TreeSet;
@@ -31,7 +30,8 @@
3130
import org.springframework.boot.json.JsonWriter;
3231
import org.springframework.boot.logging.StackTracePrinter;
3332
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
34-
import org.springframework.boot.logging.structured.ElasticCommonSchemaPairs;
33+
import org.springframework.boot.logging.structured.ContextPairs;
34+
import org.springframework.boot.logging.structured.ContextPairs.Pairs;
3535
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
3636
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
3737
import org.springframework.boot.logging.structured.StructuredLogFormatter;
@@ -49,12 +49,12 @@
4949
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
5050

5151
ElasticCommonSchemaStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
52-
StructuredLoggingJsonMembersCustomizer<?> customizer) {
53-
super((members) -> jsonMembers(environment, stackTracePrinter, members), customizer);
52+
ContextPairs contextPairs, StructuredLoggingJsonMembersCustomizer<?> customizer) {
53+
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, members), customizer);
5454
}
5555

5656
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
57-
JsonWriter.Members<LogEvent> members) {
57+
ContextPairs contextPairs, JsonWriter.Members<LogEvent> members) {
5858
Extractor extractor = new Extractor(stackTracePrinter);
5959
members.add("@timestamp", LogEvent::getInstant).as(ElasticCommonSchemaStructuredLogFormatter::asTimestamp);
6060
members.add("log").usingMembers((log) -> {
@@ -68,9 +68,7 @@ private static void jsonMembers(Environment environment, StackTracePrinter stack
6868
ElasticCommonSchemaProperties.get(environment).jsonMembers(members);
6969
members.add("message", LogEvent::getMessage).as(StructuredMessage::get);
7070
members.from(LogEvent::getContextData)
71-
.whenNot(ReadOnlyStringMap::isEmpty)
72-
.as((contextData) -> ElasticCommonSchemaPairs.nested((nested) -> contextData.forEach(nested::accept)))
73-
.usingPairs(Map::forEach);
71+
.usingPairs(contextPairs.nested(ElasticCommonSchemaStructuredLogFormatter::addContextDataPairs));
7472
members.from(LogEvent::getThrownProxy).whenNotNull().usingMembers((thrownProxyMembers) -> {
7573
thrownProxyMembers.add("error").usingMembers((error) -> {
7674
error.add("type", ThrowableProxy::getThrowable).whenNotNull().as(ObjectUtils::nullSafeClassName);
@@ -85,6 +83,10 @@ private static void jsonMembers(Environment environment, StackTracePrinter stack
8583
members.add("ecs").usingMembers((ecs) -> ecs.add("version", "8.11"));
8684
}
8785

86+
private static void addContextDataPairs(Pairs<ReadOnlyStringMap> contextPairs) {
87+
contextPairs.add((contextData, pairs) -> contextData.forEach(pairs::accept));
88+
}
89+
8890
private static java.time.Instant asTimestamp(Instant instant) {
8991
return java.time.Instant.ofEpochMilli(instant.getEpochMillisecond()).plusNanos(instant.getNanoOfMillisecond());
9092
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/GraylogExtendedLogFormatStructuredLogFormatter.java

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.math.BigDecimal;
2020
import java.util.Objects;
2121
import java.util.Set;
22-
import java.util.function.BiConsumer;
22+
import java.util.function.BinaryOperator;
2323
import java.util.regex.Pattern;
2424

2525
import org.apache.commons.logging.Log;
@@ -36,13 +36,13 @@
3636
import org.springframework.boot.json.WritableJson;
3737
import org.springframework.boot.logging.StackTracePrinter;
3838
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
39+
import org.springframework.boot.logging.structured.ContextPairs;
3940
import org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties;
4041
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
4142
import org.springframework.boot.logging.structured.StructuredLogFormatter;
4243
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
4344
import org.springframework.core.env.Environment;
4445
import org.springframework.core.log.LogMessage;
45-
import org.springframework.util.Assert;
4646
import org.springframework.util.ObjectUtils;
4747
import org.springframework.util.StringUtils;
4848

@@ -72,12 +72,12 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
7272
private static final Set<String> ADDITIONAL_FIELD_ILLEGAL_KEYS = Set.of("id", "_id");
7373

7474
GraylogExtendedLogFormatStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
75-
StructuredLoggingJsonMembersCustomizer<?> customizer) {
76-
super((members) -> jsonMembers(environment, stackTracePrinter, members), customizer);
75+
ContextPairs contextPairs, StructuredLoggingJsonMembersCustomizer<?> customizer) {
76+
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, members), customizer);
7777
}
7878

7979
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
80-
JsonWriter.Members<LogEvent> members) {
80+
ContextPairs contextPairs, JsonWriter.Members<LogEvent> members) {
8181
Extractor extractor = new Extractor(stackTracePrinter);
8282
members.add("version", "1.1");
8383
members.add("short_message", LogEvent::getMessage)
@@ -93,7 +93,8 @@ private static void jsonMembers(Environment environment, StackTracePrinter stack
9393
members.add("_log_logger", LogEvent::getLoggerName);
9494
members.from(LogEvent::getContextData)
9595
.whenNot(ReadOnlyStringMap::isEmpty)
96-
.usingPairs(GraylogExtendedLogFormatStructuredLogFormatter::createAdditionalFields);
96+
.usingPairs(contextPairs.flat(additionalFieldJoiner(),
97+
GraylogExtendedLogFormatStructuredLogFormatter::addContextDataPairs));
9798
members.add()
9899
.whenNotNull(LogEvent::getThrownProxy)
99100
.usingMembers((thrownProxyMembers) -> throwableMembers(thrownProxyMembers, extractor));
@@ -135,25 +136,23 @@ private static void throwableMembers(Members<LogEvent> members, Extractor extrac
135136
members.add("_error_message", (event) -> event.getThrownProxy().getMessage());
136137
}
137138

138-
private static void createAdditionalFields(ReadOnlyStringMap contextData, BiConsumer<Object, Object> pairs) {
139-
contextData.forEach((name, value) -> createAdditionalField(name, value, pairs));
139+
private static void addContextDataPairs(ContextPairs.Pairs<ReadOnlyStringMap> contextPairs) {
140+
contextPairs.add((contextData, pairs) -> contextData.forEach(pairs::accept));
140141
}
141142

142-
private static void createAdditionalField(String name, Object value, BiConsumer<Object, Object> pairs) {
143-
Assert.notNull(name, "'name' must not be null");
144-
if (!FIELD_NAME_VALID_PATTERN.matcher(name).matches()) {
145-
logger.warn(LogMessage.format("'%s' is not a valid field name according to GELF standard", name));
146-
return;
147-
}
148-
if (ADDITIONAL_FIELD_ILLEGAL_KEYS.contains(name)) {
149-
logger.warn(LogMessage.format("'%s' is an illegal field name according to GELF standard", name));
150-
return;
151-
}
152-
pairs.accept(asAdditionalFieldName(name), value);
153-
}
154-
155-
private static Object asAdditionalFieldName(String name) {
156-
return (!name.startsWith("_")) ? "_" + name : name;
143+
private static BinaryOperator<String> additionalFieldJoiner() {
144+
return (prefix, name) -> {
145+
name = prefix + name;
146+
if (!FIELD_NAME_VALID_PATTERN.matcher(name).matches()) {
147+
logger.warn(LogMessage.format("'%s' is not a valid field name according to GELF standard", name));
148+
return null;
149+
}
150+
if (ADDITIONAL_FIELD_ILLEGAL_KEYS.contains(name)) {
151+
logger.warn(LogMessage.format("'%s' is an illegal field name according to GELF standard", name));
152+
return null;
153+
}
154+
return (!name.startsWith("_")) ? "_" + name : name;
155+
};
157156
}
158157

159158
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/LogstashStructuredLogFormatter.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.json.JsonWriter;
3232
import org.springframework.boot.logging.StackTracePrinter;
3333
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
34+
import org.springframework.boot.logging.structured.ContextPairs;
3435
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
3536
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3637
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
@@ -44,12 +45,13 @@
4445
*/
4546
class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
4647

47-
LogstashStructuredLogFormatter(StackTracePrinter stackTracePrinter,
48+
LogstashStructuredLogFormatter(StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
4849
StructuredLoggingJsonMembersCustomizer<?> customizer) {
49-
super((members) -> jsonMembers(stackTracePrinter, members), customizer);
50+
super((members) -> jsonMembers(stackTracePrinter, contextPairs, members), customizer);
5051
}
5152

52-
private static void jsonMembers(StackTracePrinter stackTracePrinter, JsonWriter.Members<LogEvent> members) {
53+
private static void jsonMembers(StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
54+
JsonWriter.Members<LogEvent> members) {
5355
Extractor extractor = new Extractor(stackTracePrinter);
5456
members.add("@timestamp", LogEvent::getInstant).as(LogstashStructuredLogFormatter::asTimestamp);
5557
members.add("@version", "1");
@@ -60,7 +62,7 @@ private static void jsonMembers(StackTracePrinter stackTracePrinter, JsonWriter.
6062
members.add("level_value", LogEvent::getLevel).as(Level::intLevel);
6163
members.from(LogEvent::getContextData)
6264
.whenNot(ReadOnlyStringMap::isEmpty)
63-
.usingPairs((contextData, pairs) -> contextData.forEach(pairs::accept));
65+
.usingPairs(contextPairs.flat("_", LogstashStructuredLogFormatter::addContextDataPairs));
6466
members.add("tags", LogEvent::getMarker)
6567
.whenNotNull()
6668
.as(LogstashStructuredLogFormatter::getMarkers)
@@ -75,6 +77,10 @@ private static String asTimestamp(Instant instant) {
7577
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(offsetDateTime);
7678
}
7779

80+
private static void addContextDataPairs(ContextPairs.Pairs<ReadOnlyStringMap> contextPairs) {
81+
contextPairs.add((contextData, pairs) -> contextData.forEach(pairs::accept));
82+
}
83+
7884
private static Set<String> getMarkers(Marker marker) {
7985
Set<String> result = new TreeSet<>();
8086
addMarkers(result, marker);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/StructuredLogLayout.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import org.springframework.boot.logging.StackTracePrinter;
3333
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
34+
import org.springframework.boot.logging.structured.ContextPairs;
3435
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3536
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
3637
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
@@ -113,25 +114,29 @@ private void addCommonFormatters(CommonFormatters<LogEvent> commonFormatters) {
113114
private ElasticCommonSchemaStructuredLogFormatter createEcsFormatter(Instantiator<?> instantiator) {
114115
Environment environment = instantiator.getArg(Environment.class);
115116
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
117+
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
116118
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
117119
.getArg(StructuredLoggingJsonMembersCustomizer.class);
118-
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, jsonMembersCustomizer);
120+
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, contextPairs,
121+
jsonMembersCustomizer);
119122
}
120123

121124
private GraylogExtendedLogFormatStructuredLogFormatter createGraylogFormatter(Instantiator<?> instantiator) {
122125
Environment environment = instantiator.getArg(Environment.class);
123126
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
127+
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
124128
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
125129
.getArg(StructuredLoggingJsonMembersCustomizer.class);
126-
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter,
130+
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter, contextPairs,
127131
jsonMembersCustomizer);
128132
}
129133

130134
private LogstashStructuredLogFormatter createLogstashFormatter(Instantiator<?> instantiator) {
131135
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
136+
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
132137
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
133138
.getArg(StructuredLoggingJsonMembersCustomizer.class);
134-
return new LogstashStructuredLogFormatter(stackTracePrinter, jsonMembersCustomizer);
139+
return new LogstashStructuredLogFormatter(stackTracePrinter, contextPairs, jsonMembersCustomizer);
135140
}
136141

137142
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/ElasticCommonSchemaStructuredLogFormatter.java

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.util.Iterator;
2020
import java.util.List;
21-
import java.util.Map;
2221
import java.util.Objects;
2322
import java.util.Set;
2423
import java.util.TreeSet;
@@ -30,9 +29,10 @@
3029
import org.slf4j.event.KeyValuePair;
3130

3231
import org.springframework.boot.json.JsonWriter;
32+
import org.springframework.boot.json.JsonWriter.PairExtractor;
3333
import org.springframework.boot.logging.StackTracePrinter;
3434
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
35-
import org.springframework.boot.logging.structured.ElasticCommonSchemaPairs;
35+
import org.springframework.boot.logging.structured.ContextPairs;
3636
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
3737
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
3838
import org.springframework.boot.logging.structured.StructuredLogFormatter;
@@ -48,13 +48,19 @@
4848
*/
4949
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<ILoggingEvent> {
5050

51+
private static final PairExtractor<KeyValuePair> keyValuePairExtractor = PairExtractor.of((pair) -> pair.key,
52+
(pair) -> pair.value);
53+
5154
ElasticCommonSchemaStructuredLogFormatter(Environment environment, StackTracePrinter stackTracePrinter,
52-
ThrowableProxyConverter throwableProxyConverter, StructuredLoggingJsonMembersCustomizer<?> customizer) {
53-
super((members) -> jsonMembers(environment, stackTracePrinter, throwableProxyConverter, members), customizer);
55+
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
56+
StructuredLoggingJsonMembersCustomizer<?> customizer) {
57+
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, throwableProxyConverter, members),
58+
customizer);
5459
}
5560

5661
private static void jsonMembers(Environment environment, StackTracePrinter stackTracePrinter,
57-
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
62+
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
63+
JsonWriter.Members<ILoggingEvent> members) {
5864
Extractor extractor = new Extractor(stackTracePrinter, throwableProxyConverter);
5965
members.add("@timestamp", ILoggingEvent::getInstant);
6066
members.add("log").usingMembers((log) -> {
@@ -67,14 +73,10 @@ private static void jsonMembers(Environment environment, StackTracePrinter stack
6773
});
6874
ElasticCommonSchemaProperties.get(environment).jsonMembers(members);
6975
members.add("message", ILoggingEvent::getFormattedMessage);
70-
members.from(ILoggingEvent::getMDCPropertyMap)
71-
.whenNotEmpty()
72-
.as(ElasticCommonSchemaPairs::nested)
73-
.usingPairs(Map::forEach);
74-
members.from(ILoggingEvent::getKeyValuePairs)
75-
.whenNotEmpty()
76-
.as(ElasticCommonSchemaStructuredLogFormatter::nested)
77-
.usingPairs(Map::forEach);
76+
members.add().usingPairs(contextPairs.nested((pairs) -> {
77+
pairs.addMapEntries(ILoggingEvent::getMDCPropertyMap);
78+
pairs.add(ILoggingEvent::getKeyValuePairs, keyValuePairExtractor);
79+
}));
7880
members.add().whenNotNull(ILoggingEvent::getThrowableProxy).usingMembers((throwableMembers) -> {
7981
throwableMembers.add("error").usingMembers((error) -> {
8082
error.add("type", ILoggingEvent::getThrowableProxy).as(IThrowableProxy::getClassName);
@@ -89,11 +91,6 @@ private static void jsonMembers(Environment environment, StackTracePrinter stack
8991
members.add("ecs").usingMembers((ecs) -> ecs.add("version", "8.11"));
9092
}
9193

92-
private static Map<String, Object> nested(List<KeyValuePair> keyValuePairs) {
93-
return ElasticCommonSchemaPairs.nested((nested) -> keyValuePairs
94-
.forEach((keyValuePair) -> nested.accept(keyValuePair.key, keyValuePair.value)));
95-
}
96-
9794
private static Set<String> getMarkers(List<Marker> markers) {
9895
Set<String> result = new TreeSet<>();
9996
addMarkers(result, markers.iterator());

0 commit comments

Comments
 (0)