-
Notifications
You must be signed in to change notification settings - Fork 149
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
Log4j2 JsonLayout support
- Loading branch information
Showing
26 changed files
with
1,380 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
jar { | ||
manifest { | ||
attributes 'Implementation-Title': 'com.newrelic.instrumentation.apache-log4j-2.11', | ||
// The module was renamed to its current name from the name below. The alias exists so the instrumentation | ||
// is still excluded/included for customers who have the old name in their configuration. | ||
'Implementation-Title-Alias': 'com.newrelic.instrumentation.apache-log4j-2' | ||
} | ||
} | ||
|
||
dependencies { | ||
implementation(project(":agent-bridge")) | ||
implementation("org.apache.logging.log4j:log4j-core:2.20.0") | ||
} | ||
|
||
verifyInstrumentation { | ||
passesOnly("org.apache.logging.log4j:log4j-core:[2.11.0,)") | ||
excludeRegex '.*(alpha|beta|rc).*' | ||
} | ||
|
||
site { | ||
title 'Log4j2' | ||
type 'Framework' | ||
} |
155 changes: 155 additions & 0 deletions
155
...tation/apache-log4j-2.11/src/main/java/com/nr/agent/instrumentation/log4j2/AgentUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/* | ||
* | ||
* * Copyright 2022 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package com.nr.agent.instrumentation.log4j2; | ||
|
||
import com.newrelic.agent.bridge.AgentBridge; | ||
import com.newrelic.agent.bridge.logging.AppLoggingUtils; | ||
import com.newrelic.agent.bridge.logging.LogAttributeKey; | ||
import com.newrelic.agent.bridge.logging.LogAttributeType; | ||
import org.apache.logging.log4j.Level; | ||
import org.apache.logging.log4j.core.LogEvent; | ||
import org.apache.logging.log4j.message.Message; | ||
import org.apache.logging.log4j.util.ReadOnlyStringMap; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_CLASS; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_MESSAGE; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.ERROR_STACK; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.INSTRUMENTATION; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LEVEL; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LOGGER_FQCN; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.LOGGER_NAME; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.MESSAGE; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.THREAD_ID; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.THREAD_NAME; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.TIMESTAMP; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.UNKNOWN; | ||
|
||
public class AgentUtil { | ||
/** | ||
* Record a LogEvent to be sent to New Relic. | ||
* | ||
* @param event to parse | ||
*/ | ||
public static void recordNewRelicLogEvent(LogEvent event) { | ||
if (event != null) { | ||
Message message = event.getMessage(); | ||
Throwable throwable = event.getThrown(); | ||
|
||
if (shouldCreateLogEvent(message, throwable)) { | ||
ReadOnlyStringMap contextData = event.getContextData(); | ||
Map<LogAttributeKey, Object> logEventMap = new HashMap<>(calculateInitialMapSize(contextData)); | ||
logEventMap.put(INSTRUMENTATION, "apache-log4j-2.11"); | ||
if (message != null) { | ||
String formattedMessage = message.getFormattedMessage(); | ||
if (formattedMessage != null && !formattedMessage.isEmpty()) { | ||
logEventMap.put(MESSAGE, formattedMessage); | ||
} | ||
} | ||
logEventMap.put(TIMESTAMP, event.getTimeMillis()); | ||
|
||
if (AppLoggingUtils.isAppLoggingContextDataEnabled() && contextData != null) { | ||
for (Map.Entry<String, String> entry : contextData.toMap().entrySet()) { | ||
String key = entry.getKey(); | ||
String value = entry.getValue(); | ||
LogAttributeKey logAttrKey = new LogAttributeKey(key, LogAttributeType.CONTEXT); | ||
logEventMap.put(logAttrKey, value); | ||
} | ||
} | ||
|
||
Level level = event.getLevel(); | ||
if (level != null) { | ||
String levelName = level.name(); | ||
if (levelName.isEmpty()) { | ||
logEventMap.put(LEVEL, UNKNOWN); | ||
} else { | ||
logEventMap.put(LEVEL, levelName); | ||
} | ||
} | ||
|
||
String errorStack = ExceptionUtil.getErrorStack(throwable); | ||
if (errorStack != null) { | ||
logEventMap.put(ERROR_STACK, errorStack); | ||
} | ||
|
||
String errorMessage = ExceptionUtil.getErrorMessage(throwable); | ||
if (errorMessage != null) { | ||
logEventMap.put(ERROR_MESSAGE, errorMessage); | ||
} | ||
|
||
String errorClass = ExceptionUtil.getErrorClass(throwable); | ||
if (errorClass != null) { | ||
logEventMap.put(ERROR_CLASS, errorClass); | ||
} | ||
|
||
String threadName = event.getThreadName(); | ||
if (threadName != null) { | ||
logEventMap.put(THREAD_NAME, threadName); | ||
} | ||
|
||
logEventMap.put(THREAD_ID, event.getThreadId()); | ||
|
||
String loggerName = event.getLoggerName(); | ||
if (loggerName != null) { | ||
logEventMap.put(LOGGER_NAME, loggerName); | ||
} | ||
|
||
String loggerFqcn = event.getLoggerFqcn(); | ||
if (loggerFqcn != null) { | ||
logEventMap.put(LOGGER_FQCN, loggerFqcn); | ||
} | ||
|
||
AgentBridge.getAgent().getLogSender().recordLogEvent(logEventMap); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* A LogEvent MUST NOT be reported if neither a log message nor an error is logged. If either is present report the LogEvent. | ||
* | ||
* @param message Message to validate | ||
* @param throwable Throwable to validate | ||
* @return true if a LogEvent should be created, otherwise false | ||
*/ | ||
private static boolean shouldCreateLogEvent(Message message, Throwable throwable) { | ||
return (message != null) || !ExceptionUtil.isThrowableNull(throwable); | ||
} | ||
|
||
private static int calculateInitialMapSize(ReadOnlyStringMap mdcPropertyMap) { | ||
return AppLoggingUtils.isAppLoggingContextDataEnabled() && mdcPropertyMap != null | ||
? mdcPropertyMap.size() + DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES | ||
: DEFAULT_NUM_OF_LOG_EVENT_ATTRIBUTES; | ||
} | ||
|
||
/** | ||
* Checks pretty or compact JSON layout strings for a series of characters and returns the index of | ||
* the characters or -1 if they were not found. This is used to find the log "message" substring | ||
* so that the NR-LINKING metadata blob can be inserted when using local decorating with JsonLayout. | ||
* | ||
* @param writerString String representing JSON formatted log event | ||
* @return positive int if index was found, else -1 | ||
*/ | ||
public static int getIndexToModifyJson(String writerString) { | ||
return writerString.indexOf("\",", writerString.indexOf("message")); | ||
} | ||
|
||
/** | ||
* Check if a valid match was found when calling String.indexOf. | ||
* If index value is -1 then no valid match was found, a positive integer represents a valid index. | ||
* | ||
* @param indexToModifyJson int representing index returned by indexOf | ||
* @return true if a valid index was found, else false | ||
*/ | ||
public static boolean foundIndexToInsertLinkingMetadata(int indexToModifyJson) { | ||
return indexToModifyJson != -1; | ||
} | ||
|
||
} |
File renamed without changes.
80 changes: 80 additions & 0 deletions
80
...ache-log4j-2.11/src/main/java/org/apache/logging/log4j/core/LogEvent_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* | ||
* * Copyright 2023 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package org.apache.logging.log4j.core; | ||
|
||
import com.newrelic.api.agent.NewRelic; | ||
import com.newrelic.api.agent.weaver.MatchType; | ||
import com.newrelic.api.agent.weaver.NewField; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import org.apache.logging.log4j.Level; | ||
import org.apache.logging.log4j.Marker; | ||
import org.apache.logging.log4j.ThreadContext; | ||
import org.apache.logging.log4j.core.impl.ThrowableProxy; | ||
import org.apache.logging.log4j.core.time.Instant; | ||
import org.apache.logging.log4j.message.Message; | ||
import org.apache.logging.log4j.util.ReadOnlyStringMap; | ||
|
||
import java.util.Map; | ||
|
||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingLocalDecoratingEnabled; | ||
|
||
@Weave(originalName = "org.apache.logging.log4j.core.LogEvent", type = MatchType.Interface) | ||
public abstract class LogEvent_Instrumentation { | ||
|
||
/* | ||
* In cases where the LogEvent is sent to an AsyncAppender, getLinkingMetadata would get called on a new thread and the trace.id and span.id | ||
* would be missing. To work around this we save the linking metadata on the LogEvent on the thread where it was created and use it later. | ||
*/ | ||
@NewField | ||
public Map<String, String> agentLinkingMetadata = isApplicationLoggingLocalDecoratingEnabled() ? NewRelic.getAgent().getLinkingMetadata() : null; | ||
|
||
public abstract LogEvent toImmutable(); | ||
|
||
@Deprecated | ||
public abstract Map<String, String> getContextMap(); | ||
|
||
public abstract ReadOnlyStringMap getContextData(); | ||
|
||
public abstract ThreadContext.ContextStack getContextStack(); | ||
|
||
public abstract String getLoggerFqcn(); | ||
|
||
public abstract Level getLevel(); | ||
|
||
public abstract String getLoggerName(); | ||
|
||
public abstract Marker getMarker(); | ||
|
||
public abstract Message getMessage(); | ||
|
||
public abstract long getTimeMillis(); | ||
|
||
public abstract Instant getInstant(); | ||
|
||
public abstract StackTraceElement getSource(); | ||
|
||
public abstract String getThreadName(); | ||
|
||
public abstract long getThreadId(); | ||
|
||
public abstract int getThreadPriority(); | ||
|
||
public abstract Throwable getThrown(); | ||
|
||
public abstract ThrowableProxy getThrownProxy(); | ||
|
||
public abstract boolean isEndOfBatch(); | ||
|
||
public abstract boolean isIncludeLocation(); | ||
|
||
public abstract void setEndOfBatch(boolean endOfBatch); | ||
|
||
public abstract void setIncludeLocation(boolean locationRequired); | ||
|
||
public abstract long getNanoTime(); | ||
} |
66 changes: 66 additions & 0 deletions
66
...2.11/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig_Instrumentation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* | ||
* * Copyright 2022 New Relic Corporation. All rights reserved. | ||
* * SPDX-License-Identifier: Apache-2.0 | ||
* | ||
*/ | ||
|
||
package org.apache.logging.log4j.core.config; | ||
|
||
import com.newrelic.api.agent.NewRelic; | ||
import com.newrelic.api.agent.weaver.MatchType; | ||
import com.newrelic.api.agent.weaver.NewField; | ||
import com.newrelic.api.agent.weaver.Weave; | ||
import com.newrelic.api.agent.weaver.WeaveAllConstructors; | ||
import com.newrelic.api.agent.weaver.Weaver; | ||
import org.apache.logging.log4j.core.LogEvent; | ||
|
||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingEnabled; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingForwardingEnabled; | ||
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingMetricsEnabled; | ||
import static com.nr.agent.instrumentation.log4j2.AgentUtil.recordNewRelicLogEvent; | ||
|
||
@Weave(originalName = "org.apache.logging.log4j.core.config.LoggerConfig", type = MatchType.ExactClass) | ||
public class LoggerConfig_Instrumentation { | ||
@NewField | ||
public static AtomicBoolean instrumented = new AtomicBoolean(false); | ||
|
||
@WeaveAllConstructors | ||
public LoggerConfig_Instrumentation() { | ||
// Generate the instrumentation module supportability metric only once | ||
if (!instrumented.getAndSet(true)) { | ||
NewRelic.incrementCounter("Supportability/Logging/Java/Log4j2.11/enabled"); | ||
} | ||
} | ||
|
||
protected void callAppenders(LogEvent event) { | ||
// Do nothing if application_logging.enabled: false | ||
if (isApplicationLoggingEnabled()) { | ||
// Do nothing if logger has parents and isAdditive is set to true to avoid duplicated counters and logs | ||
if (getParent() == null || !isAdditive()) { | ||
if (isApplicationLoggingMetricsEnabled()) { | ||
// Generate log level metrics | ||
NewRelic.incrementCounter("Logging/lines"); | ||
NewRelic.incrementCounter("Logging/lines/" + event.getLevel().toString()); | ||
} | ||
|
||
if (isApplicationLoggingForwardingEnabled()) { | ||
// Record and send LogEvent to New Relic | ||
recordNewRelicLogEvent(event); | ||
} | ||
} | ||
} | ||
Weaver.callOriginal(); | ||
} | ||
|
||
public LoggerConfig getParent() { | ||
return Weaver.callOriginal(); | ||
} | ||
|
||
public boolean isAdditive() { | ||
return Weaver.callOriginal(); | ||
} | ||
|
||
} |
Oops, something went wrong.