Skip to content

Commit f74c821

Browse files
maxxedevphilsttr
andauthored
Make it possible to pretty print throwables as array of strings. (#1043)
* Make it possible to pretty print throwables as array of strings. fixes #1042 --------- Co-authored-by: Phil Clay <philsttr@users.noreply.github.com>
1 parent 358f644 commit f74c821

File tree

8 files changed

+129
-4
lines changed

8 files changed

+129
-4
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,19 @@ For example:
16341634
See the [net.logstash.logback.decorate](/src/main/java/net/logstash/logback/decorate) package
16351635
and sub-packages for other decorators.
16361636

1637+
If you prefer pretty printing for easier interactive viewing of (error) logs, you
1638+
may also prefer to write the stacktrace as an array of strings where each string
1639+
is a stacktrace line:
1640+
1641+
```xml
1642+
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
1643+
<decorator class="net.logstash.logback.decorate.PrettyPrintingDecorator">
1644+
<indentArraysWithNewLine>true</indentArraysWithNewLine>
1645+
</decorator>
1646+
<writeStackTraceAsArray>true</writeStackTraceAsArray>
1647+
</encoder>
1648+
```
1649+
16371650
### Registering Jackson Modules
16381651

16391652
By default, Jackson modules are dynamically registered via
@@ -2871,6 +2884,7 @@ The provider name is the xml element name to use when configuring. Each provider
28712884
<ul>
28722885
<li><tt>fieldName</tt> - Output field name (<tt>stack_trace</tt>)</li>
28732886
<li><tt>throwableConverter</tt> - The <tt>ThrowableHandlingConverter</tt> to use to format the stacktrace (<tt>stack_trace</tt>)</li>
2887+
<li><tt>writeAsArray</tt> - write the stacktrace as an array of strings where each string is a stacktrace line</li>
28742888
</ul>
28752889
</td>
28762890
</tr>

src/main/java/net/logstash/logback/LogstashFormatter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,14 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter)
396396
this.stackTraceProvider.setThrowableConverter(throwableConverter);
397397
}
398398

399+
public boolean isWriteStackTraceAsArray() {
400+
return this.stackTraceProvider.isWriteAsArray();
401+
}
402+
403+
public void setWriteStackTraceAsArray(boolean writeStackTraceAsArray) {
404+
this.stackTraceProvider.setWriteAsArray(writeStackTraceAsArray);
405+
}
406+
399407
public String getVersion() {
400408
return this.versionProvider.getVersion();
401409
}

src/main/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProvider.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public class StackTraceJsonProvider extends AbstractFieldJsonProvider<ILoggingEv
4141
*/
4242
private ThrowableHandlingConverter throwableConverter = new ExtendedThrowableProxyConverter();
4343

44+
/**
45+
* If true, stacktrace will be output as a json array of strings split by newlines
46+
* If else, stacktrace will be output as a json string
47+
*/
48+
private boolean writeAsArray;
49+
4450
public StackTraceJsonProvider() {
4551
setFieldName(FIELD_STACK_TRACE);
4652
}
@@ -60,8 +66,15 @@ public void stop() {
6066
@Override
6167
public void writeTo(JsonGenerator generator, ILoggingEvent event) {
6268
IThrowableProxy throwableProxy = event.getThrowableProxy();
63-
if (throwableProxy != null) {
64-
JsonWritingUtils.writeStringField(generator, getFieldName(), throwableConverter.convert(event));
69+
if (throwableProxy == null) {
70+
return;
71+
}
72+
String stacktrace = throwableConverter.convert(event);
73+
if (writeAsArray) {
74+
String[] lines = stacktrace.split("\n");
75+
JsonWritingUtils.writeStringArrayField(generator, getFieldName(), lines);
76+
} else {
77+
JsonWritingUtils.writeStringField(generator, getFieldName(), stacktrace);
6578
}
6679
}
6780

@@ -73,7 +86,17 @@ public void setFieldNames(LogstashFieldNames fieldNames) {
7386
public ThrowableHandlingConverter getThrowableConverter() {
7487
return throwableConverter;
7588
}
89+
7690
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) {
7791
this.throwableConverter = throwableConverter;
7892
}
93+
94+
public boolean isWriteAsArray() {
95+
return writeAsArray;
96+
}
97+
98+
public void setWriteAsArray(boolean writeAsArray) {
99+
this.writeAsArray = writeAsArray;
100+
}
101+
79102
}

src/main/java/net/logstash/logback/decorate/PrettyPrintingDecorator.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import ch.qos.logback.core.CoreConstants;
1919
import tools.jackson.core.PrettyPrinter;
20+
import tools.jackson.core.util.DefaultIndenter;
2021
import tools.jackson.core.util.DefaultPrettyPrinter;
2122
import tools.jackson.core.util.Separators;
2223
import tools.jackson.databind.ObjectMapper;
@@ -28,10 +29,13 @@
2829
*/
2930
public class PrettyPrintingDecorator<M extends ObjectMapper, B extends MapperBuilder<M, B>> implements MapperBuilderDecorator<M, B> {
3031

32+
private static final DefaultPrettyPrinter.FixedSpaceIndenter DEFAULT_ARRAY_INDENTER = DefaultPrettyPrinter.FixedSpaceIndenter.instance();
33+
3134
private Separators separators = PrettyPrinter.DEFAULT_SEPARATORS
3235
.withRootSeparator(CoreConstants.EMPTY_STRING);
3336

34-
private DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(separators);
37+
private DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(separators)
38+
.withArrayIndenter(DEFAULT_ARRAY_INDENTER);
3539

3640
@Override
3741
public B decorate(B mapperBuilder) {
@@ -66,4 +70,17 @@ public void setSpacesInObjectEntries(boolean spacesInObjectEntries) {
6670
separators = separators.withObjectNameValueSpacing(spacesInObjectEntries ? Separators.Spacing.BOTH : Separators.Spacing.NONE);
6771
prettyPrinter = prettyPrinter.withSeparators(separators);
6872
}
73+
74+
/**
75+
* Sets whether arrays are indented with a new line.
76+
*
77+
* @param indentArraysWithNewLine whether arrays are indented with a new line.
78+
*/
79+
public void setIndentArraysWithNewLine(boolean indentArraysWithNewLine) {
80+
if (indentArraysWithNewLine) {
81+
prettyPrinter = prettyPrinter.withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
82+
} else {
83+
prettyPrinter = prettyPrinter.withArrayIndenter(DEFAULT_ARRAY_INDENTER);
84+
}
85+
}
6986
}

src/main/java/net/logstash/logback/encoder/LogstashEncoder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter)
203203
getFormatter().setThrowableConverter(throwableConverter);
204204
}
205205

