From da255b2c417610b13c558f3048ef423a0c9a6d91 Mon Sep 17 00:00:00 2001 From: ndesai-newrelic Date: Tue, 19 Dec 2023 10:52:29 -0600 Subject: [PATCH] feat: adds configurable request header instrumentation to network events The agent will now produce network event attributes for select header values if the headers are detected on the request. The header names to instrument are passed into the agent when started. #138,#135 --- .../com/NewRelic/NRMModularAgentModule.java | 47 ++++++++++++------- index.js | 9 ++++ ios/bridge/NRMModularAgent.m | 5 ++ 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/com/NewRelic/NRMModularAgentModule.java b/android/src/main/java/com/NewRelic/NRMModularAgentModule.java index b282f25..6115355 100644 --- a/android/src/main/java/com/NewRelic/NRMModularAgentModule.java +++ b/android/src/main/java/com/NewRelic/NRMModularAgentModule.java @@ -11,9 +11,11 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.ReadableType; import com.newrelic.agent.android.FeatureFlag; import com.newrelic.agent.android.NewRelic; import com.newrelic.agent.android.ApplicationFramework; @@ -27,6 +29,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; + +import javax.annotation.Nonnull; public class NRMModularAgentModule extends ReactContextBaseJavaModule { @@ -39,6 +44,7 @@ public NRMModularAgentModule(ReactApplicationContext reactContext) { } @Override + @Nonnull public String getName() { return "NRMModularAgent"; } @@ -148,7 +154,7 @@ public void startAgent(String appKey, String agentVersion, String reactNativeVer } Map mapToAttributes(ReadableMap readableMap) { - Map attributeMap = new HashMap(); + Map attributeMap = new HashMap<>(); ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); while (iterator.hasNextKey()) { @@ -177,6 +183,19 @@ Map mapToAttributes(ReadableMap readableMap) { return attributeMap; } + List mapToList(ReadableArray readableArray) { + List attributeArray = new ArrayList<>(); + + for (int i = 0; i < readableArray.size(); i++) { + ReadableType type = readableArray.getType(i); + + if(type == ReadableType.String) { + attributeArray.add(readableArray.getString(i)); + } + } + return attributeArray; + } + @ReactMethod public void isAgentStarted(String name, Callback callback) { callback.invoke(false, NewRelic.isStarted()); @@ -229,6 +248,12 @@ public void recordCustomEvent(String eventType, String eventName, ReadableMap re NewRelic.recordCustomEvent(eventType, eventName, mapToAttributes(readableMap)); } + @ReactMethod + public void addHTTPHeadersTrackingFor(ReadableArray readableArray) { + NewRelic.addHTTPHeadersTrackingFor(mapToList(readableArray)); + } + + @ReactMethod public void crashNow(String message) { if(message.isEmpty()) { @@ -442,22 +467,10 @@ private StackTraceElement[] generateStackTraceElements(Map stack for(int i = 0; i < stackFrameMap.size(); ++i) { Map element = (Map) stackFrameMap.get(Integer.toString(i)); String methodName = null; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - methodName = (String) element.getOrDefault("methodName", ""); - } else { - if (element.containsKey("methodName")) { - methodName = (String)element.get("methodName"); - } - } - String fileName = null; - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - fileName = (String) element.getOrDefault("file", ""); - } else { - if (element.containsKey("file")) { - fileName = (String)element.get("file"); - } - } - int lineNumber = element.get("lineNumber") != null ? ((Double) element.get("lineNumber")).intValue() : 1; + methodName = (String) element.getOrDefault("methodName", ""); + String fileName; + fileName = (String) element.getOrDefault("file", ""); + int lineNumber = element.get("lineNumber") != null ? ((Double) Objects.requireNonNull(element.get("lineNumber"))).intValue() : 1; StackTraceElement stackTraceElement = new StackTraceElement(" ", methodName, fileName, lineNumber); stackTraceList.add(stackTraceElement); } diff --git a/index.js b/index.js index dff599d..d68b925 100644 --- a/index.js +++ b/index.js @@ -266,6 +266,15 @@ class NewRelic { this.NRMAModularAgentWrapper.execute('noticeHttpTransaction', url, httpMethod, statusCode, startTime, endTime, bytesSent, bytesReceived, responseBody); } + /** + * Add Headers as Attributes in Http Requests, for use in New Relic Insights. + * The method includes a list of headers, specified as a map. + * @param headers {Map} A map that includes a list of headers. + */ + addHTTPHeadersTrackingFor(headers) { + this.NRMAModularAgentWrapper.execute('addHTTPHeadersTrackingFor', headers); + } + /** * Records network failures. * If a network request fails, use this method to record details about the failure. diff --git a/ios/bridge/NRMModularAgent.m b/ios/bridge/NRMModularAgent.m index 1d0c12d..4b2dea2 100644 --- a/ios/bridge/NRMModularAgent.m +++ b/ios/bridge/NRMModularAgent.m @@ -299,6 +299,11 @@ - (dispatch_queue_t)methodQueue{ [NewRelic recordCustomEvent:eventType name:eventName attributes:attributes]; } +RCT_EXPORT_METHOD(addHTTPHeadersTrackingFor:(NSArray _Nonnull) headers) { + // todo: Not sure if we need to check the validity of these arguments at all.. + [NewRelic addHTTPHeadersTrackingFor:headers]; +} + /** * Track a method as an interaction */