Skip to content

Commit 68ecbe5

Browse files
committed
Add stackhash field to JsonEncoder (on by default)
The StackHasher used includes all the common stack element filtering such that it should produce a relatively stable hash.
1 parent c705c20 commit 68ecbe5

File tree

4 files changed

+58
-2
lines changed

4 files changed

+58
-2
lines changed

src/main/java/io/avaje/logback/encoder/FilterBuilder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public StackElementFilter.Builder byPattern(List<Pattern> excludes) {
4040
return this;
4141
}
4242

43+
@Override
44+
public StackElementFilter.Builder allFilters() {
45+
generated();
46+
reflectiveInvocation();
47+
jdkInternals();
48+
spring();
49+
return this;
50+
}
51+
4352
@Override
4453
public StackElementFilter build() {
4554
if (filters.isEmpty()) {

src/main/java/io/avaje/logback/encoder/JsonEncoder.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
1111
import ch.qos.logback.classic.spi.ILoggingEvent;
12+
import ch.qos.logback.classic.spi.IThrowableProxy;
13+
import ch.qos.logback.classic.spi.ThrowableProxy;
1214
import ch.qos.logback.core.encoder.EncoderBase;
1315
import io.avaje.json.PropertyNames;
1416
import io.avaje.json.simple.SimpleMapper;
@@ -20,6 +22,7 @@ public final class JsonEncoder extends EncoderBase<ILoggingEvent> {
2022
private final JsonStream json;
2123
private final Map<String, String> customFieldsMap = new HashMap<>();
2224
private final PropertyNames properties;
25+
private final StackHasher stackHasher;
2326
private ThrowableHandlingConverter throwableConverter = new ShortenedThrowableConverter();
2427

2528
private DateTimeFormatter formatter;
@@ -29,12 +32,14 @@ public final class JsonEncoder extends EncoderBase<ILoggingEvent> {
2932
private int fieldExtra;
3033
private String component;
3134
private String environment;
35+
private boolean includeStackHash = true;
3236

3337
public JsonEncoder() {
3438
this.json = JsonStream.builder().build();
35-
this.properties = json.properties("component", "env", "timestamp", "level", "logger", "message", "thread", "stacktrace");
39+
this.properties = json.properties("component", "env", "timestamp", "level", "logger", "message", "thread", "stackhash", "stacktrace");
3640
this.component = System.getenv("COMPONENT");
3741
this.environment = System.getenv("ENVIRONMENT");
42+
this.stackHasher = new StackHasher(StackElementFilter.builder().allFilters().build());
3843
}
3944

4045
@Override
@@ -96,7 +101,15 @@ public byte[] encode(ILoggingEvent event) {
96101
writer.name(6);
97102
writer.value(threadName);
98103
if (!stackTraceBody.isEmpty()) {
99-
writer.name(7);
104+
if (includeStackHash) {
105+
IThrowableProxy throwableProxy = event.getThrowableProxy();
106+
if (throwableProxy instanceof ThrowableProxy) {
107+
String hash = stackHasher.hexHash(((ThrowableProxy) throwableProxy).getThrowable());
108+
writer.name(7);
109+
writer.value(hash);
110+
}
111+
}
112+
writer.name(8);
100113
writer.value(stackTraceBody);
101114
}
102115
customFieldsMap.forEach((k, v) -> {
@@ -113,6 +126,10 @@ public byte[] encode(ILoggingEvent event) {
113126
return outputStream.toByteArray();
114127
}
115128

129+
public void setIncludeStackHash(boolean includeStackHash) {
130+
this.includeStackHash = includeStackHash;
131+
}
132+
116133
public void setComponent(String component) {
117134
this.component = component;
118135
}

src/main/java/io/avaje/logback/encoder/StackElementFilter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,15 @@ interface Builder {
101101
*/
102102
Builder byPattern(List<Pattern> excludes);
103103

104+
/**
105+
* Include all the standard filters generated, reflective invocation, jdk internals and spring.
106+
*/
107+
Builder allFilters();
108+
104109
/**
105110
* Build and return the StackElementFilter with the given options.
106111
*/
107112
StackElementFilter build();
113+
108114
}
109115
}

src/test/java/io/avaje/logback/encoder/JsonEncoderTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,37 @@ void throwable_usingConverter() {
9090

9191
JsonEncoder encoder = new JsonEncoder();
9292
encoder.setThrowableConverter(converter);
93+
encoder.setIncludeStackHash(false);
9394
encoder.start();
9495

9596
byte[] bytes = encoder.encode(createLogEvent(createThrowable()));
9697
SimpleMapper simpleMapper = SimpleMapper.builder().build();
9798
Map<String, Object> asMap = simpleMapper.map().fromJson(bytes);
9899

99100
assertThat((String)asMap.get("stacktrace")).startsWith("j.l.NullPointerException: ");
101+
assertThat(asMap).doesNotContainKey("stackhash");
102+
}
103+
104+
@Test
105+
void throwable_usingConverter_includeStackHash() {
106+
final TrimPackageAbbreviator trimPackages = new TrimPackageAbbreviator();
107+
trimPackages.setTargetLength(10);
108+
109+
final ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
110+
converter.setMaxDepthPerThrowable(3);
111+
converter.setClassNameAbbreviator(trimPackages);
112+
113+
JsonEncoder encoder = new JsonEncoder();
114+
encoder.setThrowableConverter(converter);
115+
encoder.setIncludeStackHash(true);
116+
encoder.start();
117+
118+
byte[] bytes = encoder.encode(createLogEvent(createThrowable()));
119+
SimpleMapper simpleMapper = SimpleMapper.builder().build();
120+
Map<String, Object> asMap = simpleMapper.map().fromJson(bytes);
121+
122+
assertThat((String)asMap.get("stacktrace")).startsWith("j.l.NullPointerException: ");
123+
assertThat((String)asMap.get("stackhash")).isEqualTo("2a4a23a6");
100124
}
101125

102126
@Test

0 commit comments

Comments
 (0)