Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow inline stacktrace generation #807

Closed
Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,7 @@ is included in the logstash-logback-encoder library to format stacktraces by:
* Using evaluators to determine if the stacktrace should be logged.
* Outputting in either 'normal' order (root-cause-last), or root-cause-first.
* Computing and inlining hexadecimal hashes for each exception stack using the `inlineHash` or `stackHash` provider ([more info](stack-hash.md)).
* Use custom line separator for stack traces

For example:

Expand All @@ -1766,6 +1767,7 @@ For example:
<evaluator class="myorg.MyCustomEvaluator"/>
<rootCauseFirst>true</rootCauseFirst>
<inlineHash>true</inlineHash>
<lineSeparator>\\n</lineSeparator>
</throwableConverter>
</encoder>
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluator;
import ch.qos.logback.core.status.ErrorStatus;
import net.logstash.logback.encoder.SeparatorParser;

/**
* A {@link ThrowableHandlingConverter} (similar to logback's {@link ThrowableProxyConverter})
Expand Down Expand Up @@ -112,10 +113,15 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
private static final String OPTION_VALUE_ROOT_FIRST = "rootFirst";
private static final String OPTION_VALUE_INLINE_HASH = "inlineHash";

private static final String OPTION_VALUE_INLINE_STACK = "inline";

private static final int OPTION_INDEX_MAX_DEPTH = 0;
private static final int OPTION_INDEX_SHORTENED_CLASS_NAME = 1;
private static final int OPTION_INDEX_MAX_LENGTH = 2;

/** String sequence to use to delimit lines instead of {@link CoreConstants#LINE_SEPARATOR} when inline is active */
public static final String DEFAULT_INLINE_SEPARATOR = "\\n";

private AtomicInteger errorCount = new AtomicInteger();

/**
Expand Down Expand Up @@ -160,6 +166,9 @@ public class ShortenedThrowableConverter extends ThrowableHandlingConverter {
*/
private boolean inlineHash;

/** line delimiter */
private String lineSeparator = CoreConstants.LINE_SEPARATOR;

private StackElementFilter stackElementFilter;

