Skip to content
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ Default max stack trace size is `300`. It is recommended that you do not exceed
lead to higher log event ingest costs. Max stack trace size can be configured by environment variable (`NEW_RELIC_LOG_EXTENSION_MAX_STACK_SIZE=integer`)
or system property (`-Dnewrelic.log_extension.max_stack_size=integer`).

### Include full exception stack trace
<b>Only supported on logback</b>

You can configure the logging extension to include in `error.stack` the full stack trace just like the downstream library does.
This includes the caused by chain.

As an example, this snippet:
```
Exception inner2 = new IllegalAccessException("test caused by 2");
Exception inner1 = new RuntimeException("test caused by 1", inner2);
LOGGER.error("failed for test!", new RuntimeException("test exception", inner1));
```
Will set the `error.stack` as
```
java.lang.RuntimeException: test exception
at (regular stacktrace)
at ...
Caused by: java.lang.RuntimeException: test caused by 1
... 49 common frames omitted
Caused by: java.lang.IllegalAccessException: test caused by 2
... 49 common frames omitted
```

Default is `false` for retro-compatibility.

This setting can be enabled via environment variable (`NEW_RELIC_LOG_EXTENSION_INCLUDE_FULL_ERROR_STACK=true`) or system variable (`-Dnewrelic.log_extension.include_full_error_stack=true`).

