Skip to content

Commit

Permalink
Make it possible to pretty print throwables as array of strings.
Browse files Browse the repository at this point in the history
  • Loading branch information
maxxedev committed Aug 18, 2024
1 parent 76f5295 commit 4a4f0f0
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 2 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,18 @@ 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 output throwable as json array of strings where each string
is a stacktrace line:

```xml
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator">
<indentArraysWithNewLine>true</indentArraysWithNewLine>
<outputThrowableAsArray>true</outputThrowableAsArray>
</encoder>
```

### Registering Jackson Modules

By default, Jackson modules are dynamically registered via
Expand Down Expand Up @@ -2766,6 +2778,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>outputThrowableAsArray</tt> - Output throwable as json 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 isOutputThrowableAsArray() {
return this.stackTraceProvider.isOutputThrowableAsArray();
}

public void setOutputThrowableAsArray(boolean outputAsArray) {
this.stackTraceProvider.setOutputThrowableAsArray(outputAsArray);
}

public String getVersion() {
return this.versionProvider.getVersion();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public class StackTraceJsonProvider extends AbstractFieldJsonProvider<ILoggingEv

public static final String FIELD_STACK_TRACE = "stack_trace";

/**
* 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 outputThrowableAsArray = false;

/**
* Used to format throwables as Strings.
*
Expand All @@ -47,6 +53,14 @@ public StackTraceJsonProvider() {
setFieldName(FIELD_STACK_TRACE);
}

public boolean isOutputThrowableAsArray() {
return outputThrowableAsArray;
}

public void setOutputThrowableAsArray(boolean outputThrowableAsArray) {
this.outputThrowableAsArray = outputThrowableAsArray;
}

@Override
public void start() {
this.throwableConverter.start();
Expand All @@ -62,8 +76,15 @@ public void stop() {
@Override
public void writeTo(JsonGenerator generator, ILoggingEvent event) throws IOException {
IThrowableProxy throwableProxy = event.getThrowableProxy();
if (throwableProxy != null) {
JsonWritingUtils.writeStringField(generator, getFieldName(), throwableConverter.convert(event));
if (throwableProxy == null) {
return;
}
String stacktrace = throwableConverter.convert(event);
if (outputThrowableAsArray) {
String[] lines = stacktrace.split("\n");
JsonWritingUtils.writeStringArrayField(generator, getFieldName(), lines);
} else {
JsonWritingUtils.writeStringField(generator, getFieldName(), stacktrace);
}
}

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

import ch.qos.logback.core.CoreConstants;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;

/**
Expand Down Expand Up @@ -62,4 +63,12 @@ public void setSpacesInObjectEntries(boolean spacesInObjectEntries) {
prettyPrinter = prettyPrinter.withoutSpacesInObjectEntries();
}
}

public void setIndentArraysWithNewLine(boolean indentArraysWithNewLine) {
if (indentArraysWithNewLine) {
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
} else {
prettyPrinter.indentArraysWith(null);
}
}
}
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 isOutputThrowableAsArray() {
return getFormatter().isOutputThrowableAsArray();
}

public void setOutputThrowableAsArray(boolean outputAsArray) {
this.getFormatter().setOutputThrowableAsArray(outputAsArray);
}

public String getTimeZone() {
return getFormatter().getTimeZone();
}
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 @@ -26,6 +28,7 @@
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import com.fasterxml.jackson.core.JsonGenerator;
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,21 @@ public void testFieldNames() throws IOException {
verify(generator).writeStringField("newFieldName", "stack");
}

@Test
public void testOutputAsArray() throws IOException {
String stacktrace = Throwables.getStackTrace(new RuntimeException("testing exception handling"));
when(converter.convert(event)).thenReturn(stacktrace);

provider.setOutputThrowableAsArray(true);

when(event.getThrowableProxy()).thenReturn(ThrowableProxy);

provider.writeTo(generator, event);

verify(generator).writeArrayFieldStart("stack_trace");
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.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;

import com.fasterxml.jackson.core.JsonGenerator;
Expand Down Expand Up @@ -94,4 +95,29 @@ void noSpacesInObjectEntries() throws IOException {
writer.flush();
assertThat(writer.toString()).isEqualTo("{\n \"key1\":\"value1\"\n}{\n \"key2\":\"value2\"\n}");
}

@Test
void arrayElementsOnNewLine() throws IOException {
PrettyPrintingJsonGeneratorDecorator decorator = new PrettyPrintingJsonGeneratorDecorator();
decorator.setIndentArraysWithNewLine(true);

StringWriter writer = new StringWriter();
ObjectMapper objectMapper = new ObjectMapper();
JsonGenerator generator = decorator.decorate(objectMapper.createGenerator(writer));

generator.writeObject(Collections.singletonMap("key1", Arrays.asList("RuntimeException: foobar",
"\tat com.example.Foobar")));
generator.writeObject(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" +
"}");
}
}

0 comments on commit 4a4f0f0

Please sign in to comment.