private StackHasher stackHasher;
Expand Down Expand Up @@ -217,13 +226,16 @@ private void parseOptions() {
* Remaining options are either
* - "rootFirst" - indicating that stacks should be printed root-cause first
* - "inlineHash" - indicating that hexadecimal error hashes should be computed and inlined
* - "inline" - indicating that the whole stack trace should be inlined, using "\\n" as separator
* - evaluator name - name of evaluators that will determine if the stacktrace is ignored
* - exclusion pattern - pattern for stack trace elements to exclude
*/
if (OPTION_VALUE_ROOT_FIRST.equals(option)) {
setRootCauseFirst(true);
} else if (OPTION_VALUE_INLINE_HASH.equals(option)) {
setInlineHash(true);
} else if (OPTION_VALUE_INLINE_STACK.equals(option)) {
setLineSeparator(DEFAULT_INLINE_SEPARATOR);
} else {
@SuppressWarnings("rawtypes")
Map evaluatorMap = (Map) getContext().getObject(CoreConstants.EVALUATOR_MAP);
Expand Down Expand Up @@ -282,12 +294,36 @@ public String convert(ILoggingEvent event) {
appendRootCauseLast(builder, null, ThrowableProxyUtil.REGULAR_EXCEPTION_INDENT, throwableProxy, stackHashes);
}
if (builder.length() > maxLength) {
builder.setLength(maxLength - ELLIPSIS.length() - CoreConstants.LINE_SEPARATOR.length());
builder.append(ELLIPSIS).append(CoreConstants.LINE_SEPARATOR);
builder.setLength(maxLength - ELLIPSIS.length() - getLineSeparator().length());
builder.append(ELLIPSIS).append(getLineSeparator());
}
return builder.toString();
}

public String getLineSeparator() {
return lineSeparator;
}

/**
* Sets which lineSeparator to use between events.
* <p>
*
* The following values have special meaning:
* <ul>
* <li>{@code null} or empty string = no new line.</li>
* <li>"{@code SYSTEM}" = operating system new line (default).</li>
* <li>"{@code UNIX}" = unix line ending ({@code \n}).</li>
* <li>"{@code WINDOWS}" = windows line ending {@code \r\n}).</li>
* </ul>
* <p>
* Any other value will be used as given as the lineSeparator.
*
* @param lineSeparator the line separator
*/
public void setLineSeparator(String lineSeparator) {
MarneusCalgarXP marked this conversation as resolved.
Show resolved Hide resolved
this.lineSeparator = SeparatorParser.parseSeparator(lineSeparator);
}

/**
* Return true if any evaluator returns true, indicating that
* the stack trace should not be logged.
Expand Down Expand Up @@ -467,7 +503,7 @@ private void appendPlaceHolder(StringBuilder builder, int indent, int consecutiv
.append(consecutiveExcluded)
.append(" ")
.append(message)
.append(CoreConstants.LINE_SEPARATOR);
.append(getLineSeparator());
}

/**
Expand Down Expand Up @@ -506,7 +542,7 @@ private void appendStackTraceElement(StringBuilder builder, int indent, StackTra
if (shouldAppendPackagingData(step, previousStep)) {
appendPackagingData(builder, step);
}
builder.append(CoreConstants.LINE_SEPARATOR);
builder.append(getLineSeparator());
}

/**
Expand Down Expand Up @@ -547,7 +583,7 @@ private void appendFirstLine(StringBuilder builder, String prefix, int indent, I
builder.append(abbreviator.abbreviate(throwableProxy.getClassName()))
.append(": ")
.append(throwableProxy.getMessage())
.append(CoreConstants.LINE_SEPARATOR);
.append(getLineSeparator());
}

private void indent(StringBuilder builder, int indent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,17 +315,19 @@ public void testOptions() throws EvaluationException {
assertThat(converter.getShortenedClassNameLength()).isEqualTo(ShortenedThrowableConverter.FULL_CLASS_NAME_LENGTH);
assertThat(converter.getMaxLength()).isEqualTo(ShortenedThrowableConverter.FULL_MAX_LENGTH);
assertThat(converter.isRootCauseFirst()).isTrue();
assertThat(converter.getLineSeparator()).isEqualTo(CoreConstants.LINE_SEPARATOR);
assertThat(converter.getEvaluators().get(0)).isEqualTo(evaluator);
assertThat(converter.getExcludes().get(0)).isEqualTo("regex");

// test short values
converter.setOptionList(Arrays.asList("short", "short", "short", "rootFirst", "inlineHash", "evaluator", "regex"));
converter.setOptionList(Arrays.asList("short", "short", "short", "rootFirst", "inlineHash", "inline", "evaluator", "regex"));
converter.start();

assertThat(converter.getMaxDepthPerThrowable()).isEqualTo(ShortenedThrowableConverter.SHORT_MAX_DEPTH_PER_THROWABLE);
assertThat(converter.getShortenedClassNameLength()).isEqualTo(ShortenedThrowableConverter.SHORT_CLASS_NAME_LENGTH);
assertThat(converter.getMaxLength()).isEqualTo(ShortenedThrowableConverter.SHORT_MAX_LENGTH);

assertThat(converter.getLineSeparator()).isEqualTo(ShortenedThrowableConverter.DEFAULT_INLINE_SEPARATOR);

// test numeric values
converter.setOptionList(Arrays.asList("1", "2", "3"));
converter.start();
Expand Down Expand Up @@ -424,6 +426,30 @@ public void test_inline_hash_root_cause_first() {
}
}

@Test
public void test_inline_stack() {
try {
StackTraceElementGenerator.generateCausedBy();
fail("Exception must have been thrown");
} catch (RuntimeException e) {
// GIVEN
StackHasher mockedHasher = Mockito.mock(StackHasher.class);
ShortenedThrowableConverter converter = new ShortenedThrowableConverter();
converter.setLineSeparator(ShortenedThrowableConverter.DEFAULT_INLINE_SEPARATOR);
converter.setRootCauseFirst(true);
converter.start();
converter.setStackHasher(mockedHasher);

// WHEN
String formatted = converter.convert(createEvent(e));

// THEN
// verify we have the expected stack trace line separator inlined
assertThat(formatted).doesNotContain(CoreConstants.LINE_SEPARATOR);
assertThat(formatted).contains(ShortenedThrowableConverter.DEFAULT_INLINE_SEPARATOR);
}
}

@Test
public void test_inline_hash_with_suppressed() {
try {
Expand Down