From 852195b85024ad31ce118139e007f864de9de40e Mon Sep 17 00:00:00 2001 From: ndesai-newrelic <89222514+ndesai-newrelic@users.noreply.github.com> Date: Mon, 22 Jul 2024 13:02:22 -0500 Subject: [PATCH] Nr 258545 log forwarding (#95) * feat:added log forwarding for cordova. * feat: log forwarding for mobile --- CHANGELOG.md | 17 +++ README.md | 45 ++++++ package.json | 2 +- plugin.xml | 13 +- src/android/NewRelicCordovaPlugin.java | 190 ++++++++++++++++++------- src/ios/NewRelicCordovaPlugin.h | 18 +++ src/ios/NewRelicCordovaPlugin.m | 157 ++++++++++++++------ www/js/newrelic.js | 149 ++++++++++++++++++- 8 files changed, 489 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 595802a..6e13a94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +# 7.0.0 + +## New Features + +1. Application Exit Information + - Added ApplicationExitInfo to data reporting + - Enabled by default + +2. Log Forwarding to New Relic + - Implement static API for sending logs to New Relic + - Can be enabled/disabled in your mobile application's entity settings page + +## Improvements + +- Native Android agent updated to version 7.5.0 +- Native iOS agent updated to version 7.5.0 + # 6.2.10 * Improvements diff --git a/README.md b/README.md index 997684d..8ae9815 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,51 @@ By default, these configurations are already set to true on agent start. NewRelic.httpRequestBodyCaptureEnabled(true); ``` +## [logInfo] (message: string): void; +> Logs an informational message. +```js +NewRelic.logInfo("User logged in successfully"); +``` +## [logDebug] (message: string): void; +> Logs a debug message. +```js +NewRelic.logDebug("Debug message"); +``` + +## [logVerbose] (message: string): void; +> Logs a verbose message, typically used for extensive informational events that are useful in tracing the application's execution in a more detailed manner. +```js +NewRelic.logVerbose("Verbose message detailing step-by-step execution"); +``` + +## [logWarn] (message: string): void; +> Logs a warning message. Warning messages are typically used to indicate a potential issue or to highlight situations that are not necessarily errors, but which may require attention or could lead to errors if ignored. +```js +NewRelic.logWarn("Warning message indicating a potential issue"); +``` + +## [logError] (message: string): void; +> Logs an error message. Error messages are used to indicate a failure or problem that has occurred in the application. These are critical messages that usually require immediate attention. +```js +NewRelic.logError("Error message indicating a failure"); +``` +## [log] (level: string, message: string): void; +> Logs a message with a specified level. The level indicates the severity or importance of the message. Common levels include "info", "warn", "error", "debug", and "verbose". +```js +NewRelic.log("INFO", "User logged in successfully"); +``` +## [logAttributes] (attributes: {[key: string]: boolean | number | string}): void; +> Logs multiple attributes at once by passing a map of key-value pairs. This method allows for the efficient addition of contextual information to logs, enhancing the diagnostic capability of logged events. +```js +NewRelic.logAttributes({ + "userID": 12345, + "sessionID": "abcde12345", + "isLoggedIn": true, + "message":"this is test", + "level":"INFO" +}); +``` + ## Error Reporting ### recordError(err: [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error), attributes?: {[key: string]: boolean | number | string}) : void; Records JavaScript errors for Cordova. It is useful to add this method by adding it to the error handler of the framework that you are using. Here are some examples below: diff --git a/package.json b/package.json index a7b8e35..ab04e63 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "newrelic-cordova-plugin", - "version": "6.2.10", + "version": "7.0.0", "description": "New Relic Cordova Plugin for iOS and Android", "repo": "https://github.com/newrelic/newrelic-cordova-plugin/", "scripts": { diff --git a/plugin.xml b/plugin.xml index 83e433b..3eedb41 100644 --- a/plugin.xml +++ b/plugin.xml @@ -6,7 +6,7 @@ + id="newrelic-cordova-plugin" version="7.0.0"> NewRelic New Relic Cordova Plugin for iOS and Android New Relic @@ -18,7 +18,7 @@ - + @@ -33,6 +33,8 @@ + + @@ -59,6 +61,7 @@ + @@ -76,7 +79,7 @@ - + @@ -86,7 +89,7 @@ - + @@ -118,7 +121,7 @@ - + strToLogLevel = new HashMap<>(); strToLogLevel.put("ERROR", AgentLog.ERROR); strToLogLevel.put("WARNING", AgentLog.WARN); @@ -100,7 +106,8 @@ public void initialize(CordovaInterface cordova, CordovaWebView webView) { String collectorAddress = preferences.getString("collector_address", null); String crashCollectorAddress = preferences.getString("crash_collector_address", null); - NewRelic newRelic = NewRelic.withApplicationToken(appToken) + LogReporting.setLogLevel(LogLevel.VERBOSE); + NewRelic newRelic = NewRelic.withApplicationToken(appToken) .withApplicationFramework(ApplicationFramework.Cordova, pluginVersion) .withLoggingEnabled(preferences.getString("logging_enabled", "true").toLowerCase().equals("true")) .withLogLevel(logLevel); @@ -195,14 +202,19 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo } case "recordLogs": { if (preferences.getString("console_logs_enabled", "true").equalsIgnoreCase("true")) { - final String eventType = args.getString(0); - final String eventName = args.getString(1); - final JSONObject attributesASJson = args.getJSONObject(2); - final Map attributes = new Gson().fromJson(String.valueOf(attributesASJson), - Map.class); - NewRelic.recordCustomEvent(eventType, eventName, attributes); - break; + cordova.getThreadPool().execute(() -> { + try { + final String eventType = args.getString(0); + final String eventName = args.getString(1); + final JSONObject attributesASJson = args.getJSONObject(2); + final Map attributes = new Gson().fromJson(String.valueOf(attributesASJson), Map.class); + NewRelic.recordCustomEvent(eventType, eventName, attributes); + } catch (JSONException e) { + NewRelic.recordHandledException(e); + } + }); } + break; } case "setAttribute": { final String name = args.getString(0); @@ -222,39 +234,43 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo break; } case "recordError": { - final String errorName = args.getString(0); - final String errorMessage = args.getString(1); - final String errorStack = args.getString(2); - final Boolean isFatal = args.getBoolean(3); - final JSONObject attributesAsJson = args.getJSONObject(4); - - HashMap exceptionMap = new HashMap<>(); - try { - exceptionMap.put("name", errorName); - exceptionMap.put("message", errorMessage); - exceptionMap.put("isFatal", isFatal); - if (attributesAsJson != null) { - final Map attributes = new Gson().fromJson(String.valueOf(attributesAsJson), - Map.class); - for (String key : attributes.keySet()) { - exceptionMap.put(key, attributes.get(key)); + cordova.getThreadPool().execute(() -> { + try { + final String errorName = args.getString(0); + final String errorMessage = args.getString(1); + final String errorStack = args.getString(2); + final Boolean isFatal = args.getBoolean(3); + final JSONObject attributesAsJson = args.getJSONObject(4); + + HashMap exceptionMap = new HashMap<>(); + try { + exceptionMap.put("name", errorName); + exceptionMap.put("message", errorMessage); + exceptionMap.put("isFatal", isFatal); + if (attributesAsJson != null) { + final Map attributes = new Gson().fromJson(String.valueOf(attributesAsJson), + Map.class); + for (String key : attributes.keySet()) { + exceptionMap.put(key, attributes.get(key)); + } + } + } catch (IllegalArgumentException e) { + Log.w("NRMA", e.getMessage()); } - } - } catch (IllegalArgumentException e) { - Log.w("NRMA", e.getMessage()); - } - - if(errorStack == null) { - NewRelic.recordBreadcrumb("JS Errors", exceptionMap); - StatsEngine.get().inc("Supportability/Mobile/Cordova/JSError"); - break; - } - StackTraceElement[] stackTraceElements = parseStackTrace(errorStack); - NewRelicCordovaException exception = new NewRelicCordovaException(errorMessage, stackTraceElements); - exception.setStackTrace(stackTraceElements); - NewRelic.recordHandledException(exception, exceptionMap); + if (errorStack == null) { + NewRelic.recordBreadcrumb("JS Errors", exceptionMap); + StatsEngine.get().inc("Supportability/Mobile/Cordova/JSError"); + } + StackTraceElement[] stackTraceElements = parseStackTrace(errorStack); + NewRelicCordovaException exception = new NewRelicCordovaException(errorMessage, stackTraceElements); + exception.setStackTrace(stackTraceElements); + NewRelic.recordHandledException(exception, exceptionMap); + } catch (JSONException e) { + NewRelic.recordHandledException(e); + } + }); break; } case "noticeHttpTransaction": { @@ -267,19 +283,19 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo final int bytesReceived = args.getInt(6); final String body = args.getString(7); final Object traceAttributes = args.get(9); - Map traceHeadersMap = new HashMap(); + Map traceHeadersMap = new HashMap(); if (traceAttributes instanceof JSONObject) { traceHeadersMap = new Gson().fromJson(String.valueOf(traceAttributes), Map.class); } Object params = args.get(8); - Map paramsMap = new HashMap<>(); + Map paramsMap = new HashMap<>(); if (params instanceof JSONObject) { paramsMap = new Gson().fromJson(String.valueOf(params), Map.class); } NewRelic.noticeHttpTransaction(url, method, status, startTime, endTime, bytesSent, bytesReceived, - body,paramsMap,"",traceHeadersMap); + body, paramsMap, "", traceHeadersMap); break; } case "generateDistributedTracingHeaders": { @@ -418,7 +434,7 @@ public void run() { } case "analyticsEventEnabled": { final boolean enabled = args.getBoolean(0); - if(enabled) { + if (enabled) { NewRelic.enableFeature(FeatureFlag.AnalyticsEvents); } else { NewRelic.disableFeature(FeatureFlag.AnalyticsEvents); @@ -427,7 +443,7 @@ public void run() { } case "networkRequestEnabled": { final boolean enabled = args.getBoolean(0); - if(enabled) { + if (enabled) { NewRelic.enableFeature(FeatureFlag.NetworkRequests); } else { NewRelic.disableFeature(FeatureFlag.NetworkRequests); @@ -436,7 +452,7 @@ public void run() { } case "networkErrorRequestEnabled": { final boolean enabled = args.getBoolean(0); - if(enabled) { + if (enabled) { NewRelic.enableFeature(FeatureFlag.NetworkErrorRequests); } else { NewRelic.disableFeature(FeatureFlag.NetworkErrorRequests); @@ -445,7 +461,7 @@ public void run() { } case "httpRequestBodyCaptureEnabled": { final boolean enabled = args.getBoolean(0); - if(enabled) { + if (enabled) { NewRelic.enableFeature(FeatureFlag.HttpResponseBodyCapture); } else { NewRelic.disableFeature(FeatureFlag.HttpResponseBodyCapture); @@ -477,7 +493,72 @@ public void run() { } headers.put("headersList", array); callbackContext.success(headers); + break; + } + case "logInfo": { + final String message = args.getString(0); + NewRelic.logInfo(message); + break; + } + case "logError": { + final String message = args.getString(0); + NewRelic.logError(message); + break; } + case "logWarn": { + final String message = args.getString(0); + NewRelic.logWarning(message); + break; + } + case "logVerbose": { + final String message = args.getString(0); + NewRelic.logVerbose(message); + break; + } + case "logDebug": { + final String message = args.getString(0); + NewRelic.logDebug(message); + break; + } + case "log": { + final String message = args.getString(0); + final String level = args.getString(1); + + if (message == null || message.isEmpty()) { + Log.w(TAG, "Empty message given to log"); + return false; + } + + if (level == null) { + Log.w(TAG, "Null logLevel given to log"); + return false; + } + + Map strToLogLevel = new HashMap<>(); + strToLogLevel.put("ERROR", LogLevel.ERROR); + strToLogLevel.put("WARNING", LogLevel.WARN); + strToLogLevel.put("INFO", LogLevel.INFO); + strToLogLevel.put("VERBOSE", LogLevel.VERBOSE); + strToLogLevel.put("AUDIT", LogLevel.DEBUG); + + LogLevel logLevel = strToLogLevel.get(level); + + assert logLevel != null; + NewRelic.log(logLevel, message); + break; + } + case "logAll": { + final String message = args.getString(0); + final JSONObject attributesASJson = args.getJSONObject(1); + logAttributesFromJson(attributesASJson, message); + break; + } + case "logAttributes": { + final JSONObject attributesASJson = args.getJSONObject(0); + logAttributesFromJson(attributesASJson, null); + break; + } + } } catch (Exception e) { @@ -489,6 +570,19 @@ public void run() { } + private void logAttributesFromJson(JSONObject attributesAsJson, String message) { + final Map attributes = new Gson().fromJson(String.valueOf(attributesAsJson), Map.class); + if (attributes == null) { + Log.e(TAG, "Null attributes given to logAttributes"); + return; + } + if (message != null) { + attributes.put("message", message); + } + NewRelic.logAttributes(attributes); + } + + protected static final class NRTraceConstants { public static final String TRACE_PARENT = "traceparent"; public static final String TRACE_STATE = "tracestate"; diff --git a/src/ios/NewRelicCordovaPlugin.h b/src/ios/NewRelicCordovaPlugin.h index aaaf8f8..0165a21 100644 --- a/src/ios/NewRelicCordovaPlugin.h +++ b/src/ios/NewRelicCordovaPlugin.h @@ -67,4 +67,22 @@ - (void)generateDistributedTracingHeaders:(CDVInvokedUrlCommand *)command; +- (void)logInfo:(CDVInvokedUrlCommand *)command; + +- (void)logError:(CDVInvokedUrlCommand *)command; + +- (void)logWarn:(CDVInvokedUrlCommand *)command; + +- (void)logDebug:(CDVInvokedUrlCommand *)command; + +- (void)logVerbose:(CDVInvokedUrlCommand *)command; + +- (void)log:(CDVInvokedUrlCommand *)command; + +- (void)logAttributes:(CDVInvokedUrlCommand *)command; + +- (void)logAll:(CDVInvokedUrlCommand *)command; + + + @end diff --git a/src/ios/NewRelicCordovaPlugin.m b/src/ios/NewRelicCordovaPlugin.m index 54c685d..bd6ffb0 100644 --- a/src/ios/NewRelicCordovaPlugin.m +++ b/src/ios/NewRelicCordovaPlugin.m @@ -1,6 +1,6 @@ /* * Copyright (c) 2022-present New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 + * SPDX-License-Identifier: Apache-2.0 */ #import "NewRelicCordovaPlugin.h" @@ -12,7 +12,7 @@ + (void) setPlatformVersion:(NSString*)version; @implementation NewRelicCordovaPlugin { NSRegularExpression* jscRegex; NSRegularExpression* geckoRegex; - NSRegularExpression* nodeRegex; + NSRegularExpression* nodeRegex; } @@ -21,13 +21,13 @@ - (void)pluginInitialize NSString* applicationToken = [self.commandDelegate.settings objectForKey:@"ios_app_token"]; NSDictionary* config = self.commandDelegate.settings; - + jscRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*(?:([^@]*)(?:\\((.*?)\\))?@)?(\\S.*?):(\\d+)(?::(\\d+))?\\s*$" options:NSRegularExpressionCaseInsensitive error:nil]; geckoRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*(.*?)(?:\\((.*?)\\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\\[native).*?|[^@]*bundle)(?::(\\d+))?(?::(\\d+))?\\s*$" - options:NSRegularExpressionCaseInsensitive - error:nil]; + options:NSRegularExpressionCaseInsensitive + error:nil]; nodeRegex = [NSRegularExpression regularExpressionWithPattern:@"^\\s*at (?:((?:\\[object object\\])?[^\\/]+(?: \\[as \\S+\\])?) )?\\(?(.*?):(\\d+)(?::(\\d+))?\\)?\\s*$" options:NSRegularExpressionCaseInsensitive error:nil]; @@ -58,15 +58,12 @@ - (void)pluginInitialize if (![self shouldDisableFeature:config[@"offline_storage_enabled"]]) { [NewRelic enableFeatures:NRFeatureFlag_OfflineStorage]; } - - if (![self shouldDisableFeature:config[@"background_reporting_enabled"]]) { - [NewRelic enableFeatures:NRFeatureFlag_BackgroundReporting]; - } - + if (![self shouldDisableFeature:config[@"new_event_system_enabled"]]) { - [NewRelic enableFeatures:NRFeatureFlag_NewEventSystem]; + [NewRelic enableFeatures:NRFeatureFlag_NewEventSystem]; } + // Set log level depending on loggingEnabled and logLevel NRLogLevels logLevel = NRLogLevelWarning; NSDictionary *logDict = @{ @@ -92,7 +89,8 @@ - (void)pluginInitialize [NewRelic setPlatform:NRMAPlatform_Cordova]; [NewRelic setPlatformVersion:config[@"plugin_version"]]; - + + if ([self isEmptyConfigParameter:collectorAddress] && [self isEmptyConfigParameter:crashCollectorAddress]) { [NewRelic startWithApplicationToken:applicationToken]; } else { @@ -130,20 +128,20 @@ - (void)recordCustomEvent:(CDVInvokedUrlCommand *)command { NSString* eventType = [command.arguments objectAtIndex:0]; NSString* eventName = [command.arguments objectAtIndex:1]; NSDictionary *attributes = [command.arguments objectAtIndex:2]; - + [NewRelic recordCustomEvent:eventType name:eventName attributes:attributes]; } - (void)recordLogs:(CDVInvokedUrlCommand *)command { NSDictionary* config = self.commandDelegate.settings; - if (![self shouldDisableFeature:config[@"console_logs_enabled"]]) { - - NSString* eventType = [command.arguments objectAtIndex:0]; - NSString* eventName = [command.arguments objectAtIndex:1]; - NSDictionary *attributes = [command.arguments objectAtIndex:2]; - - [NewRelic recordCustomEvent:eventType name:eventName attributes:attributes]; - } + if (![self shouldDisableFeature:config[@"console_logs_enabled"]]) { + + NSString* eventType = [command.arguments objectAtIndex:0]; + NSString* eventName = [command.arguments objectAtIndex:1]; + NSDictionary *attributes = [command.arguments objectAtIndex:2]; + + [NewRelic recordCustomEvent:eventType name:eventName attributes:attributes]; + } } - (void)setAttribute:(CDVInvokedUrlCommand *)command { @@ -170,12 +168,12 @@ - (void)startInteraction:(CDVInvokedUrlCommand *)command { NSString* interactionId = [NewRelic startInteractionWithName:(NSString * _Null_unspecified)actionName]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:interactionId]; - + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } - (void)endInteraction:(CDVInvokedUrlCommand *)command { - + CDVPluginResult* pluginResult = nil; NSString* actionName = [command.arguments objectAtIndex:0]; [NewRelic stopCurrentInteraction:actionName]; @@ -221,7 +219,7 @@ - (void)recordError:(CDVInvokedUrlCommand *)command { NSString* errorStack = [command.arguments objectAtIndex:2]; NSString* isFatal = @"false"; NSDictionary* errorAttributes = [command.arguments objectAtIndex:4]; - + if ([[command.arguments objectAtIndex:3] boolValue] == YES) { isFatal = @"true"; } @@ -253,13 +251,13 @@ - (void)recordError:(CDVInvokedUrlCommand *)command { - (void)noticeDistributedTrace:(CDVInvokedUrlCommand *)command { CDVPluginResult* pluginResult = nil; - + NSDictionary* headers = [NewRelic generateDistributedTracingHeaders]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:headers]; - + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - + } - (void)noticeHttpTransaction:(CDVInvokedUrlCommand *)command { @@ -286,7 +284,7 @@ - (void)noticeHttpTransaction:(CDVInvokedUrlCommand *)command { else { traceAttributes = [command.arguments objectAtIndex:9]; } - + NSURL *nsurl = [NSURL URLWithString:url]; NSData* data; if (body == [NSNull null]) { @@ -294,7 +292,7 @@ - (void)noticeHttpTransaction:(CDVInvokedUrlCommand *)command { } else { data = [body dataUsingEncoding:NSUTF8StringEncoding]; } - + [NewRelic noticeNetworkRequestForURL:nsurl httpMethod:method startTime:[startTime doubleValue] endTime:[endTime doubleValue] responseHeaders:nil statusCode:(long)[status integerValue] bytesSent:(long)[bytesSent integerValue] bytesReceived:(long)[bytesreceived integerValue] responseData:data traceHeaders:traceAttributes andParams:params]; } @@ -329,14 +327,14 @@ - (void)noticeNetworkFailure:(CDVInvokedUrlCommand *)command { NSURL *nsurl = [NSURL URLWithString:url]; NSDictionary *dict = @{ - @"Unknown": [NSNumber numberWithInt:NRURLErrorUnknown], - @"BadURL": [NSNumber numberWithInt:NRURLErrorBadURL], - @"TimedOut": [NSNumber numberWithInt:NRURLErrorTimedOut], - @"CannotConnectToHost": [NSNumber numberWithInt:NRURLErrorCannotConnectToHost], - @"DNSLookupFailed": [NSNumber numberWithInt:NRURLErrorDNSLookupFailed], - @"BadServerResponse": [NSNumber numberWithInt:NRURLErrorBadServerResponse], - @"SecureConnectionFailed": [NSNumber numberWithInt:NRURLErrorSecureConnectionFailed], - }; + @"Unknown": [NSNumber numberWithInt:NRURLErrorUnknown], + @"BadURL": [NSNumber numberWithInt:NRURLErrorBadURL], + @"TimedOut": [NSNumber numberWithInt:NRURLErrorTimedOut], + @"CannotConnectToHost": [NSNumber numberWithInt:NRURLErrorCannotConnectToHost], + @"DNSLookupFailed": [NSNumber numberWithInt:NRURLErrorDNSLookupFailed], + @"BadServerResponse": [NSNumber numberWithInt:NRURLErrorBadServerResponse], + @"SecureConnectionFailed": [NSNumber numberWithInt:NRURLErrorSecureConnectionFailed], + }; NSInteger iOSFailureCode = [[dict valueForKey:failure] integerValue]; [NewRelic noticeNetworkFailureForURL:nsurl httpMethod:httpMethod startTime:[startTime doubleValue] endTime:[endTime doubleValue] andFailureCode:iOSFailureCode]; } @@ -428,28 +426,101 @@ - (void)shutdown:(CDVInvokedUrlCommand *)command { - (void)addHTTPHeadersTrackingFor:(CDVInvokedUrlCommand *) command{ NSArray* headers = [command.arguments objectAtIndex:0]; [NewRelic addHTTPHeaderTrackingFor:headers]; - + } - (void)getHTTPHeadersTrackingFor:(CDVInvokedUrlCommand *) command{ CDVPluginResult* pluginResult = nil; - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{@"headersList": @"[]"}]; - + NSArray* headers = [NewRelic httpHeadersAddedForTracking]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: @{@"headersList": headers}]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - + } - (void)generateDistributedTracingHeaders:(CDVInvokedUrlCommand *)command { CDVPluginResult* pluginResult = nil; - + NSDictionary* headers = [NewRelic generateDistributedTracingHeaders]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:headers]; - + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + +} +- (void)logInfo:(CDVInvokedUrlCommand *)command { + NSString* message = [command.arguments objectAtIndex:0]; + [NewRelic logInfo:message]; + +} + +- (void)logError:(CDVInvokedUrlCommand *)command { + NSString* message = [command.arguments objectAtIndex:0]; + [NewRelic logError:message]; +} + +- (void)logWarn:(CDVInvokedUrlCommand *)command { + NSString* message = [command.arguments objectAtIndex:0]; + [NewRelic logWarning:message]; +} + +- (void)logDebug:(CDVInvokedUrlCommand *)command { + NSString* message = [command.arguments objectAtIndex:0]; + [NewRelic logDebug:message]; +} + +- (void)logVerbose:(CDVInvokedUrlCommand *)command { + NSString* message = [command.arguments objectAtIndex:0]; + [NewRelic logVerbose:message]; +} +- (void)log:(CDVInvokedUrlCommand *)command { + NSString* level = [command.arguments objectAtIndex:0]; + NSString* message = [command.arguments objectAtIndex:1]; + + if(message == nil || message.length == 0) { + return; + } + + if(level == nil || level.length == 0) { + return; + } + + NRLogLevels logLevel = NRLogLevelWarning; + NSDictionary *logDict = @{ + @"ERROR": [NSNumber numberWithInt:NRLogLevelError], + @"WARNING": [NSNumber numberWithInt:NRLogLevelWarning], + @"INFO": [NSNumber numberWithInt:NRLogLevelInfo], + @"VERBOSE": [NSNumber numberWithInt:NRLogLevelVerbose], + @"AUDIT": [NSNumber numberWithInt:NRLogLevelAudit], + }; + if ([logDict objectForKey:[level uppercaseString]]) { + NSString* configLogLevel = [level uppercaseString]; + NSNumber* newLogLevel = [logDict valueForKey:configLogLevel]; + logLevel = [newLogLevel intValue]; + } + + [NewRelic log:message level:logLevel]; +} + +- (void)logAttributes:(CDVInvokedUrlCommand *)command { + NSDictionary *attributes = [command.arguments objectAtIndex:0]; + + [NewRelic logAttributes :attributes]; + +} + +- (void)logAll:(CDVInvokedUrlCommand *)command { + + NSString* message = [command.arguments objectAtIndex:0]; + NSDictionary *attributes = [command.arguments objectAtIndex:1]; + + NSMutableDictionary *mutableDictionary = [attributes mutableCopy]; //Make the dictionary mutable to change/add + + mutableDictionary[@"message"] = message; + + [NewRelic logAttributes:mutableDictionary]; } @end diff --git a/www/js/newrelic.js b/www/js/newrelic.js index 392bf1d..09ea825 100644 --- a/www/js/newrelic.js +++ b/www/js/newrelic.js @@ -124,10 +124,32 @@ endInteraction: function (interactionId, cb, fail) { cordova.exec(cb, fail, "NewRelicCordovaPlugin", "endInteraction", [interactionId]); }, - + + /** + * Send a message to the New Relic Mobile agent logs. + * @param type A string that represents the type of the console log. It can be 'error', 'warn', 'log', 'debug', or 'assert'. + * @param args An array of arguments that are passed to the console log. + */ sendConsole(type, args) { const argsStr = JSON.stringify(args, getCircularReplacer()); - this.send('MobileJSConsole', { consoleType: type, args: argsStr }); + + switch (type) { + case 'error': + this.logError(`[CONSOLE][ERROR]${argsStr}`); + break; + case 'warn': + this.logWarn(`[CONSOLE][WARN]${argsStr}`); + break; + case 'log': + this.logInfo(`[CONSOLE][LOG]${argsStr}`); + break; + case 'debug': + this.logDebug(`[CONSOLE][DEBUG]${argsStr}`); + break; + case 'assert': + this.logVerbose(`[CONSOLE][ASSERT]${argsStr}`); + break; + } }, send(name, args) { @@ -347,6 +369,110 @@ }); }, + /** + * Logs an informational message using the NewRelicCordovaPlugin. + * + * @param {string} message - The message to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logInfo method. + * @param {function} fail - The callback function to be executed if the logInfo method fails. + */ + logInfo: function (message, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logInfo", [message]); + }, + + + /** + * Logs an error message using the NewRelicCordovaPlugin. + * + * @param {string} message - The error message to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logError method. + * @param {function} fail - The callback function to be executed if the logError method fails. + */ + logError: function (message, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logError", [message]); + }, + + /** + * Logs a warning message using the NewRelicCordovaPlugin. + * + * @param {string} message - The warning message to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logWarn method. + * @param {function} fail - The callback function to be executed if the logWarn method fails. + */ + logWarn: function (message, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logWarn", [message]); + }, + + /** + * Logs a debug message using the NewRelicCordovaPlugin. + * + * @param {string} message - The debug message to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logDebug method. + * @param {function} fail - The callback function to be executed if the logDebug method fails. + */ + logDebug: function (message, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logDebug", [message]); + }, + + /** + * Logs a verbose message using the NewRelicCordovaPlugin. + * + * @param {string} message - The verbose message to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logVerbose method. + * @param {function} fail - The callback function to be executed if the logVerbose method fails. + */ + logVerbose: function (message, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logVerbose", [message]); + }, + + /** + * Logs a message with a specified log level using the NewRelicCordovaPlugin. + * + * @param {string} logLevel - The log level of the message to be logged. + * @param {string} message - The message to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the log method. + * @param {function} fail - The callback function to be executed if the log method fails. + */ + log:function (logLevel, message, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "log", [logLevel, message]); + }, + + /** + * Logs attributes using the NewRelicCordovaPlugin. + * + * @param {Map} attributes - The attributes to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logAttributes method. + * @param {function} fail - The callback function to be executed if the logAttributes method fails. + */ + logAttributes: function (attributes, cb, fail) { + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logAttributes", [attributes]); + }, + + /** + * Logs all attributes using the NewRelicCordovaPlugin. + * + * @param {Error} err The error to record. + * * @param {Map} attributes - The attributes to be logged. + * @param {function} cb - The callback function to be executed upon successful execution of the logAll method. + * @param {function} fail - The callback function to be executed if the logAll method fails. + */ + logAll:function (err,attributes = {}, cb, fail) { + if (attributes === null) { + attributes = {}; + } + if (err) { + var error; + + if (err instanceof Error) { + error = err; + } + + if (typeof err === 'string') { + error = new Error(err || ''); + } + } + cordova.exec(cb, fail, "NewRelicCordovaPlugin", "logAll", [error.message,attributes]); + } } networkRequest = {}; @@ -557,7 +683,11 @@ const defaultLog = window.console.log; const defaultWarn = window.console.warn; const defaultError = window.console.error; - + const defaultDebug = window.console.debug; + const defaultAssert = window.console.assert; + + + console.log = function () { NewRelic.sendConsole('log', arguments); defaultLog.apply(console, arguments); @@ -570,6 +700,16 @@ NewRelic.sendConsole('error', arguments); defaultError.apply(console, arguments); }; + + console.debug = function () { + NewRelic.sendConsole('debug', arguments); + defaultDebug.apply(console, arguments); + }; + + console.assert = function () { + NewRelic.sendConsole('assert', arguments); + defaultAssert.apply(console, arguments); + }; class Utils { static isObject(value) { @@ -688,5 +828,4 @@ }; }; - module.exports = NewRelic; - + module.exports = NewRelic; \ No newline at end of file