Skip to content

Commit

Permalink
Merge pull request #1472 from newrelic/NR-155391-json-layout-support
Browse files Browse the repository at this point in the history
Add local decorating support for adding linking metadata when using Log4j2 JsonLayout
  • Loading branch information
jasonjkeller authored Oct 9, 2023
2 parents 5f6995f + 97237cd commit 5a371d5
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
*/
package com.newrelic.agent.bridge.logging;

import com.newrelic.agent.bridge.logging.LogAttributeKey;
import com.newrelic.agent.bridge.logging.LogAttributeType;
import com.newrelic.api.agent.NewRelic;

import java.io.UnsupportedEncodingException;
Expand Down Expand Up @@ -54,11 +52,32 @@ public class AppLoggingUtils {
* @return agent linking metadata string blob
*/
public static String getLinkingMetadataBlob() {
Map<String, String> agentLinkingMetadata = NewRelic.getAgent().getLinkingMetadata();
return constructLinkingMetadataBlob(NewRelic.getAgent().getLinkingMetadata());
}

/**
* Gets a String representing the agent linking metadata in blob format:
* NR-LINKING|entity.guid|hostname|trace.id|span.id|entity.name|
*
* @param agentLinkingMetadata map of linking metadata
* @return agent linking metadata string blob
*/
public static String getLinkingMetadataBlobFromMap(Map<String, String> agentLinkingMetadata) {
return constructLinkingMetadataBlob(agentLinkingMetadata);
}

/**
* Constructs a String representing the agent linking metadata in blob format:
* NR-LINKING|entity.guid|hostname|trace.id|span.id|entity.name|
*
* @param agentLinkingMetadata map of linking metadata
* @return agent linking metadata string blob
*/
private static String constructLinkingMetadataBlob(Map<String, String> agentLinkingMetadata) {
StringBuilder blob = new StringBuilder();
blob.append(" ").append(BLOB_PREFIX).append(BLOB_DELIMITER);

if (agentLinkingMetadata != null && agentLinkingMetadata.size() > 0) {
if (agentLinkingMetadata != null && !agentLinkingMetadata.isEmpty()) {
appendAttributeToBlob(agentLinkingMetadata.get(ENTITY_GUID), blob);
appendAttributeToBlob(agentLinkingMetadata.get(HOSTNAME), blob);
appendAttributeToBlob(agentLinkingMetadata.get(TRACE_ID), blob);
Expand Down
5 changes: 2 additions & 3 deletions instrumentation/apache-log4j-2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ jar {

dependencies {
implementation(project(":agent-bridge"))
implementation("org.apache.logging.log4j:log4j-core:2.17.1")
implementation("org.apache.logging.log4j:log4j-core:2.20.0")
}

verifyInstrumentation {
passesOnly("org.apache.logging.log4j:log4j-core:[2.6,)")
excludeRegex '.*(alpha|beta|rc).*'
passesOnly("org.apache.logging.log4j:log4j-core:[2.6,3.0.0-alpha1)")
}

site {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
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;
Expand Down Expand Up @@ -128,4 +127,28 @@ private static int calculateInitialMapSize(Map<String, String> mdcPropertyMap) {
? 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;
}

}
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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
*
* * Copyright 2023 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package org.apache.logging.log4j.core.layout;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import org.apache.logging.log4j.core.LogEvent_Instrumentation;
import org.apache.logging.log4j.core.util.StringBuilderWriter;

import java.io.IOException;
import java.io.Writer;

import static com.newrelic.agent.bridge.logging.AppLoggingUtils.BLOB_PREFIX;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.getLinkingMetadataBlob;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.getLinkingMetadataBlobFromMap;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingEnabled;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingLocalDecoratingEnabled;
import static com.nr.agent.instrumentation.log4j2.AgentUtil.foundIndexToInsertLinkingMetadata;
import static com.nr.agent.instrumentation.log4j2.AgentUtil.getIndexToModifyJson;

@Weave(originalName = "org.apache.logging.log4j.core.layout.AbstractJacksonLayout", type = MatchType.ExactClass)
abstract class AbstractJacksonLayout_Instrumentation {

public abstract void toSerializable(final LogEvent_Instrumentation event, final Writer writer) throws IOException;

public String toSerializable(final LogEvent_Instrumentation event) {
final StringBuilderWriter writer = new StringBuilderWriter();
try {
toSerializable(event, writer);
String writerString = writer.toString();
String modified = writerString;

// Append linking metadata to the log message if local decorating is enabled
if (isApplicationLoggingEnabled() && isApplicationLoggingLocalDecoratingEnabled()) {
// It is possible that the log might already have NR-LINKING metadata from JUL instrumentation
if (!writerString.contains(BLOB_PREFIX)) {
int indexToModifyJson = getIndexToModifyJson(writerString);
if (foundIndexToInsertLinkingMetadata(indexToModifyJson)) {
// Replace the JSON string with modified version that includes NR-LINKING metadata
if (event != null && event.agentLinkingMetadata != null && (!event.agentLinkingMetadata.isEmpty())) {
// Get linking metadata stored on LogEvent if available. This ensures that
// the trace.id and span.id will be available when using an async appender.
modified = new StringBuilder(writerString).insert(indexToModifyJson, getLinkingMetadataBlobFromMap(event.agentLinkingMetadata))
.toString();
} else {
// Get linking metadata from current thread if it is not available on LogEvent.
modified = new StringBuilder(writerString).insert(indexToModifyJson, getLinkingMetadataBlob()).toString();
}
}
}
return modified;
}

return writerString;
} catch (final IOException e) {
return Weaver.callOriginal();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

import static com.newrelic.agent.bridge.logging.AppLoggingUtils.BLOB_PREFIX;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.getLinkingMetadataBlob;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingEnabled;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingLocalDecoratingEnabled;
Expand All @@ -30,11 +31,15 @@ public void encode(final StringBuilder source, final ByteBufferDestination desti
}

private void appendAgentMetadata(StringBuilder source) {
int breakLine = source.toString().lastIndexOf("\n");
if (breakLine != -1) {
source.replace(breakLine, breakLine + 1, "");
String sourceString = source.toString();
// It is possible that the log might already have NR-LINKING metadata from JUL instrumentation
if (!sourceString.contains(BLOB_PREFIX)) {
int breakLine = sourceString.lastIndexOf("\n");
if (breakLine != -1) {
source.replace(breakLine, breakLine + 1, "");
}
source.append(getLinkingMetadataBlob()).append("\n");
}
source.append(getLinkingMetadataBlob()).append("\n");
}

}
}

0 comments on commit 5a371d5

Please sign in to comment.