Be aware that enabling this setting will use the downstream library's stacktrace generation, not taking in consideration
the value of [Exception Stack Trace Size](#exception-stack-trace-size).

## Support

Should you need assistance with New Relic products, you are in good hands with several diagnostic tools and support channels.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public class LogExtensionConfig {
public static final String MAX_STACK_SIZE_SYS_PROP = CONFIG_PREFIX_SYS_PROP + "max_stack_size";
public static final String ADD_MDC_ENV_VAR = CONFIG_PREFIX_ENV_VAR + "ADD_MDC";
public static final String ADD_MDC_SYS_PROP = CONFIG_PREFIX_SYS_PROP + "add_mdc";

public static final String INCLUDE_FULL_ERROR_STACK_ENV = CONFIG_PREFIX_ENV_VAR + "INCLUDE_FULL_ERROR_STACK";
public static final String INCLUDE_FULL_ERROR_SYS_PROP = CONFIG_PREFIX_SYS_PROP + "include_full_error_stack";
public static final boolean ADD_MDC_DEFAULT = false;

/**
Expand All @@ -35,6 +38,24 @@ public static int getMaxStackSize() {
}
}

/**
* Get a boolean representing if error stack should include the full stacktrace, including the "caused by" chain
* <p/>Default, for retro-compatibility, is {@code false}.
* @return boolean representing include full error stack configuration
*/
public static boolean getIncludeFullErrorStacktrace(){
String envVar = System.getenv(INCLUDE_FULL_ERROR_STACK_ENV);
String sysProp = System.getProperty(INCLUDE_FULL_ERROR_SYS_PROP);

if (envVar != null){
return Boolean.parseBoolean(envVar);
} else if (sysProp != null) {
return Boolean.parseBoolean(sysProp);
} else {
return false;
}
}

/**
* Get a boolean indicating if MDC should be added to logs
* <p>
Expand Down
3 changes: 2 additions & 1 deletion logback/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
There are some changes to your application to use the New Relic
Logback Extension. All steps are required.

**Optional**: [Configuration Options](..%2FREADME.md#configuration-options) for setting max stack size or collecting MDC.
**Optional**: [Configuration Options](..%2FREADME.md#configuration-options) for setting max stack size (or to include
the full stack trace in `error.stack`) or collecting MDC.

### 1. Include the dependency in your project.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import static com.newrelic.logging.core.LogExtensionConfig.getIncludeFullErrorStacktrace;
import static com.newrelic.logging.core.LogExtensionConfig.getMaxStackSize;

/**
Expand Down Expand Up @@ -49,7 +50,7 @@ public byte[] encode(ILoggingEvent event) {
@Override
public void start() {
super.start();
layout = new NewRelicJsonLayout(maxStackSize);
layout = new NewRelicJsonLayout(maxStackSize, getIncludeFullErrorStacktrace());
layout.start();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.LayoutBase;
import com.fasterxml.jackson.core.JsonGenerator;
import com.newrelic.logging.core.ElementName;
Expand All @@ -21,18 +22,21 @@
import java.util.Map;

import static com.newrelic.logging.core.LogExtensionConfig.CONTEXT_PREFIX;
import static com.newrelic.logging.core.LogExtensionConfig.getIncludeFullErrorStacktrace;
import static com.newrelic.logging.core.LogExtensionConfig.getMaxStackSize;
import static com.newrelic.logging.logback.NewRelicAsyncAppender.NEW_RELIC_PREFIX;

public class NewRelicJsonLayout extends LayoutBase<ILoggingEvent> {
private final Integer maxStackSize;
private final boolean includeFullErrorStacktrace;

public NewRelicJsonLayout() {
this(getMaxStackSize());
this(getMaxStackSize(), getIncludeFullErrorStacktrace());
}

public NewRelicJsonLayout(Integer maxStackSize) {
public NewRelicJsonLayout(Integer maxStackSize, boolean includeFullErrorStacktrace) {
this.maxStackSize = maxStackSize;
this.includeFullErrorStacktrace = includeFullErrorStacktrace;
}

@Override
Expand Down Expand Up @@ -96,14 +100,18 @@ private void writeToGenerator(ILoggingEvent event, JsonGenerator generator) thro
generator.writeObjectField(ElementName.ERROR_CLASS, proxy.getClassName());
generator.writeObjectField(ElementName.ERROR_MESSAGE, proxy.getMessage());

StackTraceElementProxy[] stackProxy = proxy.getStackTraceElementProxyArray();
if (stackProxy != null && stackProxy.length > 0) {
List<StackTraceElement> elements = new ArrayList<>(maxStackSize);
for (int i = 0; i < maxStackSize && i < stackProxy.length; i++) {
elements.add(stackProxy[i].getStackTraceElement());
if (includeFullErrorStacktrace){
generator.writeObjectField(ElementName.ERROR_STACK, ThrowableProxyUtil.asString(proxy));
} else {
StackTraceElementProxy[] stackProxy = proxy.getStackTraceElementProxyArray();
if (stackProxy != null && stackProxy.length > 0) {
List<StackTraceElement> elements = new ArrayList<>(maxStackSize);
for (int i = 0; i < maxStackSize && i < stackProxy.length; i++) {
elements.add(stackProxy[i].getStackTraceElement());
}

generator.writeObjectField(ElementName.ERROR_STACK, ExceptionUtil.getErrorStack(elements.toArray(new StackTraceElement[0]), maxStackSize));
}

generator.writeObjectField(ElementName.ERROR_STACK, ExceptionUtil.getErrorStack(elements.toArray(new StackTraceElement[0]), maxStackSize));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,17 @@ void shouldAppendErrorDataCorrectly() throws Throwable {
thenTheExceptionDataIsInTheMessage();
}

@Test
@Timeout(3)
void shouldAppendFullErrorDataCorrectly() throws Throwable {
givenMockAgentData();
givenARedirectedAppender();
givenALoggingEventWithExceptionDataIncludingCausedBy();
whenTheEventIsAppended();
thenJsonLayoutWasUsed();
thenTheExceptionCausedByDataIsInTheMessage();
}

@Test
@Timeout(3)
void shouldAppendCustomArgsToJsonCorrectly() throws Throwable {
Expand Down Expand Up @@ -143,6 +154,12 @@ private void givenALoggingEventWithExceptionData() {
event.setThrowableProxy(new ThrowableProxy(new Exception("~~ oops ~~")));
}

private void givenALoggingEventWithExceptionDataIncludingCausedBy() {
System.setProperty("newrelic.log_extension.include_full_error_stack", "true");
givenALoggingEvent();
event.setThrowableProxy(new ThrowableProxy(new Exception("~~ oops ~~", new Exception("~~ oops inner 1 ~~", new Exception("~~ oops inner 2 ~~")))));
}

private void givenALoggingEventWithCallerData() {
givenALoggingEvent();
event.setCallerData(new StackTraceElement[] { new Exception().getStackTrace()[0] });
Expand Down Expand Up @@ -240,6 +257,16 @@ private void thenTheExceptionDataIsInTheMessage() throws Throwable {
);
}

private void thenTheExceptionCausedByDataIsInTheMessage() throws Throwable {
LogAsserts.assertFieldValues(
getOutput(),
ImmutableMap.of(
"error.stack.causedby", Pattern.compile(".*Caused By.*", Pattern.DOTALL),
"error.stack.inner1", Pattern.compile(".*oops inner 1.*", Pattern.DOTALL),
"error.stack.inner2", Pattern.compile(".*oops inner 2.*", Pattern.DOTALL)
));
}

private void thenTheCustomArgsAreInTheMessage() throws Throwable {
LogAsserts.assertFieldValues(
getOutput(),
Expand Down