Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -97,10 +99,12 @@ Collection<StructuredLoggingJsonMembersCustomizer<Object>> customizers(Instantia
* @param maxThrowableDepth the maximum throwable depth to print
* @param includeCommonFrames whether common frames should be included
* @param includeHashes whether stack trace hashes should be included
* @param excludedFrames list of patterns excluded from stacktrace, f.e.
* java.lang.reflect.Method
*/
record StackTrace(@Nullable String printer, @Nullable Root root, @Nullable Integer maxLength,
@Nullable Integer maxThrowableDepth, @Nullable Boolean includeCommonFrames,
@Nullable Boolean includeHashes) {
@Nullable Integer maxThrowableDepth, @Nullable Boolean includeCommonFrames, @Nullable Boolean includeHashes,
@Nullable List<String> excludedFrames) {

@Nullable StackTracePrinter createPrinter() {
String name = sanitizePrinter();
Expand Down Expand Up @@ -130,7 +134,8 @@ private String sanitizePrinter() {
}

private boolean hasAnyOtherProperty() {
return Stream.of(root(), maxLength(), maxThrowableDepth(), includeCommonFrames(), includeHashes())
return Stream
.of(root(), maxLength(), maxThrowableDepth(), includeCommonFrames(), includeHashes(), excludedFrames())
.anyMatch(Objects::nonNull);
}

Expand All @@ -144,6 +149,10 @@ private StandardStackTracePrinter createStandardPrinter() {
printer = map.from(this::includeCommonFrames)
.to(printer, apply(StandardStackTracePrinter::withCommonFrames));
printer = map.from(this::includeHashes).to(printer, apply(StandardStackTracePrinter::withHashes));
printer = map.from(this::excludedFrames)
.whenNot(List::isEmpty)
.as(this::biPredicate)
.to(printer, StandardStackTracePrinter::withFrameFilter);
return printer;
}

Expand All @@ -152,6 +161,14 @@ private BiFunction<StandardStackTracePrinter, Boolean, StandardStackTracePrinter
return (printer, value) -> (!value) ? printer : action.apply(printer);
}

private BiPredicate<Integer, StackTraceElement> biPredicate(List<String> excludedFrames) {
List<Pattern> exclusionPatterns = excludedFrames.stream().map(Pattern::compile).toList();
return (ignored, element) -> {
String classNameAndMethod = element.getClassName() + "." + element.getMethodName();
return exclusionPatterns.stream().noneMatch((pattern) -> pattern.matcher(classNameAndMethod).find());
};
}

/**
* Root ordering.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@
"type": "java.util.Map<java.lang.String,java.lang.String>",
"description": "Mapping between member paths and an alternative name that should be used in structured logging JSON"
},
{
"name": "logging.structured.json.stacktrace.excluded-frames",
"type": "java.util.List<java.lang.String>",
"description": "List of patterns excluded from stacktrace, f.e. java.lang.reflect.Method."
},
{
"name": "logging.structured.json.stacktrace.include-common-frames",
"type": "java.lang.Boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
package org.springframework.boot.logging.structured;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
Expand Down Expand Up @@ -66,10 +69,11 @@ void getWhenHasStackTracePropertiesBindsFromEnvironment() {
environment.setProperty("logging.structured.json.stacktrace.max-throwable-depth", "5");
environment.setProperty("logging.structured.json.stacktrace.include-common-frames", "true");
environment.setProperty("logging.structured.json.stacktrace.include-hashes", "true");
environment.setProperty("logging.structured.json.stacktrace.excluded-frames", "java.lang.reflect.Method");
StructuredLoggingJsonProperties properties = StructuredLoggingJsonProperties.get(environment);
assertThat(properties).isNotNull();
assertThat(properties.stackTrace())
.isEqualTo(new StructuredLoggingJsonProperties.StackTrace("standard", Root.FIRST, 1024, 5, true, true));
assertThat(properties.stackTrace()).isEqualTo(new StructuredLoggingJsonProperties.StackTrace("standard",
Root.FIRST, 1024, 5, true, true, List.of("java.lang.reflect.Method")));
}

private void setupJsonProperties(MockEnvironment environment) {
Expand Down Expand Up @@ -98,7 +102,7 @@ void shouldRegisterRuntimeHints() throws Exception {
.accepts(hints);
assertThat(RuntimeHintsPredicates.reflection()
.onConstructorInvocation(StackTrace.class.getDeclaredConstructor(String.class, Root.class, Integer.class,
Integer.class, Boolean.class, Boolean.class)))
Integer.class, Boolean.class, Boolean.class, List.class)))
.accepts(hints);
assertThat(RuntimeHintsPredicates.reflection()
.onConstructorInvocation(Context.class.getDeclaredConstructor(boolean.class, String.class))).accepts(hints);
Expand All @@ -115,44 +119,44 @@ class StackTraceTests {

@Test
void createPrinterWhenEmptyReturnsNull() {
StackTrace properties = new StackTrace(null, null, null, null, null, null);
StackTrace properties = new StackTrace(null, null, null, null, null, null, null);
assertThat(properties.createPrinter()).isNull();
}

@Test
void createPrinterWhenNoPrinterAndNotEmptyReturnsStandard() {
StackTrace properties = new StackTrace(null, Root.LAST, null, null, null, null);
StackTrace properties = new StackTrace(null, Root.LAST, null, null, null, null, null);
assertThat(properties.createPrinter()).isInstanceOf(StandardStackTracePrinter.class);
}

@Test
void createPrinterWhenLoggingSystemReturnsNull() {
StackTrace properties = new StackTrace("logging-system", null, null, null, null, null);
StackTrace properties = new StackTrace("logging-system", null, null, null, null, null, null);
assertThat(properties.createPrinter()).isNull();
}

@Test
void createPrinterWhenLoggingSystemRelaxedReturnsNull() {
StackTrace properties = new StackTrace("LoggingSystem", null, null, null, null, null);
StackTrace properties = new StackTrace("LoggingSystem", null, null, null, null, null, null);
assertThat(properties.createPrinter()).isNull();
}

@Test
void createPrinterWhenStandardReturnsStandardPrinter() {
StackTrace properties = new StackTrace("standard", null, null, null, null, null);
StackTrace properties = new StackTrace("standard", null, null, null, null, null, null);
assertThat(properties.createPrinter()).isInstanceOf(StandardStackTracePrinter.class);
}

@Test
void createPrinterWhenStandardRelaxedReturnsStandardPrinter() {
StackTrace properties = new StackTrace("STANDARD", null, null, null, null, null);
StackTrace properties = new StackTrace("STANDARD", null, null, null, null, null, null);
assertThat(properties.createPrinter()).isInstanceOf(StandardStackTracePrinter.class);
}

@Test
void createPrinterWhenStandardAppliesCustomizations() {
Exception exception = TestException.create();
StackTrace properties = new StackTrace(null, Root.FIRST, 300, 2, true, false);
StackTrace properties = new StackTrace(null, Root.FIRST, 300, 2, true, false, null);
StandardStackTracePrinter printer = (StandardStackTracePrinter) properties.createPrinter();
assertThat(printer).isNotNull();
printer = printer.withLineSeparator("\n");
Expand All @@ -168,17 +172,32 @@ void createPrinterWhenStandardAppliesCustomizations() {
@Test
void createPrinterWhenStandardWithHashesPrintsHash() {
Exception exception = TestException.create();
StackTrace properties = new StackTrace(null, null, null, null, null, true);
StackTrace properties = new StackTrace(null, null, null, null, null, true, null);
StackTracePrinter printer = properties.createPrinter();
assertThat(printer).isNotNull();
String actual = printer.printStackTraceToString(exception);
assertThat(actual).containsPattern("<#[0-9a-z]{8}>");
}

@ParameterizedTest
@CsvSource({
"org.springframework.boot.logging.TestException.createTestException, org.springframework.boot.logging.TestException.createException, org.springframework.boot.logging.TestException.createTestException",
"org.springframework.boot.logging.TestException.createException, org.springframework.boot.logging.TestException.createTestException, org.springframework.boot.logging.TestException.createException" })
void createPrinterWhenStandardWithExcludedFramesSuppressFrames(String excludedFrame, String present,
String absent) {
Exception exception = TestException.create();
StackTrace properties = new StackTrace(null, null, null, null, null, null, List.of(excludedFrame));
StackTracePrinter printer = properties.createPrinter();
assertThat(printer).isNotNull();
String actual = printer.printStackTraceToString(exception);
assertThat(actual).containsPattern(present).doesNotContain(absent).contains("filtered");
}

@Test
void createPrinterWhenClassNameCreatesPrinter() {
Exception exception = TestException.create();
StackTrace properties = new StackTrace(TestStackTracePrinter.class.getName(), null, null, null, true, null);
StackTrace properties = new StackTrace(TestStackTracePrinter.class.getName(), null, null, null, true, null,
null);
StackTracePrinter printer = properties.createPrinter();
assertThat(printer).isNotNull();
assertThat(printer.printStackTraceToString(exception)).isEqualTo("java.lang.RuntimeException: exception");
Expand All @@ -188,7 +207,7 @@ void createPrinterWhenClassNameCreatesPrinter() {
void createPrinterWhenClassNameInjectsConfiguredPrinter() {
Exception exception = TestException.create();
StackTrace properties = new StackTrace(TestStackTracePrinterCustomized.class.getName(), Root.FIRST, 300, 2,
true, null);
true, null, null);
StackTracePrinter printer = properties.createPrinter();
assertThat(printer).isNotNull();
String actual = TestException.withoutLineNumbers(printer.printStackTraceToString(exception));
Expand All @@ -197,25 +216,25 @@ void createPrinterWhenClassNameInjectsConfiguredPrinter() {

@Test
void hasCustomPrinterShouldReturnFalseWhenPrinterIsEmpty() {
StackTrace stackTrace = new StackTrace("", null, null, null, null, null);
StackTrace stackTrace = new StackTrace("", null, null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isFalse();
}

@Test
void hasCustomPrinterShouldReturnFalseWhenPrinterHasLoggingSystem() {
StackTrace stackTrace = new StackTrace("loggingsystem", null, null, null, null, null);
StackTrace stackTrace = new StackTrace("loggingsystem", null, null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isFalse();
}

@Test
void hasCustomPrinterShouldReturnFalseWhenPrinterHasStandard() {
StackTrace stackTrace = new StackTrace("standard", null, null, null, null, null);
StackTrace stackTrace = new StackTrace("standard", null, null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isFalse();
}

@Test
void hasCustomPrinterShouldReturnTrueWhenPrinterHasCustom() {
StackTrace stackTrace = new StackTrace("custom-printer", null, null, null, null, null);
StackTrace stackTrace = new StackTrace("custom-printer", null, null, null, null, null, null);
assertThat(stackTrace.hasCustomPrinter()).isTrue();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,9 @@ To do this, you can use one or more of the following properties:

| configprop:logging.structured.json.stacktrace.include-hashes[]
| If a hash of the stack trace should be included

| configprop:logging.structured.json.stacktrace.exluded-frames[]
| List of patterns excluded from stacktrace, f.e. `java.lang.reflect.Method`, `ByCGLIB`
|===

For example, the following will use root first stack traces, limit their length, and include hashes.
Expand Down
Loading