Skip to content
Merged
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<decorator class="net.logstash.logback.decorate.PrettyPrintingDecorator">
<indentArraysWithNewLine>true</indentArraysWithNewLine>
</decorator>
<writeStackTraceAsArray>true</writeStackTraceAsArray>
</encoder>
```

### Registering Jackson Modules

By default, Jackson modules are dynamically registered via
Expand Down Expand Up @@ -2871,6 +2884,7 @@ The provider name is the xml element name to use when configuring. Each provider
<ul>
<li><tt>fieldName</tt> - Output field name (<tt>stack_trace</tt>)</li>
<li><tt>throwableConverter</tt> - The <tt>ThrowableHandlingConverter</tt> to use to format the stacktrace (<tt>stack_trace</tt>)</li>
<li><tt>writeAsArray</tt> - write the stacktrace as an array of strings where each string is a stacktrace line</li>
</ul>
</td>
</tr>
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/net/logstash/logback/LogstashFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public class StackTraceJsonProvider extends AbstractFieldJsonProvider<ILoggingEv
*/
private ThrowableHandlingConverter throwableConverter = new ExtendedThrowableProxyConverter();

/**
* If true, stacktrace will be output as a json array of strings split by newlines
* If else, stacktrace will be output as a json string
*/
private boolean writeAsArray;

public StackTraceJsonProvider() {
setFieldName(FIELD_STACK_TRACE);
}
Expand All @@ -60,8 +66,15 @@ public void stop() {
@Override
public void writeTo(JsonGenerator generator, ILoggingEvent event) {
IThrowableProxy throwableProxy = event.getThrowableProxy();
if (throwableProxy != null) {
JsonWritingUtils.writeStringField(generator, getFieldName(), throwableConverter.convert(event));
if (throwableProxy == null) {
return;
}
String stacktrace = throwableConverter.convert(event);
if (writeAsArray) {
String[] lines = stacktrace.split("\n");
JsonWritingUtils.writeStringArrayField(generator, getFieldName(), lines);
} else {
JsonWritingUtils.writeStringField(generator, getFieldName(), stacktrace);
}
}

Expand All @@ -73,7 +86,17 @@ public void setFieldNames(LogstashFieldNames fieldNames) {
public ThrowableHandlingConverter getThrowableConverter() {
return throwableConverter;
}

public void setThrowableConverter(ThrowableHandlingConverter throwableConverter) {
this.throwableConverter = throwableConverter;
}

public boolean isWriteAsArray() {
return writeAsArray;
}

public void setWriteAsArray(boolean writeAsArray) {
this.writeAsArray = writeAsArray;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import ch.qos.logback.core.CoreConstants;
import tools.jackson.core.PrettyPrinter;
import tools.jackson.core.util.DefaultIndenter;
import tools.jackson.core.util.DefaultPrettyPrinter;
import tools.jackson.core.util.Separators;
import tools.jackson.databind.ObjectMapper;
Expand All @@ -28,10 +29,13 @@
*/
public class PrettyPrintingDecorator<M extends ObjectMapper, B extends MapperBuilder<M, B>> implements MapperBuilderDecorator<M, B> {

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) {
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/net/logstash/logback/layout/LogstashLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -89,4 +90,29 @@ void noSpacesInObjectEntries() {
writer.flush();
assertThat(writer.toString()).isEqualTo("{\n \"key1\":\"value1\"\n}{\n \"key2\":\"value2\"\n}");
}

@Test
void arrayElementsOnNewLine() {
PrettyPrintingDecorator<JsonMapper, JsonMapper.Builder> 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"
+ "}");
}
}