Skip to content

Commit c682cdb

Browse files
Add support for structured stacktrace hash field Signed-off-by: Venkata Naga Sai Srikanth Gollapudi <42247688+GollapudiSrikanth@users.noreply.github.com>
Signed-off-by: Venkata Naga Sai Srikanth Gollapudi <42247688+GollapudiSrikanth@users.noreply.github.com>
1 parent a93ad0f commit c682cdb

24 files changed

+679
-79
lines changed

core/spring-boot/src/main/java/org/springframework/boot/logging/StandardStackTracePrinter.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,35 @@ public StandardStackTracePrinter withHashes(@Nullable ToIntFunction<StackTraceEl
278278
this.frameFilter, this.formatter, this.frameFormatter, frameHasher);
279279
}
280280

281+
/**
282+
* Compute a hash for the given throwable using the default frame hasher and return it
283+
* as a zero-padded 8-character hex string.
284+
* @param throwable the throwable to hash
285+
* @return the hash as a hex string, or {@code null} if the throwable is {@code null}
286+
* @since 4.0.0
287+
*/
288+
public static @Nullable String hashAsHexString(@Nullable Throwable throwable) {
289+
return hashAsHexString(throwable, DEFAULT_FRAME_HASHER);
290+
}
291+
292+
/**
293+
* Compute a hash for the given throwable using the specified frame hasher and return
294+
* it as a zero-padded 8-character hex string.
295+
* @param throwable the throwable to hash
296+
* @param frameHasher the function used to hash individual stack trace elements
297+
* @return the hash as a hex string, or {@code null} if the throwable is {@code null}
298+
* @since 4.0.0
299+
*/
300+
public static @Nullable String hashAsHexString(@Nullable Throwable throwable,
301+
ToIntFunction<StackTraceElement> frameHasher) {
302+
if (throwable == null) {
303+
return null;
304+
}
305+
StackTrace stackTrace = new StackTrace(throwable);
306+
int hash = stackTrace.hash(new HashSet<>(), frameHasher);
307+
return String.format("%08x", hash);
308+
}
309+
281310
private StandardStackTracePrinter withOption(Option option) {
282311
EnumSet<Option> options = EnumSet.copyOf(this.options);
283312
options.add(option);

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.boot.logging.structured.ContextPairs.Pairs;
3434
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
3535
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
36+
import org.springframework.boot.logging.structured.StackTraceHashFieldConfiguration;
3637
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3738
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
3839
import org.springframework.core.env.Environment;
@@ -48,13 +49,15 @@
4849
class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
4950

5051
ElasticCommonSchemaStructuredLogFormatter(Environment environment, @Nullable StackTracePrinter stackTracePrinter,
51-
ContextPairs contextPairs, StructuredLoggingJsonMembersCustomizer.Builder<?> customizerBuilder) {
52-
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, members),
52+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
53+
StructuredLoggingJsonMembersCustomizer.Builder<?> customizerBuilder) {
54+
super((members) -> jsonMembers(environment, stackTracePrinter, hashFieldConfiguration, contextPairs, members),
5355
customizerBuilder.nested().build());
5456
}
5557

5658
private static void jsonMembers(Environment environment, @Nullable StackTracePrinter stackTracePrinter,
57-
ContextPairs contextPairs, JsonWriter.Members<LogEvent> members) {
59+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
60+
JsonWriter.Members<LogEvent> members) {
5861
Extractor extractor = new Extractor(stackTracePrinter);
5962
members.add("@timestamp", LogEvent::getInstant).as(ElasticCommonSchemaStructuredLogFormatter::asTimestamp);
6063
members.add("log").usingMembers((log) -> {
@@ -76,6 +79,11 @@ private static void jsonMembers(Environment environment, @Nullable StackTracePri
7679
error.add("message", Throwable::getMessage);
7780
error.add("stack_trace", extractor::stackTrace);
7881
}));
82+
if (hashFieldConfiguration != null) {
83+
members.add(hashFieldConfiguration.getFieldName(), LogEvent::getThrown)
84+
.whenNotNull()
85+
.as(hashFieldConfiguration::computeHash);
86+
}
7987
members.add("tags", LogEvent::getMarker)
8088
.whenNotNull()
8189
.as(ElasticCommonSchemaStructuredLogFormatter::getMarkers)

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.boot.logging.structured.ContextPairs.Joiner;
4242
import org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties;
4343
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
44+
import org.springframework.boot.logging.structured.StackTraceHashFieldConfiguration;
4445
import org.springframework.boot.logging.structured.StructuredLogFormatter;
4546
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
4647
import org.springframework.core.env.Environment;
@@ -74,13 +75,16 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
7475
private static final Set<String> ADDITIONAL_FIELD_ILLEGAL_KEYS = Set.of("id", "_id");
7576

7677
GraylogExtendedLogFormatStructuredLogFormatter(Environment environment,
77-
@Nullable StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
78+
@Nullable StackTracePrinter stackTracePrinter,
79+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
7880
@Nullable StructuredLoggingJsonMembersCustomizer<?> customizer) {
79-
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, members), customizer);
81+
super((members) -> jsonMembers(environment, stackTracePrinter, hashFieldConfiguration, contextPairs, members),
82+
customizer);
8083
}
8184

