Skip to content

Commit

Permalink
Merge branch 'main' of github.com:newrelic/newrelic-java-agent into g…
Browse files Browse the repository at this point in the history
…ha-ubuntu-24
  • Loading branch information
obenkenobi committed Jan 29, 2025
2 parents 2be70e7 + d492642 commit e1702db
Show file tree
Hide file tree
Showing 224 changed files with 7,536 additions and 1,095 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/Test-AITs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
steps:
- name: Retrieve agent from cache
id: retrieve-agent
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # pin@v4
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # pin@v4.2.0 # pin@v4
with:
path: /home/runner/work/newrelic-java-agent/newrelic-java-agent/newrelic-agent/build/newrelicJar/newrelic.jar
key: agent-jar-${{ github.run_id }}
Expand Down Expand Up @@ -197,8 +197,13 @@ jobs:
# looks like docker is not properly sanitized between runs in GHA
# so its disk space may be pilling up and blows up during our tests
- name: Clean Docker image data
run: docker rmi $(docker images -q)
# other cleanups were suggested on runner-images repo
- name: Clean up disk
run: |
docker rmi $(docker images -q)
sudo apt clean
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
## JDK Installs

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/X-Reusable-BuildAgent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
run: ./gradlew $GRADLE_OPTIONS clean jar

- name: Cache agent
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # pin@v4
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # pin@v4.2.0 # pin@v4
with:
path: /home/runner/work/newrelic-java-agent/newrelic-java-agent/newrelic-agent/build/newrelicJar/newrelic.jar
key: agent-jar-${{ github.run_id }}
2 changes: 1 addition & 1 deletion .github/workflows/X-Reusable-VerifyInstrumentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:

- name: Retrieve agent from cache
id: retrieve-agent
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # pin@v4
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # pin@v4.2.0 # pin@v4
with:
path: /home/runner/work/newrelic-java-agent/newrelic-java-agent/newrelic-agent/build/newrelicJar/newrelic.jar
key: agent-jar-${{ github.run_id }}
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,45 @@ Noteworthy changes to the agent are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Version 8.17.0
## New features and improvements

