Skip to content

Commit 97e7777

Browse files
authored
Merge pull request #2535 from newrelic/logback-1.5.20
logback-1.5.20 module
2 parents 13f3717 + c6b8f68 commit 97e7777

File tree

12 files changed

+973
-0
lines changed

12 files changed

+973
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
jar {
2+
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.logback-classic-1.5.20' }
3+
}
4+
5+
dependencies {
6+
implementation(project(":agent-bridge"))
7+
implementation("ch.qos.logback:logback-classic:1.5.20")
8+
}
9+
10+
verifyInstrumentation {
11+
passesOnly("ch.qos.logback:logback-classic:[1.5.20,)")
12+
excludeRegex '.*(alpha|groovyless).*'
13+
}
14+
15+
java {
16+
toolchain {
17+
languageVersion.set(JavaLanguageVersion.of(11))
18+
}
19+
}
20+
21+
test {
22+
// These instrumentation tests only run on Java 11+ regardless of the -PtestN gradle property that is set.
23+
onlyIf {
24+
!project.hasProperty('test8')
25+
}
26+
}
27+
28+
site {
29+
title 'Logback'
30+
type 'Framework'
31+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
*
3+
* * Copyright 2022 New Relic Corporation. All rights reserved.
4+
* * SPDX-License-Identifier: Apache-2.0
5+
*
6+
*/
7+
8+
package ch.qos.logback.classic;
9+
10+
import com.newrelic.agent.bridge.logging.AppLoggingUtils;
11+
import com.newrelic.api.agent.NewRelic;
12+
import com.newrelic.api.agent.weaver.MatchType;
13+
import com.newrelic.api.agent.weaver.NewField;
14+
import com.newrelic.api.agent.weaver.Weave;
15+
import com.newrelic.api.agent.weaver.WeaveAllConstructors;
16+
import com.newrelic.api.agent.weaver.Weaver;
17+
import org.slf4j.Marker;
18+
19+
import java.util.concurrent.atomic.AtomicBoolean;
20+
21+
@Weave(originalName = "ch.qos.logback.classic.Logger", type = MatchType.ExactClass)
22+
public abstract class Logger_Instrumentation {
23+
@NewField
24+
public static AtomicBoolean instrumented = new AtomicBoolean(false);
25+
26+
@WeaveAllConstructors
27+
Logger_Instrumentation() {
28+
// Generate the instrumentation module supportability metric only once
29+
if (!instrumented.getAndSet(true)) {
30+
NewRelic.incrementCounter("Supportability/Logging/Java/LogbackClassic1.2/enabled");
31+
}
32+
}
33+
34+
private void buildLoggingEventAndAppend(final String localFQCN, final Marker marker, final Level level, final String msg, final Object[] params,
35+
final Throwable t) {
36+
// Do nothing if application_logging.enabled: false
37+
if (AppLoggingUtils.isApplicationLoggingEnabled()) {
38+
if (AppLoggingUtils.isApplicationLoggingMetricsEnabled()) {
39+
// Generate log level metrics
40+
NewRelic.incrementCounter("Logging/lines");
41+
NewRelic.incrementCounter("Logging/lines/" + level);
42+
}
43+
}
44+
Weaver.callOriginal();
45+
}
46+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
*
3+
* * Copyright 2022 New Relic Corporation. All rights reserved.
4+
* * SPDX-License-Identifier: Apache-2.0
5+
*
6+
*/
7+
8+
package ch.qos.logback.classic.spi;
9+
10+
import ch.qos.logback.classic.Level;
11+
import ch.qos.logback.classic.Logger;
12+
import ch.qos.logback.classic.LoggerContext;
13+
import com.newrelic.api.agent.weaver.MatchType;
14+
import com.newrelic.api.agent.weaver.Weave;
15+
import com.newrelic.api.agent.weaver.Weaver;
16+
17+
import java.util.Collections;
18+
import java.util.Map;
19+
20+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.getLinkingMetadataBlob;
21+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isAppLoggingContextDataEnabled;
22+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingEnabled;
23+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingForwardingEnabled;
24+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingLocalDecoratingEnabled;
25+
import static com.nr.agent.instrumentation.logbackclassic1520.AgentUtil.recordNewRelicLogEvent;
26+
27+
@Weave(originalName = "ch.qos.logback.classic.spi.LoggingEvent", type = MatchType.ExactClass)
28+
public class LoggingEvent_Instrumentation {
29+
30+
transient String fqnOfLoggerClass;
31+
private String loggerName;
32+
private LoggerContext loggerContext;
33+
private LoggerContextVO loggerContextVO;
34+
private transient Level level;
35+
private String message;
36+
private transient Object[] argumentArray;
37+
private ThrowableProxy throwableProxy;
38+
private long timeStamp;
39+
40+
public LoggingEvent_Instrumentation(String fqcn, Logger logger, Level level, String message, Throwable throwable, Object[] argArray) {
41+
// Do nothing if application_logging.enabled: false
42+
this.fqnOfLoggerClass = fqcn;
43+
this.loggerName = logger.getName();
44+
this.loggerContext = logger.getLoggerContext();
45+
this.loggerContextVO = loggerContext.getLoggerContextRemoteView();
46+
this.level = level;
47+
48+
boolean applicationLoggingEnabled = isApplicationLoggingEnabled();
49+
if (applicationLoggingEnabled && isApplicationLoggingLocalDecoratingEnabled()) {
50+
// Append New Relic linking metadata from agent to log message
51+
this.message = message + getLinkingMetadataBlob();
52+
} else {
53+
this.message = message;
54+
}
55+
56+
this.argumentArray = argArray;
57+
58+
if (throwable == null) {
59+
throwable = extractThrowableAndRearrangeArguments(argArray);
60+
}
61+
62+
if (throwable != null) {
63+
this.throwableProxy = new ThrowableProxy(throwable);
64+
LoggerContext lc = logger.getLoggerContext();
65+
if (lc.isPackagingDataEnabled()) {
66+
this.throwableProxy.calculatePackagingData();
67+
}
68+
}
69+
70+
timeStamp = System.currentTimeMillis();
71+
72+
Thread thread = Thread.currentThread();
73+
String threadName = thread.getName();
74+
long threadId = thread.getId();
75+
76+
if (applicationLoggingEnabled && isApplicationLoggingForwardingEnabled()) {
77+
Map<String, String> mdc;
78+
if (isAppLoggingContextDataEnabled()) {
79+
mdc = getMdc();
80+
} else {
81+
mdc = Collections.emptyMap();
82+
}
83+
// Record and send LogEvent to New Relic
84+
recordNewRelicLogEvent(getFormattedMessage(), mdc, timeStamp, level, throwable, threadName, threadId, loggerName, fqnOfLoggerClass);
85+
}
86+
}
87+
88+
public String getFormattedMessage() {
89+
return Weaver.callOriginal();
90+
}
91+
92+
private Throwable extractThrowableAndRearrangeArguments(Object[] argArray) {
93+
return Weaver.callOriginal();
94+
}
95+
96+
public Map<String, String> getMdc() {
97+
return Weaver.callOriginal();
98+
}
99+
100+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
*
3+
* * Copyright 2022 New Relic Corporation. All rights reserved.
4+
* * SPDX-License-Identifier: Apache-2.0
5+
*
6+
*/
7+
8+
package com.nr.agent.instrumentation.logbackclassic1520;
9+
10+
import ch.qos.logback.classic.Level;
11+
import com.newrelic.agent.bridge.AgentBridge;
12+
import com.newrelic.agent.bridge.logging.AppLoggingUtils;
13+
import com.newrelic.agent.bridge.logging.LogAttributeKey;
14+
import com.newrelic.agent.bridge.logging.LogAttributeType;
15+
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
19+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES;
20+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_CLASS;
21+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_MESSAGE;
22+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_STACK;
23+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.INSTRUMENTATION;
24+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LEVEL;
25+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LOGGER_FQCN;
26+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LOGGER_NAME;
27+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.MESSAGE;
28+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.THREAD_ID;
29+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.THREAD_NAME;
30+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.TIMESTAMP;
31+
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.UNKNOWN;
32+
33+
public class AgentUtil {
34+
/**
35+
* Record a LogEvent to be sent to New Relic.
36+
*
37+
* @param message log message
38+
* @param timeStampMillis log timestamp
39+
* @param level log level
40+
*/
41+
public static void recordNewRelicLogEvent(String message, Map<String, String> mdcPropertyMap, long timeStampMillis, Level level, Throwable throwable, String threadName, long threadId,
42+
String loggerName, String fqcnLoggerName) {
43+
boolean messageEmpty = message.isEmpty();
44+
45+
if (shouldCreateLogEvent(messageEmpty, throwable)) {
46+
Map<LogAttributeKey, Object> logEventMap = new HashMap<>(calculateInitialMapSize(mdcPropertyMap));
47+
logEventMap.put(INSTRUMENTATION, "logback-classic-1.2");
48+
if (!messageEmpty) {
49+
logEventMap.put(MESSAGE, message);
50+
}
51+
logEventMap.put(TIMESTAMP, timeStampMillis);
52+
53+
if (AppLoggingUtils.isAppLoggingContextDataEnabled()) {
54+
for (Map.Entry<String, String> mdcEntry : mdcPropertyMap.entrySet()) {
55+
LogAttributeKey logAttrKey = new LogAttributeKey(mdcEntry.getKey(), LogAttributeType.CONTEXT);
56+
logEventMap.put(logAttrKey, mdcEntry.getValue());
57+
}
58+
}
59+
60+
if (level.toString().isEmpty()) {
61+
logEventMap.put(LEVEL, UNKNOWN);
62+
} else {
63+
logEventMap.put(LEVEL, level);
64+
}
65+
66+
String errorStack = ExceptionUtil.getErrorStack(throwable);
67+
if (errorStack != null) {
68+
logEventMap.put(ERROR_STACK, errorStack);
69+
}
70+
71+
String errorMessage = ExceptionUtil.getErrorMessage(throwable);
72+
if (errorMessage != null) {
73+
logEventMap.put(ERROR_MESSAGE, errorMessage);
74+
}
75+
76+
String errorClass = ExceptionUtil.getErrorClass(throwable);
77+
if (errorClass != null) {
78+
logEventMap.put(ERROR_CLASS, errorClass);
79+
}
80+
81+
if (threadName != null) {
82+
logEventMap.put(THREAD_NAME, threadName);
83+
}
84+
85+
logEventMap.put(THREAD_ID, threadId);
86+
87+
if (loggerName != null) {
88+
logEventMap.put(LOGGER_NAME, loggerName);
89+
}
90+
91+
if (fqcnLoggerName != null) {
92+
logEventMap.put(LOGGER_FQCN, fqcnLoggerName);
93+
}
94+
95+
AgentBridge.getAgent().getLogSender().recordLogEvent(logEventMap);
96+
}
97+
}
98+
99+
/**
100+
* A LogEvent MUST NOT be reported if neither a log message nor an error is logged. If either is present report the LogEvent.
101+
*
102+
* @param messageEmpty Message to validate
103+
* @param throwable Throwable to validate
104+
* @return true if a LogEvent should be created, otherwise false
105+
*/
106+
private static boolean shouldCreateLogEvent(boolean messageEmpty, Throwable throwable) {
107+
return !messageEmpty || !ExceptionUtil.isThrowableNull(throwable);
108+
}
109+
110+
private static int calculateInitialMapSize(Map<String, String> mdcPropertyMap) {
111+
return AppLoggingUtils.isAppLoggingContextDataEnabled()
112+
? mdcPropertyMap.size() + DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES
113+
: DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES;
114+
}
115+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.nr.agent.instrumentation.logbackclassic1520;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.Collections;
6+
import java.util.List;
7+
8+
public class ExceptionUtil {
9+
public static final int MAX_STACK_SIZE = 300;
10+
11+
public static boolean isThrowableNull(Throwable throwable) {
12+
return throwable == null;
13+
}
14+
15+
public static String getErrorStack(Throwable throwable) {
16+
if (throwable == null) {
17+
return null;
18+
}
19+
20+
Throwable t = throwable;
21+
List<String> lines = new ArrayList<>();
22+
boolean inner = false;
23+
while (t != null && lines.size() < MAX_STACK_SIZE) {
24+
if (inner) {
25+
lines.add(" caused by: " + t.getClass().getName() + ": " + t.getMessage());
26+
}
27+
lines.addAll(stackTracesToStrings(t.getStackTrace()));
28+
t = t.equals(t.getCause()) ? null : t.getCause();
29+
inner = true;
30+
}
31+
32+
return String.join("\n", lines.subList(0, Math.min(lines.size(), MAX_STACK_SIZE)));
33+
}
34+
35+
public static String getErrorMessage(Throwable throwable) {
36+
if (throwable == null) {
37+
return null;
38+
}
39+
return throwable.getMessage();
40+
}
41+
42+
public static String getErrorClass(Throwable throwable) {
43+
if (throwable == null) {
44+
return null;
45+
}
46+
return throwable.getClass().getName();
47+
}
48+
49+
private static Collection<String> stackTracesToStrings(StackTraceElement[] stackTraces) {
50+
if (stackTraces == null || stackTraces.length == 0) {
51+
return Collections.emptyList();
52+
}
53+
List<String> lines = new ArrayList<>(stackTraces.length);
54+
for (StackTraceElement e : stackTraces) {
55+
lines.add(" at " + e.toString());
56+
}
57+
58+
return lines;
59+
}
60+
}

0 commit comments

Comments
 (0)