8285
private static void jsonMembers(Environment environment, @Nullable StackTracePrinter stackTracePrinter,
83-
ContextPairs contextPairs, JsonWriter.Members<LogEvent> members) {
86+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
87+
JsonWriter.Members<LogEvent> members) {
8488
Extractor extractor = new Extractor(stackTracePrinter);
8589
members.add("version", "1.1");
8690
members.add("short_message", LogEvent::getMessage)
@@ -103,6 +107,11 @@ private static void jsonMembers(Environment environment, @Nullable StackTracePri
103107
members.add()
104108
.whenNotNull(getThrown)
105109
.usingMembers((thrownMembers) -> throwableMembers(thrownMembers, extractor));
110+
if (hashFieldConfiguration != null) {
111+
members.add(hashFieldConfiguration.getFieldName(), LogEvent::getThrown)
112+
.whenNotNull()
113+
.as(hashFieldConfiguration::computeHash);
114+
}
106115
}
107116

108117
private static String getMessageText(Message message) {

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
3636
import org.springframework.boot.logging.structured.ContextPairs;
3737
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
38+
import org.springframework.boot.logging.structured.StackTraceHashFieldConfiguration;
3839
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3940
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
4041
import org.springframework.util.CollectionUtils;
@@ -47,12 +48,14 @@
4748
*/
4849
class LogstashStructuredLogFormatter extends JsonWriterStructuredLogFormatter<LogEvent> {
4950

50-
LogstashStructuredLogFormatter(@Nullable StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
51+
LogstashStructuredLogFormatter(@Nullable StackTracePrinter stackTracePrinter,
52+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
5153
@Nullable StructuredLoggingJsonMembersCustomizer<?> customizer) {
52-
super((members) -> jsonMembers(stackTracePrinter, contextPairs, members), customizer);
54+
super((members) -> jsonMembers(stackTracePrinter, hashFieldConfiguration, contextPairs, members), customizer);
5355
}
5456

55-
private static void jsonMembers(@Nullable StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
57+
private static void jsonMembers(@Nullable StackTracePrinter stackTracePrinter,
58+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
5659
JsonWriter.Members<LogEvent> members) {
5760
Extractor extractor = new Extractor(stackTracePrinter);
5861
members.add("@timestamp", LogEvent::getInstant).as(LogstashStructuredLogFormatter::asTimestamp);
@@ -72,6 +75,11 @@ private static void jsonMembers(@Nullable StackTracePrinter stackTracePrinter, C
7275
.as(LogstashStructuredLogFormatter::getMarkers)
7376
.whenNot(collectionIsEmpty);
7477
members.add("stack_trace", LogEvent::getThrown).whenNotNull().as(extractor::stackTrace);
78+
if (hashFieldConfiguration != null) {
79+
members.add(hashFieldConfiguration.getFieldName(), LogEvent::getThrown)
80+
.whenNotNull()
81+
.as(hashFieldConfiguration::computeHash);
82+
}
7583
}
7684

7785
private static String asTimestamp(Instant instant) {

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.boot.logging.StackTracePrinter;
3333
import org.springframework.boot.logging.structured.CommonStructuredLogFormat;
3434
import org.springframework.boot.logging.structured.ContextPairs;
35+
import org.springframework.boot.logging.structured.StackTraceHashFieldConfiguration;
3536
import org.springframework.boot.logging.structured.StructuredLogFormatter;
3637
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory;
3738
import org.springframework.boot.logging.structured.StructuredLogFormatterFactory.CommonFormatters;
@@ -117,35 +118,42 @@ private void addCommonFormatters(CommonFormatters<LogEvent> commonFormatters) {
117118
private ElasticCommonSchemaStructuredLogFormatter createEcsFormatter(Instantiator<?> instantiator) {
118119
Environment environment = instantiator.getArg(Environment.class);
119120
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
121+
StackTraceHashFieldConfiguration hashFieldConfiguration = instantiator
122+
.getArg(StackTraceHashFieldConfiguration.class);
120123
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
121124
StructuredLoggingJsonMembersCustomizer.Builder<?> jsonMembersCustomizerBuilder = instantiator
122125
.getArg(StructuredLoggingJsonMembersCustomizer.Builder.class);
123126
Assert.state(environment != null, "'environment' must not be null");
124127
Assert.state(contextPairs != null, "'contextPairs' must not be null");
125128
Assert.state(jsonMembersCustomizerBuilder != null, "'jsonMembersCustomizerBuilder' must not be null");
126-
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, contextPairs,
127-
jsonMembersCustomizerBuilder);
129+
return new ElasticCommonSchemaStructuredLogFormatter(environment, stackTracePrinter, hashFieldConfiguration,
130+
contextPairs, jsonMembersCustomizerBuilder);
128131
}
129132

130133
private GraylogExtendedLogFormatStructuredLogFormatter createGraylogFormatter(Instantiator<?> instantiator) {
131134
Environment environment = instantiator.getArg(Environment.class);
132135
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
136+
StackTraceHashFieldConfiguration hashFieldConfiguration = instantiator
137+
.getArg(StackTraceHashFieldConfiguration.class);
133138
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
134139
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
135140
.getArg(StructuredLoggingJsonMembersCustomizer.class);
136141
Assert.state(environment != null, "'environment' must not be null");
137142
Assert.state(contextPairs != null, "'contextPairs' must not be null");
138-
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter, contextPairs,
139-
jsonMembersCustomizer);
143+
return new GraylogExtendedLogFormatStructuredLogFormatter(environment, stackTracePrinter,
144+
hashFieldConfiguration, contextPairs, jsonMembersCustomizer);
140145
}
141146

142147
private LogstashStructuredLogFormatter createLogstashFormatter(Instantiator<?> instantiator) {
143148
StackTracePrinter stackTracePrinter = instantiator.getArg(StackTracePrinter.class);
149+
StackTraceHashFieldConfiguration hashFieldConfiguration = instantiator
150+
.getArg(StackTraceHashFieldConfiguration.class);
144151
ContextPairs contextPairs = instantiator.getArg(ContextPairs.class);
145152
StructuredLoggingJsonMembersCustomizer<?> jsonMembersCustomizer = instantiator
146153
.getArg(StructuredLoggingJsonMembersCustomizer.class);
147154
Assert.state(contextPairs != null, "'contextPairs' must not be null");
148-
return new LogstashStructuredLogFormatter(stackTracePrinter, contextPairs, jsonMembersCustomizer);
155+
return new LogstashStructuredLogFormatter(stackTracePrinter, hashFieldConfiguration, contextPairs,
156+
jsonMembersCustomizer);
149157
}
150158

151159
}

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.boot.logging.structured.ContextPairs;
3737
import org.springframework.boot.logging.structured.ElasticCommonSchemaProperties;
3838
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
39+
import org.springframework.boot.logging.structured.StackTraceHashFieldConfiguration;
3940
import org.springframework.boot.logging.structured.StructuredLogFormatter;
4041
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
4142
import org.springframework.core.env.Environment;
@@ -53,15 +54,16 @@ class ElasticCommonSchemaStructuredLogFormatter extends JsonWriterStructuredLogF
5354
(pair) -> pair.value);
5455

5556
ElasticCommonSchemaStructuredLogFormatter(Environment environment, @Nullable StackTracePrinter stackTracePrinter,
56-
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
57+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
58+
ThrowableProxyConverter throwableProxyConverter,
5759
StructuredLoggingJsonMembersCustomizer.Builder<?> customizerBuilder) {
58-
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, throwableProxyConverter, members),
59-
customizerBuilder.nested().build());
60+
super((members) -> jsonMembers(environment, stackTracePrinter, hashFieldConfiguration, contextPairs,
61+
throwableProxyConverter, members), customizerBuilder.nested().build());
6062
}
6163

6264
private static void jsonMembers(Environment environment, @Nullable StackTracePrinter stackTracePrinter,
63-
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
64-
JsonWriter.Members<ILoggingEvent> members) {
65+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
66+
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
6567
Extractor extractor = new Extractor(stackTracePrinter, throwableProxyConverter);
6668
members.add("@timestamp", ILoggingEvent::getInstant);
6769
members.add("log").usingMembers((log) -> {
@@ -87,13 +89,25 @@ private static void jsonMembers(Environment environment, @Nullable StackTracePri
8789
error.add("stack_trace", extractor::stackTrace);
8890
});
8991
});
92+
if (hashFieldConfiguration != null) {
93+
members.add(hashFieldConfiguration.getFieldName(), (event) -> event)
94+
.whenNotNull(getThrowableProxy)
95+
.as((event) -> hashFieldConfiguration.computeHash(extractThrowable(event)));
96+
}
9097
members.add("tags", ILoggingEvent::getMarkerList)
9198
.whenNotNull()
9299
.as(ElasticCommonSchemaStructuredLogFormatter::getMarkers)
93100
.whenNotEmpty();
94101
members.add("ecs").usingMembers((ecs) -> ecs.add("version", "8.11"));
95102
}
96103

104+
private static @Nullable Throwable extractThrowable(ILoggingEvent event) {
105+
if (event.getThrowableProxy() instanceof ch.qos.logback.classic.spi.ThrowableProxy throwableProxy) {
106+
return throwableProxy.getThrowable();
107+
}
108+
return null;
109+
}
110+
97111
private static Set<String> getMarkers(List<Marker> markers) {
98112
Set<String> result = new TreeSet<>();
99113
addMarkers(result, markers.iterator());

core/spring-boot/src/main/java/org/springframework/boot/logging/logback/GraylogExtendedLogFormatStructuredLogFormatter.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.boot.logging.structured.ContextPairs.Joiner;
4141
import org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties;
4242
import org.springframework.boot.logging.structured.JsonWriterStructuredLogFormatter;
43+
import org.springframework.boot.logging.structured.StackTraceHashFieldConfiguration;
4344
import org.springframework.boot.logging.structured.StructuredLogFormatter;
4445
import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
4546
import org.springframework.core.env.Environment;
@@ -75,16 +76,17 @@ class GraylogExtendedLogFormatStructuredLogFormatter extends JsonWriterStructure
7576
private static final Set<String> ADDITIONAL_FIELD_ILLEGAL_KEYS = Set.of("id", "_id");
7677

7778
GraylogExtendedLogFormatStructuredLogFormatter(Environment environment,
78-
@Nullable StackTracePrinter stackTracePrinter, ContextPairs contextPairs,
79+
@Nullable StackTracePrinter stackTracePrinter,
80+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
7981
ThrowableProxyConverter throwableProxyConverter,
8082
@Nullable StructuredLoggingJsonMembersCustomizer<?> customizer) {
81-
super((members) -> jsonMembers(environment, stackTracePrinter, contextPairs, throwableProxyConverter, members),
82-
customizer);
83+
super((members) -> jsonMembers(environment, stackTracePrinter, hashFieldConfiguration, contextPairs,
84+
throwableProxyConverter, members), customizer);
8385
}
8486

8587
private static void jsonMembers(Environment environment, @Nullable StackTracePrinter stackTracePrinter,
86-
ContextPairs contextPairs, ThrowableProxyConverter throwableProxyConverter,
87-
JsonWriter.Members<ILoggingEvent> members) {
88+
@Nullable StackTraceHashFieldConfiguration hashFieldConfiguration, ContextPairs contextPairs,
89+
ThrowableProxyConverter throwableProxyConverter, JsonWriter.Members<ILoggingEvent> members) {
8890
Extractor extractor = new Extractor(stackTracePrinter, throwableProxyConverter);
8991
members.add("version", "1.1");
9092
members.add("short_message", ILoggingEvent::getFormattedMessage)
@@ -106,6 +108,18 @@ private static void jsonMembers(Environment environment, @Nullable StackTracePri
106108
members.add()
107109
.whenNotNull(getThrowableProxy)
108110
.usingMembers((throwableMembers) -> throwableMembers(throwableMembers, extractor));
111+
if (hashFieldConfiguration != null) {
112+
members.add(hashFieldConfiguration.getFieldName(), (event) -> event)
113+
.whenNotNull(getThrowableProxy)
114+
.as((event) -> hashFieldConfiguration.computeHash(extractThrowable(event)));
115+
}
116+
}
117+
118+
private static @Nullable Throwable extractThrowable(ILoggingEvent event) {
119+
if (event.getThrowableProxy() instanceof ch.qos.logback.classic.spi.ThrowableProxy throwableProxy) {
120+
return throwableProxy.getThrowable();
121+
}
122+
return null;
109123
}
110124

111125
private static String getMessageText(String formattedMessage) {

0 commit comments

Comments
 (0)