- Add support for jdbc-mariadb 3.0.0 till latest and r2dbc-mariadb 1.1.2 till latest - credit to @dhilpipre - clone of 2142 by @jtduffy in [2146](https://github.com/newrelic/newrelic-java-agent/pull/2146)
- Auto discover AWS account ID in the DynamoDB instrumentation by @meiao in [2148](https://github.com/newrelic/newrelic-java-agent/pull/2148)
- Auto discover AWS account ID in the Lambda sdk instrumentation by @meiao in [2167](https://github.com/newrelic/newrelic-java-agent/pull/2167)
- Support pekko-http on scala 3 for versions 1.0.0 till latest by @kanderson250 in [2163](https://github.com/newrelic/newrelic-java-agent/pull/2163)
- Allow JFR queue size and harvest interval to be configured via agent config by @jtduffy in [2168](https://github.com/newrelic/newrelic-java-agent/pull/2168)
New configs are:
```
jfr:
# The time interval, in seconds, of how often JFR data is sent to New Relic.
# The default is 10 seconds.
harvest_interval: 10
# The size of the queue used to store JFR events. Increasing this can reduce gaps in JFR reported data
# but can also cause resource issues in the agent or cause data to be dropped if backend pipeline
# limits are exceeded.
# See: https://docs.newrelic.com/docs/data-apis/ingest-apis/event-api/introduction-event-api/#limits
# https://docs.newrelic.com/docs/data-apis/ingest-apis/metric-api/metric-api-limits-restricted-attributes/
# Default is 250000
queue_size: 250000
```
- Add AWS Firehose SDK Instrumentation for versions 2.1.0 till latest by @obenkenobi in [2149](https://github.com/newrelic/newrelic-java-agent/pull/2149)
- Implement a new instrumentation module for r2dbc-mysql 1.1.3+ by @jbedell-newrelic in [2169](https://github.com/newrelic/newrelic-java-agent/pull/2169)
- Memory usage reduced for the r2dbc-mssql and m2dbc-mysql modules by @jbedell-newrelic in [2169](https://github.com/newrelic/newrelic-java-agent/pull/2169)
- Log when multiple, different, traceparent headers found on inbound request and only report `invalid parent header count` supportability metric when that scenario occurs by @jtduffy in [2154](https://github.com/newrelic/newrelic-java-agent/pull/2154)
- Expected NPE in noticeTracer no longer logs full stack trace by @jasonjkeller in [2143](https://github.com/newrelic/newrelic-java-agent/pull/2143)

## Fixes

- Resolved a potential token timeout issue with the reactor-3.3.0 module by @jbedell-newrelic in [2169](https://github.com/newrelic/newrelic-java-agent/pull/2169)

## IAST

- CSEC version bump to 1.6.0 [2173](https://github.com/newrelic/newrelic-java-agent/pull/2173)
- Changelog: https://github.com/newrelic/csec-java-agent/releases/tag/1.6.0


## Version 8.16.0
## New features and improvements

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# The agent version.
agentVersion=8.17.0
agentVersion=8.18.0
securityAgentVersion=1.6.0

newrelicDebug=false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import com.newrelic.agent.InstrumentationProxy;
import com.newrelic.agent.core.CoreService;
import com.newrelic.agent.service.AbstractService;
import com.newrelic.agent.superagent.HealthDataChangeListener;
import com.newrelic.agent.superagent.HealthDataProducer;
import com.newrelic.agent.agentcontrol.HealthDataChangeListener;
import com.newrelic.agent.agentcontrol.HealthDataProducer;

class IntrospectorCoreService extends AbstractService implements CoreService, HealthDataProducer {
private InstrumentationProxy instrumentation = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import com.newrelic.agent.service.module.JarData;
import com.newrelic.agent.sql.SqlTrace;
import com.newrelic.agent.stats.StatsEngine;
import com.newrelic.agent.superagent.HealthDataProducer;
import com.newrelic.agent.agentcontrol.HealthDataProducer;
import com.newrelic.agent.trace.TransactionTrace;
import com.newrelic.agent.transaction.TransactionNamingScheme;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import org.apache.logging.log4j.core.LogEvent;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.newrelic.agent.bridge.logging.AppLoggingUtils.BLOB_PREFIX;
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.getLinkingMetadataBlob;
Expand All @@ -18,6 +20,8 @@
import static com.newrelic.agent.bridge.logging.AppLoggingUtils.isApplicationLoggingLocalDecoratingEnabled;

public class AgentUtils {
private static final Pattern JSON_MESSAGE_VALUE_END = Pattern.compile("\"message\".+?[^\\\\]((\",)|(\"}))");

/**
* 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
Expand All @@ -27,17 +31,14 @@ public class AgentUtils {
* @return positive int if index was found, else -1
*/
public static int getIndexToModifyJson(String writerString) {
int msgIndex = writerString.indexOf("message");
if (msgIndex < 0) {
return msgIndex;
}
// If the "message" field is before other fields in the json string
int index = writerString.indexOf("\",", msgIndex);
int index = -1;
Matcher matcher = JSON_MESSAGE_VALUE_END.matcher(writerString);

if (index < 0 ) {
// If "message" is the last field in the json string
index = writerString.indexOf("\"}", msgIndex);
if (matcher.find()) {
// Group 1 in the match is the ", char sequence
index = matcher.start(1);
}

return index;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.nr.agent.instrumentation.log4j2.layout.template.json;

import com.newrelic.agent.bridge.Agent;
import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.api.agent.Config;
import com.newrelic.api.agent.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class AgentUtilsTest {
private static final String LOG_JSON_WITH_MESSAGE_FIELD_ESCAPED_QUOTE_COMMA = "{\"instant\" : {\"epochSecond\" : 1734983121,\"nanoOfSecond\" : 537701000},\"level\" : \"INFO\",\"loggerName\" : \"org.hibernate.validator.internal.util.Version\",\"message\" : \"info \\\"bar\\\",\",\"endOfBatch\" : true,\"loggerFqcn\" : \"org.hibernate.validator.internal.util.logging.Log_$logger\"}\n";
private static final String LOG_JSON_WITH_MESSAGE_LAST_FIELD_ESCAPED_QUOTE_BRACE = "{\"instant\": {\"epochSecond\": 1734983121,\"nanoOfSecond\": 537701000},\"level\": \"INFO\",\"loggerName\": \"org.hibernate.validator.internal.util.Version\",\"endOfBatch\": true,\"loggerFqcn\": \"org.hibernate.validator.internal.util.logging.Log_$logger\",\"message\": \"info \\\"bar\\\",\"}";
private static final String LOG_JSON_WITH_MESSAGE_FIELD = "{\"instant\" : {\"epochSecond\" : 1734983121,\"nanoOfSecond\" : 537701000},\"level\" : \"INFO\",\"loggerName\" : \"org.hibernate.validator.internal.util.Version\",\"message\" : \"normal msg text\",\"endOfBatch\" : true,\"loggerFqcn\" : \"org.hibernate.validator.internal.util.logging.Log_$logger\"}\n";
private static final String LOG_JSON_NO_MESSAGE_FIELD = "{\"instant\": {\"epochSecond\": 1734983121,\"nanoOfSecond\": 537701000},\"level\": \"INFO\",\"loggerName\": \"org.hibernate.validator.internal.util.Version\",\"endOfBatch\": true,\"loggerFqcn\": \"org.hibernate.validator.internal.util.logging.Log_$logger\",\"foobar\": \"info \\\"bar\\\",\"}";

private final Agent originalAgent = AgentBridge.getAgent();
private final Agent mockAgent = mock(Agent.class);
private final Logger mockLogger = mock(Logger.class);
private final Config mockConfig = mock(Config.class);

private final LogEvent mockLockEvent = mock(LogEvent.class);

@Before
public void before() {
AgentBridge.agent = mockAgent;
when(mockAgent.getConfig()).thenReturn(mockConfig);
when(mockAgent.getLogger()).thenReturn(mockLogger);
when(mockLogger.isLoggable(Level.FINEST)).thenReturn(false);
}

@After
public void after() {
AgentBridge.agent = originalAgent;
}

@Test
public void getIndexToModifyJson_withMessageField_findsProperIndex() {
assertEquals(175, AgentUtils.getIndexToModifyJson(LOG_JSON_WITH_MESSAGE_FIELD_ESCAPED_QUOTE_COMMA));
assertEquals(262, AgentUtils.getIndexToModifyJson(LOG_JSON_WITH_MESSAGE_LAST_FIELD_ESCAPED_QUOTE_BRACE));
}

@Test
public void getIndexToModifyJson_withNoMessageField_returnsNegativeOne() {
assertEquals(-1, AgentUtils.getIndexToModifyJson(LOG_JSON_NO_MESSAGE_FIELD));
}

@Test
public void writeLinkingMetadata_addsMetadataProperly() {
when(mockConfig.getValue(anyString(), anyBoolean())).thenReturn(true);

StringBuilder sb = new StringBuilder(LOG_JSON_WITH_MESSAGE_FIELD_ESCAPED_QUOTE_COMMA);
AgentUtils.writeLinkingMetadata(mockLockEvent, sb);
assertTrue(sb.toString().contains("\"message\" : \"info \\\"bar\\\", NR-LINKING|\""));

sb = new StringBuilder(LOG_JSON_WITH_MESSAGE_LAST_FIELD_ESCAPED_QUOTE_BRACE);
AgentUtils.writeLinkingMetadata(mockLockEvent, sb);
assertTrue(sb.toString().contains("\"message\": \"info \\\"bar\\\", NR-LINKING|\"}"));

sb = new StringBuilder(LOG_JSON_WITH_MESSAGE_FIELD);
AgentUtils.writeLinkingMetadata(mockLockEvent, sb);
assertTrue(sb.toString().contains("\"message\" : \"normal msg text NR-LINKING|\""));

sb = new StringBuilder(LOG_JSON_NO_MESSAGE_FIELD);
AgentUtils.writeLinkingMetadata(mockLockEvent, sb);
assertFalse(sb.toString().contains("NR-LINKING|"));

}

// Leaving these next two methods here in case anyone wants to run a simple performance test of the regex matching.
// Simple uncomment the @Test annotation and run like a normal test.
//@Test
public void simplePerfTest() {
final String JSON_TEMPLATE = "{\"instant\" : {\"epochSecond\" : 1734983121,\"nanoOfSecond\" : 537701000},\"level\" : \"INFO\",\"loggerName\" : \"org.hibernate.validator.internal.util.Version\",\"message\" : \"{MSG_VAL}\",\"endOfBatch\" : true,\"loggerFqcn\" : \"org.hibernate.validator.internal.util.logging.Log_$logger\"}\n";
final String ESCAPED_QUOTE_COMMA = "info \\\"bar\\\",";
final int LOOP_SIZE = 1000000;
Random random = new Random();
List<String> randomStringList = new ArrayList<>();

// Gen up a list of random Strings so we don't spend cycles in the actual timing loop creating them
for (int i=0; i<LOOP_SIZE; i++) {
randomStringList.add(generateRandomStr(10));
}

long start = System.currentTimeMillis();
for (int i=0; i<LOOP_SIZE; i++) {
if (random.nextInt(101) >= 90) {
AgentUtils.getIndexToModifyJson(JSON_TEMPLATE.replace("{MSG_VAL}", ESCAPED_QUOTE_COMMA));
} else {
AgentUtils.getIndexToModifyJson(JSON_TEMPLATE.replace("{MSG_VAL}", randomStringList.get(i)));
}
}
System.out.println("Time ---> " + (System.currentTimeMillis() - start));
}

private static String generateRandomStr(int length) {
String CANDIDATE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i ++) {
sb.append(CANDIDATE_CHARS.charAt(random.nextInt(26)));
}

return sb.toString();
}
}
18 changes: 18 additions & 0 deletions instrumentation/apache-struts-6.7.0/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
dependencies {
implementation(project(":agent-bridge"))
implementation("org.apache.struts:struts2-core:6.7.0")
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.apache-struts-6.7.0',
'Implementation-Title-Alias': 'apache-struts' }
}

verifyInstrumentation {
passesOnly 'org.apache.struts:struts2-core:[6.7.0,)'
}

site {
title 'Struts'
type 'Framework'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.apache.struts2;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.Transaction;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TransactionNamePriority;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "org.apache.struts2.ActionProxy")
public abstract class ActionProxy_Instrumentation {
@Trace
public String execute() throws Exception {
Transaction transaction = AgentBridge.getAgent().getTransaction(false);
if (transaction != null) {
transaction.setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, true, "StrutsAction", getActionName());
}

return Weaver.callOriginal();
}

public abstract String getActionName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.apache.struts2.result;

import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.TracedMethod;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import org.apache.struts2.ActionInvocation;

@Weave(type = MatchType.Interface, originalName = "org.apache.struts2.result.Result")
public class Result_Instrumentation {
@Trace
public void execute(ActionInvocation invocation) throws Exception {
TracedMethod tracedMethod = NewRelic.getAgent().getTracedMethod();
if (tracedMethod != null && invocation != null) {
tracedMethod.setMetricName("StrutsResult", invocation.getAction().getClass().getName());
}

Weaver.callOriginal();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.nr.agent.instrumentation;

import com.newrelic.agent.introspec.InstrumentationTestConfig;
import com.newrelic.agent.introspec.InstrumentationTestRunner;
import com.newrelic.agent.introspec.Introspector;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.DefaultActionProxy;
import com.opensymphony.xwork2.mock.MockActionInvocation;
import org.apache.struts2.ActionProxy;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(InstrumentationTestRunner.class)
@InstrumentationTestConfig(includePrefixes = { "org.apache.struts2" })
public class ActionProxyTest {
@Test
public void execute_setsTxnName() throws Exception {
ActionProxy proxy = new ActionProxyTest.SampleStruts2ActionProxy(new MockActionInvocation(), "namespace", "actionName", "method", true, true);

StrutsSampleApp.executeOnActionProxy(proxy);

Introspector introspector = InstrumentationTestRunner.getIntrospector();
Assert.assertTrue(introspector.getTransactionNames().contains("OtherTransaction/StrutsAction/actionName"));
}

public static class SampleStruts2ActionProxy extends DefaultActionProxy {

public SampleStruts2ActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult,
boolean cleanupContext) {
super(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}


}
}
Loading

0 comments on commit e1702db

Please sign in to comment.