diff --git a/README.md b/README.md
index d974dc2d..09a745b8 100644
--- a/README.md
+++ b/README.md
@@ -1634,6 +1634,19 @@ For example:
See the [net.logstash.logback.decorate](/src/main/java/net/logstash/logback/decorate) package
and sub-packages for other decorators.
+If you prefer pretty printing for easier interactive viewing of (error) logs, you
+may also prefer to write the stacktrace as an array of strings where each string
+is a stacktrace line:
+
+```xml
+
+
+ true
+
+ true
+
+```
+
### Registering Jackson Modules
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
- fieldName - Output field name (stack_trace)
- throwableConverter - The ThrowableHandlingConverter to use to format the stacktrace (stack_trace)
+ - writeAsArray - write the stacktrace as an array of strings where each string is a stacktrace line
diff --git a/src/main/java/net/logstash/logback/LogstashFormatter.java b/src/main/java/net/logstash/logback/LogstashFormatter.java
index 75f6288e..0ec312ef 100644
--- a/src/main/java/net/logstash/logback/LogstashFormatter.java
+++ b/src/main/java/net/logstash/logback/LogstashFormatter.java
@@ -396,6 +396,14 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter)
this.stackTraceProvider.setThrowableConverter(throwableConverter);
}
+ public boolean isWriteStackTraceAsArray() {
+ return this.stackTraceProvider.isWriteAsArray();
+ }
+
+ public void setWriteStackTraceAsArray(boolean writeStackTraceAsArray) {
+ this.stackTraceProvider.setWriteAsArray(writeStackTraceAsArray);
+ }
+
public String getVersion() {
return this.versionProvider.getVersion();
}
diff --git a/src/main/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProvider.java b/src/main/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProvider.java
index 52f88b02..a4987502 100644
--- a/src/main/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProvider.java
+++ b/src/main/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProvider.java
@@ -41,6 +41,12 @@ public class StackTraceJsonProvider extends AbstractFieldJsonProvider> implements MapperBuilderDecorator {
+ private static final DefaultPrettyPrinter.FixedSpaceIndenter DEFAULT_ARRAY_INDENTER = DefaultPrettyPrinter.FixedSpaceIndenter.instance();
+
private Separators separators = PrettyPrinter.DEFAULT_SEPARATORS
.withRootSeparator(CoreConstants.EMPTY_STRING);
- private DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(separators);
+ private DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(separators)
+ .withArrayIndenter(DEFAULT_ARRAY_INDENTER);
@Override
public B decorate(B mapperBuilder) {
@@ -66,4 +70,17 @@ public void setSpacesInObjectEntries(boolean spacesInObjectEntries) {
separators = separators.withObjectNameValueSpacing(spacesInObjectEntries ? Separators.Spacing.BOTH : Separators.Spacing.NONE);
prettyPrinter = prettyPrinter.withSeparators(separators);
}
+
+ /**
+ * Sets whether arrays are indented with a new line.
+ *
+ * @param indentArraysWithNewLine whether arrays are indented with a new line.
+ */
+ public void setIndentArraysWithNewLine(boolean indentArraysWithNewLine) {
+ if (indentArraysWithNewLine) {
+ prettyPrinter = prettyPrinter.withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
+ } else {
+ prettyPrinter = prettyPrinter.withArrayIndenter(DEFAULT_ARRAY_INDENTER);
+ }
+ }
}
diff --git a/src/main/java/net/logstash/logback/encoder/LogstashEncoder.java b/src/main/java/net/logstash/logback/encoder/LogstashEncoder.java
index 94cb4aae..58805fdd 100644
--- a/src/main/java/net/logstash/logback/encoder/LogstashEncoder.java
+++ b/src/main/java/net/logstash/logback/encoder/LogstashEncoder.java
@@ -203,6 +203,14 @@ public void setThrowableConverter(ThrowableHandlingConverter throwableConverter)
getFormatter().setThrowableConverter(throwableConverter);
}
+ public boolean isWriteStackTraceAsArray() {
+ return getFormatter().isWriteStackTraceAsArray();
+ }
+
+ public void setWriteStackTraceAsArray(boolean writeStackTraceAsArray) {
+ this.getFormatter().setWriteStackTraceAsArray(writeStackTraceAsArray);
+ }
+
public String getTimeZone() {
return getFormatter().getTimeZone();
}
diff --git a/src/main/java/net/logstash/logback/layout/LogstashLayout.java b/src/main/java/net/logstash/logback/layout/LogstashLayout.java
index 0d473958..b0621320 100644
--- a/src/main/java/net/logstash/logback/layout/LogstashLayout.java
+++ b/src/main/java/net/logstash/logback/layout/LogstashLayout.java
@@ -215,7 +215,15 @@ public void setTimestampPattern(String pattern) {
public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) {
getFormatter().setThrowableConverter(throwableConverter);
}
-
+
+ public boolean isWriteStackTraceAsArray() {
+ return getFormatter().isWriteStackTraceAsArray();
+ }
+
+ public void setWriteStackTraceAsArray(boolean writeStackTraceAsArray) {
+ this.getFormatter().setWriteStackTraceAsArray(writeStackTraceAsArray);
+ }
+
public String getVersion() {
return getFormatter().getVersion();
}
diff --git a/src/test/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProviderTest.java b/src/test/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProviderTest.java
index 3814eead..a2e5bc0e 100644
--- a/src/test/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProviderTest.java
+++ b/src/test/java/net/logstash/logback/composite/loggingevent/StackTraceJsonProviderTest.java
@@ -15,6 +15,8 @@
*/
package net.logstash.logback.composite.loggingevent;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -25,6 +27,7 @@
import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
+import org.assertj.core.util.Throwables;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -90,4 +93,22 @@ public void testFieldNames() {
verify(generator).writeStringProperty("newFieldName", "stack");
}
+ @Test
+ public void testWriteAsArray() {
+ String stacktrace = Throwables.getStackTrace(new RuntimeException("testing exception handling"));
+ when(converter.convert(event)).thenReturn(stacktrace);
+
+ provider.setWriteAsArray(true);
+
+ when(event.getThrowableProxy()).thenReturn(ThrowableProxy);
+
+ provider.writeTo(generator, event);
+
+ verify(generator).writeName("stack_trace");
+ verify(generator).writeStartArray();
+ verify(generator).writeString("java.lang.RuntimeException: testing exception handling");
+ verify(generator, atLeastOnce()).writeString(anyString());
+ verify(generator).writeEndArray();
+ }
+
}
diff --git a/src/test/java/net/logstash/logback/decorate/PrettyPrintingDecoratorTest.java b/src/test/java/net/logstash/logback/decorate/PrettyPrintingDecoratorTest.java
index d88135c4..954378b6 100644
--- a/src/test/java/net/logstash/logback/decorate/PrettyPrintingDecoratorTest.java
+++ b/src/test/java/net/logstash/logback/decorate/PrettyPrintingDecoratorTest.java
@@ -19,6 +19,7 @@
import java.io.StringWriter;
import java.util.Collections;
+import java.util.List;
import org.junit.jupiter.api.Test;
import tools.jackson.core.JsonGenerator;
@@ -89,4 +90,29 @@ void noSpacesInObjectEntries() {
writer.flush();
assertThat(writer.toString()).isEqualTo("{\n \"key1\":\"value1\"\n}{\n \"key2\":\"value2\"\n}");
}
+
+ @Test
+ void arrayElementsOnNewLine() {
+ PrettyPrintingDecorator decorator = new PrettyPrintingDecorator<>();
+ decorator.setIndentArraysWithNewLine(true);
+
+ StringWriter writer = new StringWriter();
+ JsonGenerator generator = decorator.decorate(JsonMapper.builder()).build().createGenerator(writer);
+
+ generator.writePOJO(Collections.singletonMap("key1", List.of(
+ "RuntimeException: foobar",
+ "\tat com.example.Foobar")));
+ generator.writePOJO(Collections.singletonMap("key2", "value2"));
+
+ generator.flush();
+ writer.flush();
+ assertThat(writer.toString()).isEqualTo("{\n"
+ + " \"key1\" : [\n"
+ + " \"RuntimeException: foobar\",\n"
+ + " \"\\tat com.example.Foobar\"\n"
+ + " ]\n"
+ + "}{\n"
+ + " \"key2\" : \"value2\"\n"
+ + "}");
+ }
}