206+
public boolean isWriteStackTraceAsArray() {
207+
return getFormatter().isWriteStackTraceAsArray();
208+
}
209+
210+
public void setWriteStackTraceAsArray(boolean writeStackTraceAsArray) {
211+
this.getFormatter().setWriteStackTraceAsArray(writeStackTraceAsArray);
212+
}
213+
206214
public String getTimeZone() {
207215
return getFormatter().getTimeZone();
208216
}

src/main/java/net/logstash/logback/layout/LogstashLayout.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,15 @@ public void setTimestampPattern(String pattern) {
215215
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) {
216216
getFormatter().setThrowableConverter(throwableConverter);
217217
}
218-
218+
219+
public boolean isWriteStackTraceAsArray() {
220+
return getFormatter().isWriteStackTraceAsArray();
221+
}
222+
223+
public void setWriteStackTraceAsArray(boolean writeStackTraceAsArray) {
224+
this.getFormatter().setWriteStackTraceAsArray(writeStackTraceAsArray);
225+
}
226+
219227
public String getVersion() {
220228
return getFormatter().getVersion();
221229
}

src/test/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProviderTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package net.logstash.logback.composite.loggingevent;
1717

18+
import static org.mockito.Mockito.anyString;
19+
import static org.mockito.Mockito.atLeastOnce;
1820
import static org.mockito.Mockito.verify;
1921
import static org.mockito.Mockito.when;
2022

@@ -25,6 +27,7 @@
2527
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
2628
import ch.qos.logback.classic.spi.ILoggingEvent;
2729
import ch.qos.logback.classic.spi.IThrowableProxy;
30+
import org.assertj.core.util.Throwables;
2831
import org.junit.jupiter.api.BeforeEach;
2932
import org.junit.jupiter.api.Test;
3033
import org.junit.jupiter.api.extension.ExtendWith;
@@ -90,4 +93,22 @@ public void testFieldNames() {
9093
verify(generator).writeStringProperty("newFieldName", "stack");
9194
}
9295

96+
@Test
97+
public void testWriteAsArray() {
98+
String stacktrace = Throwables.getStackTrace(new RuntimeException("testing exception handling"));
99+
when(converter.convert(event)).thenReturn(stacktrace);
100+
101+
provider.setWriteAsArray(true);
102+
103+
when(event.getThrowableProxy()).thenReturn(ThrowableProxy);
104+
105+
provider.writeTo(generator, event);
106+
107+
verify(generator).writeName("stack_trace");
108+
verify(generator).writeStartArray();
109+
verify(generator).writeString("java.lang.RuntimeException: testing exception handling");
110+
verify(generator, atLeastOnce()).writeString(anyString());
111+
verify(generator).writeEndArray();
112+
}
113+
93114
}

src/test/java/net/logstash/logback/decorate/PrettyPrintingDecoratorTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.StringWriter;
2121
import java.util.Collections;
22+
import java.util.List;
2223

2324
import org.junit.jupiter.api.Test;
2425
import tools.jackson.core.JsonGenerator;
@@ -89,4 +90,29 @@ void noSpacesInObjectEntries() {
8990
writer.flush();
9091
assertThat(writer.toString()).isEqualTo("{\n \"key1\":\"value1\"\n}{\n \"key2\":\"value2\"\n}");
9192
}
93+
94+
@Test
95+
void arrayElementsOnNewLine() {
96+
PrettyPrintingDecorator<JsonMapper, JsonMapper.Builder> decorator = new PrettyPrintingDecorator<>();
97+
decorator.setIndentArraysWithNewLine(true);
98+
99+
StringWriter writer = new StringWriter();
100+
JsonGenerator generator = decorator.decorate(JsonMapper.builder()).build().createGenerator(writer);
101+
102+
generator.writePOJO(Collections.singletonMap("key1", List.of(
103+
"RuntimeException: foobar",
104+
"\tat com.example.Foobar")));
105+
generator.writePOJO(Collections.singletonMap("key2", "value2"));
106+
107+
generator.flush();
108+
writer.flush();
109+
assertThat(writer.toString()).isEqualTo("{\n"
110+
+ " \"key1\" : [\n"
111+
+ " \"RuntimeException: foobar\",\n"
112+
+ " \"\\tat com.example.Foobar\"\n"
113+
+ " ]\n"
114+
+ "}{\n"
115+
+ " \"key2\" : \"value2\"\n"
116+
+ "}");
117+
}
92118
}

0 commit comments

Comments
 (0)