Skip to content

Commit ca73bcd

Browse files
authored
Merge pull request #75 from newrelic/NEWRELIC-3252-mdc-support
Newrelic-3252 Add configurable mdc and stack trace size support
2 parents 0d26aa0 + 1c8d8ef commit ca73bcd

File tree

46 files changed

+2340
-117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2340
-117
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## 3.0.0
8+
* Exception Stack Trace Size - this has been changed from a default of `10` to `300`. This applies to all logging libraries supported by the `java-log-extension` project.
9+
* This is configurable via:
10+
* System property: `-Dnewrelic.log_extension.max_stack_size=integer`
11+
* Environment Variable: `NEW_RELIC_LOG_EXTENSION_MAX_STACK_SIZE=integer`
12+
* Mapped Diagnostic Context (MDC) - decorating logs with MDC data is now generally supported but disabled by default. MDC keys will be prefixed by `context.` to prevent clashes with New Relic specific attributes. This applies to all logging libraries supported by the `java-log-extension` project, except for Java Util Logging (JUL) which does not provide an MDC mechanism.
13+
* This is configurable via:
14+
* System property: `-Dnewrelic.log_extension.add_mdc=boolean`
15+
* Environment Variable: `NEW_RELIC_LOG_EXTENSION_ADD_MDC=boolean`
16+
* Note: This is considered a breaking change as previously some of the logging libraries automatically added MDC. If you upgrade to this version of the `java-log-extension` and wish to have MDC added to your logs then you will need to explicitly enable it. Currently, this will add all MDC as filtering out specific keys is not yet supported.
17+
718
## 2.6.0
819
* Removed the log forwarder. Please use the [New Relic Java Agent](https://github.com/newrelic/newrelic-java-agent) if you want log forwarding to be used.
920
* Upgrade Gradle to version 7.5.1

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ We support:
1818
* [Logback 1.1](logback11/README.md)
1919
* [Dropwizard 1.3](dropwizard/README.md)
2020

21+
## Configuration Options
22+
The following config options apply to all supported logging frameworks, with some noted exceptions.
23+
24+
### Mapped Diagnostic Context (MDC)
25+
Not all logging frameworks provide an MDC mechanism (e.g. JUL) but for those that do you can configure
26+
the logging extension to add the MDC data in addition to the trace context data from New Relic.
27+
28+
Default for adding MDC is `false`, change this to `true` if you wish to include MDC data on log events. It can be configured by
29+
environment variable (`NEW_RELIC_LOG_EXTENSION_ADD_MDC=boolean`) or system property (`-Dnewrelic.log_extension.add_mdc=boolean`).
30+
31+
Note that, if set to `true` all MDC data will be added to log events. Currently, there is no filtering capability for excluding specific MDC entries.
32+
33+
MDC data will be added to log events with a `context.` prefix to distinguish it as user defined context data as well as to prevent potential
34+
collisions with New Relic specific context keys.
35+
36+
### Exception Stack Trace Size
37+
You can configure the logging extension to control the max stack trace size for exceptions added to log events.
38+
39+
Default max stack trace size is `300`. It is recommended that you do not exceed this value or data could be dropped or truncated as well as
40+
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`)
41+
or system property (`-Dnewrelic.log_extension.max_stack_size=integer`).
42+
2143
## Support
2244

2345
Should you need assistance with New Relic products, you are in good hands with several diagnostic tools and support channels.

core-test/src/main/java/com/newrelic/logging/core/LogAsserts.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import static com.newrelic.logging.core.ElementName.TIMESTAMP;
1616
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
1718
import static org.junit.jupiter.api.Assertions.assertTrue;
1819
import static org.junit.jupiter.api.Assertions.fail;
1920

@@ -33,6 +34,37 @@ public static void assertFieldValues(String result, Map<String, Object> expected
3334
}
3435
}
3536

37+
/**
38+
* Assert whether a specified field (aka attribute key) exists, or not, in the resulting log attributes that are recorded.
39+
*
40+
* @param field String representing a field to check the existence of
41+
* @param result String representing the actual results to assert against
42+
* @param shouldExist boolean true if field should exist in the result, else false
43+
* @throws IOException sometimes
44+
*/
45+
public static boolean assertFieldExistence(String field, String result, boolean shouldExist) throws IOException {
46+
assertEquals('\n', result.charAt(result.length() - 1));
47+
boolean fieldExists = false;
48+
49+
try (JsonParser parser = new JsonFactory().createParser(result)) {
50+
assertEquals(parser.nextToken(), JsonToken.START_OBJECT);
51+
52+
JsonToken token;
53+
while ((token = parser.nextToken()) != JsonToken.END_OBJECT) {
54+
if (token.equals(JsonToken.FIELD_NAME)) {
55+
String elementName = parser.getValueAsString();
56+
if (shouldExist && elementName.contains(field)) {
57+
assertEquals(field, elementName);
58+
fieldExists = true;
59+
} else {
60+
assertNotEquals(field, elementName);
61+
}
62+
}
63+
}
64+
}
65+
return fieldExists;
66+
}
67+
3668
private static void assertValueIfPresent(String elementName, JsonParser parser, Map<String, Object> expectedValues) throws IOException {
3769
JsonToken valueToken = parser.nextToken();
3870
if (elementName.equals(TIMESTAMP)) {
@@ -57,7 +89,7 @@ private static void assertValueIfPresent(String elementName, JsonParser parser,
5789
assertEquals(parser.getValueAsString(), expectedValue);
5890
break;
5991
case "java.util.regex.Pattern":
60-
Pattern pattern = (Pattern)expectedValue;
92+
Pattern pattern = (Pattern) expectedValue;
6193
assertEquals(valueToken, JsonToken.VALUE_STRING);
6294
assertTrue(
6395
pattern.matcher(parser.getValueAsString()).matches(),

core/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ configure<JavaPluginConvention> {
1313

1414
dependencies {
1515
testImplementation("org.junit.jupiter:junit-jupiter:5.6.2")
16-
}
16+
// Allows for easy testing of values based on Environment Variables and System Properties
17+
testImplementation("com.github.stefanbirkner:system-lambda:1.2.1")
18+
}

core/src/main/java/com/newrelic/logging/core/ExceptionUtil.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
package com.newrelic.logging.core;
77

8+
import static com.newrelic.logging.core.LogExtensionConfig.getMaxStackSize;
9+
810
public class ExceptionUtil {
9-
public static final int MAX_STACK_SIZE = 10;
11+
public static final int MAX_STACK_SIZE_DEFAULT = 300;
1012
public static String getErrorStack(Throwable throwable) {
1113
if (throwable == null) {
1214
return null;
@@ -17,7 +19,7 @@ public static String getErrorStack(Throwable throwable) {
1719
}
1820

1921
public static String getErrorStack(StackTraceElement[] stack) {
20-
return getErrorStack(stack, MAX_STACK_SIZE);
22+
return getErrorStack(stack, getMaxStackSize());
2123
}
2224

2325
public static String getErrorStack(StackTraceElement[] stack, Integer maxStackSize) {
@@ -27,7 +29,7 @@ public static String getErrorStack(StackTraceElement[] stack, Integer maxStackSi
2729

2830
StringBuilder stackBuilder = new StringBuilder();
2931
for(int i = 0; i < Math.min(maxStackSize, stack.length); i++) {
30-
stackBuilder.append(" at " + stack[i].toString() + "\n");
32+
stackBuilder.append(" at ").append(stack[i].toString()).append("\n");
3133
}
3234
return stackBuilder.toString();
3335
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2023 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.newrelic.logging.core;
7+
8+
public class LogExtensionConfig {
9+
public static final String CONTEXT_PREFIX = "context.";
10+
private static final String CONFIG_PREFIX_ENV_VAR = "NEW_RELIC_LOG_EXTENSION_";
11+
private static final String CONFIG_PREFIX_SYS_PROP = "newrelic.log_extension.";
12+
public static final String MAX_STACK_SIZE_ENV_VAR = CONFIG_PREFIX_ENV_VAR + "MAX_STACK_SIZE";
13+
public static final String MAX_STACK_SIZE_SYS_PROP = CONFIG_PREFIX_SYS_PROP + "max_stack_size";
14+
public static final String ADD_MDC_ENV_VAR = CONFIG_PREFIX_ENV_VAR + "ADD_MDC";
15+
public static final String ADD_MDC_SYS_PROP = CONFIG_PREFIX_SYS_PROP + "add_mdc";
16+
public static final boolean ADD_MDC_DEFAULT = false;
17+
18+
/**
19+
* Get an int representing the max stack size for errors that should be added to logs
20+
* <p>
21+
* Precedence: Env var > Sys prop > Default
22+
*
23+
* @return int representing max stack size
24+
*/
25+
public static int getMaxStackSize() {
26+
String envVar = System.getenv(MAX_STACK_SIZE_ENV_VAR);
27+
String sysProp = System.getProperty(MAX_STACK_SIZE_SYS_PROP);
28+
29+
if (isInteger(envVar)) {
30+
return Integer.parseInt(envVar);
31+
} else if (isInteger(sysProp)) {
32+
return Integer.parseInt(sysProp);
33+
} else {
34+
return ExceptionUtil.MAX_STACK_SIZE_DEFAULT;
35+
}
36+
}
37+
38+
/**
39+
* Get a boolean indicating if MDC should be added to logs
40+
* <p>
41+
* Precedence: Env var > Sys prop > Default
42+
*
43+
* @return true if MDC should be added to logs, else false
44+
*/
45+
public static boolean shouldAddMDC() {
46+
String envVar = System.getenv(ADD_MDC_ENV_VAR);
47+
String sysProp = System.getProperty(ADD_MDC_SYS_PROP);
48+
49+
if (envVar != null) {
50+
return Boolean.parseBoolean(envVar);
51+
} else if (sysProp != null) {
52+
return Boolean.parseBoolean(sysProp);
53+
} else {
54+
return ADD_MDC_DEFAULT;
55+
}
56+
}
57+
58+
/**
59+
* Validate that a String value evaluates to an integer
60+
*
61+
* @param val String to be evaluated as an int
62+
* @return true if val is an int, else false
63+
*/
64+
static boolean isInteger(String val) {
65+
if (val == null) {
66+
return false;
67+
}
68+
try {
69+
Integer.parseInt(val);
70+
} catch (NumberFormatException nfe) {
71+
return false;
72+
}
73+
return true;
74+
}
75+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2023 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package com.newrelic.logging.core;
7+
8+
import org.junit.jupiter.api.Test;
9+
10+
import static com.github.stefanbirkner.systemlambda.SystemLambda.withEnvironmentVariable;
11+
import static com.newrelic.logging.core.ExceptionUtil.MAX_STACK_SIZE_DEFAULT;
12+
import static com.newrelic.logging.core.LogExtensionConfig.ADD_MDC_DEFAULT;
13+
import static com.newrelic.logging.core.LogExtensionConfig.ADD_MDC_ENV_VAR;
14+
import static com.newrelic.logging.core.LogExtensionConfig.ADD_MDC_SYS_PROP;
15+
import static com.newrelic.logging.core.LogExtensionConfig.MAX_STACK_SIZE_ENV_VAR;
16+
import static com.newrelic.logging.core.LogExtensionConfig.MAX_STACK_SIZE_SYS_PROP;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
20+
21+
class LogExtensionConfigTest {
22+
23+
@Test
24+
void testGetMaxStackSizeDefault() {
25+
int actualSize = LogExtensionConfig.getMaxStackSize();
26+
assertEquals(MAX_STACK_SIZE_DEFAULT, actualSize);
27+
}
28+
29+
@Test
30+
void testGetMaxStackSizeSysProp() {
31+
int expectedSize = 5;
32+
System.setProperty(MAX_STACK_SIZE_SYS_PROP, String.valueOf(expectedSize));
33+
int actualSize = LogExtensionConfig.getMaxStackSize();
34+
assertEquals(expectedSize, actualSize);
35+
36+
System.clearProperty(MAX_STACK_SIZE_SYS_PROP);
37+
}
38+
39+
@Test
40+
void testGetMaxStackSizeEnvVar() throws Exception {
41+
int expectedSize = 7;
42+
int actualSize = withEnvironmentVariable(MAX_STACK_SIZE_ENV_VAR, String.valueOf(expectedSize)).execute(LogExtensionConfig::getMaxStackSize);
43+
assertEquals(expectedSize, actualSize);
44+
}
45+
46+
@Test
47+
void testGetMaxStackSizeEnvVarAndSysProp() throws Exception {
48+
// Set via sys prop
49+
int sysPropSize = 90;
50+
System.setProperty(MAX_STACK_SIZE_SYS_PROP, String.valueOf(sysPropSize));
51+
52+
// Environment variable takes precedence over system property
53+
int expectedSize = 3;
54+
int actualSize = withEnvironmentVariable(MAX_STACK_SIZE_ENV_VAR, String.valueOf(expectedSize)).execute(LogExtensionConfig::getMaxStackSize);
55+
assertEquals(expectedSize, actualSize);
56+
57+
System.clearProperty(MAX_STACK_SIZE_SYS_PROP);
58+
}
59+
60+
@Test
61+
void testShouldAddMDCDefault() {
62+
boolean actualAddMDCValue = LogExtensionConfig.shouldAddMDC();
63+
assertEquals(ADD_MDC_DEFAULT, actualAddMDCValue);
64+
}
65+
66+
@Test
67+
void testShouldAddMDCSysProp() {
68+
boolean expectedAddMDCValue = true;
69+
System.setProperty(ADD_MDC_SYS_PROP, String.valueOf(expectedAddMDCValue));
70+
boolean actualAddMDCValue = LogExtensionConfig.shouldAddMDC();
71+
assertEquals(expectedAddMDCValue, actualAddMDCValue);
72+
73+
System.clearProperty(ADD_MDC_SYS_PROP);
74+
}
75+
76+
@Test
77+
void testShouldAddMDCEnvVar() throws Exception {
78+
boolean expectedAddMDCValue = true;
79+
boolean actualAddMDCValue = withEnvironmentVariable(ADD_MDC_ENV_VAR, String.valueOf(expectedAddMDCValue)).execute(LogExtensionConfig::shouldAddMDC);
80+
assertEquals(expectedAddMDCValue, actualAddMDCValue);
81+
}
82+
83+
@Test
84+
void testShouldAddMDCEnvVarAndSysProp() throws Exception {
85+
// Set via sys prop
86+
boolean sysPropAddMDCValue = false;
87+
System.setProperty(ADD_MDC_SYS_PROP, String.valueOf(sysPropAddMDCValue));
88+
89+
// Environment variable takes precedence over system property
90+
boolean expectedAddMDCValue = true;
91+
boolean actualAddMDCValue = withEnvironmentVariable(ADD_MDC_ENV_VAR, String.valueOf(expectedAddMDCValue)).execute(LogExtensionConfig::shouldAddMDC);
92+
assertEquals(expectedAddMDCValue, actualAddMDCValue);
93+
94+
System.clearProperty(ADD_MDC_SYS_PROP);
95+
}
96+
97+
@Test
98+
void isInteger() {
99+
assertFalse(LogExtensionConfig.isInteger("FOO"));
100+
assertFalse(LogExtensionConfig.isInteger("90.12"));
101+
assertTrue(LogExtensionConfig.isInteger("200"));
102+
}
103+
}

0 commit comments

Comments
 (0)