diff --git a/android/build.gradle b/android/build.gradle index a6c41ffd0..6c8c4264f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,9 +16,17 @@ rootProject.allprojects { repositories { google() mavenCentral() + maven { + url "https://mvn.instabug.com/nexus/repository/instabug-internal/" + credentials { + username "instabug" + password System.getenv("INSTABUG_REPOSITORY_PASSWORD") + } + } } } + apply plugin: 'com.android.library' android { @@ -44,11 +52,10 @@ android { } dependencies { - api 'com.instabug.library:instabug:14.3.0' + api 'com.instabug.library:instabug:14.3.0.6752106-SNAPSHOT' testImplementation 'junit:junit:4.13.2' testImplementation "org.mockito:mockito-inline:3.12.1" testImplementation "io.mockk:mockk:1.13.13" - } // add upload_symbols task diff --git a/android/src/main/java/com/instabug/apm/networking/ApmNetworkLoggerHelper.java b/android/src/main/java/com/instabug/apm/networking/ApmNetworkLoggerHelper.java new file mode 100644 index 000000000..183ebd89f --- /dev/null +++ b/android/src/main/java/com/instabug/apm/networking/ApmNetworkLoggerHelper.java @@ -0,0 +1,120 @@ +package com.instabug.apm.networking; + + +import androidx.annotation.NonNull; + +import com.instabug.apm.networking.mapping.NetworkRequestAttributes; +import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +public class ApmNetworkLoggerHelper { + + /// Log network request to the Android SDK using a package private API [APMNetworkLogger.log] + static public void log(@NonNull Map data) { + try { + APMNetworkLogger apmNetworkLogger = new APMNetworkLogger(); + final String requestUrl = (String) data.get("url"); + final String requestBody = (String) data.get("requestBody"); + final String responseBody = (String) data.get("responseBody"); + final String requestMethod = (String) data.get("method"); + //-------------------------------------------- + final String requestContentType = (String) data.get("requestContentType"); + final String responseContentType = (String) data.get("responseContentType"); + //-------------------------------------------- + final long requestBodySize = ((Number) data.get("requestBodySize")).longValue(); + final long responseBodySize = ((Number) data.get("responseBodySize")).longValue(); + //-------------------------------------------- + final String errorDomain = (String) data.get("errorDomain"); + final Integer statusCode = (Integer) data.get("responseCode"); + final long requestDuration = ((Number) data.get("duration")).longValue() / 1000; + final long requestStartTime = ((Number) data.get("startTime")).longValue() * 1000; + final String requestHeaders = (new JSONObject((HashMap) data.get("requestHeaders"))).toString(4); + final String responseHeaders = (new JSONObject((HashMap) data.get("responseHeaders"))).toString(4); + final String errorMessage; + + if (errorDomain.equals("")) { + errorMessage = null; + } else { + errorMessage = errorDomain; + } + //-------------------------------------------------- + String gqlQueryName = null; + if (data.containsKey("gqlQueryName")) { + gqlQueryName = (String) data.get("gqlQueryName"); + } + String serverErrorMessage = ""; + if (data.containsKey("serverErrorMessage")) { + serverErrorMessage = (String) data.get("serverErrorMessage"); + } + Boolean isW3cHeaderFound = null; + Number partialId = null; + Number networkStartTimeInSeconds = null; + String w3CGeneratedHeader = null; + String w3CCaughtHeader = null; + + if (data.containsKey("isW3cHeaderFound")) { + isW3cHeaderFound = (Boolean) data.get("isW3cHeaderFound"); + } + + if (data.containsKey("partialId")) { + + + partialId = ((Number) data.get("partialId")); + + } + if (data.containsKey("networkStartTimeInSeconds")) { + networkStartTimeInSeconds = ((Number) data.get("networkStartTimeInSeconds")); + } + + if (data.containsKey("w3CGeneratedHeader")) { + + w3CGeneratedHeader = (String) data.get("w3CGeneratedHeader"); + + + } + if (data.containsKey("w3CCaughtHeader")) { + w3CCaughtHeader = (String) data.get("w3CCaughtHeader"); + + } + + NetworkRequestAttributes requestAttributes = new NetworkRequestAttributes( + requestStartTime * 1000, + requestDuration, + requestHeaders, + requestBody, + requestBodySize, + requestMethod, + requestUrl, + requestContentType, + responseHeaders, + responseBody, + responseBodySize, + statusCode, + responseContentType, + gqlQueryName, + errorMessage, + serverErrorMessage + ); + + APMCPNetworkLog.W3CExternalTraceAttributes w3cExternalTraceAttributes = + null; + if (isW3cHeaderFound != null) { + w3cExternalTraceAttributes = new APMCPNetworkLog.W3CExternalTraceAttributes( + isW3cHeaderFound, partialId == null ? null : partialId.longValue(), + networkStartTimeInSeconds == null ? null : networkStartTimeInSeconds.longValue(), + w3CGeneratedHeader, w3CCaughtHeader + + ); + } + + apmNetworkLogger.log(requestAttributes, w3cExternalTraceAttributes); + + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/android/src/main/java/com/instabug/flutter/modules/ApmApi.java b/android/src/main/java/com/instabug/flutter/modules/ApmApi.java index 607c569a4..496b90eaf 100644 --- a/android/src/main/java/com/instabug/flutter/modules/ApmApi.java +++ b/android/src/main/java/com/instabug/flutter/modules/ApmApi.java @@ -1,7 +1,5 @@ package com.instabug.flutter.modules; -import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -10,20 +8,15 @@ import com.instabug.apm.configuration.cp.APMFeature; import com.instabug.apm.configuration.cp.FeatureAvailabilityCallback; import com.instabug.apm.model.ExecutionTrace; -import com.instabug.apm.networking.APMNetworkLogger; -import com.instabug.apm.networkinterception.cp.APMCPNetworkLog; +import com.instabug.apm.networking.ApmNetworkLoggerHelper; import com.instabug.flutter.generated.ApmPigeon; -import com.instabug.flutter.util.Reflection; import com.instabug.flutter.util.ThreadManager; -import io.flutter.plugin.common.BinaryMessenger; - -import org.json.JSONObject; - -import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import io.flutter.plugin.common.BinaryMessenger; + public class ApmApi implements ApmPigeon.ApmHostApi { private final String TAG = ApmApi.class.getName(); private final HashMap traces = new HashMap<>(); @@ -33,14 +26,14 @@ public static void init(BinaryMessenger messenger) { ApmPigeon.ApmHostApi.setup(messenger, api); } - /** - * The function sets the enabled status of APM. - * - * @param isEnabled The `setEnabled` method in the code snippet is used to enable or disable a - * feature, and it takes a `Boolean` parameter named `isEnabled`. When this method is called with - * `true`, it enables the feature, and when called with `false`, it disables the feature. The method - * internally calls - */ + /** + * The function sets the enabled status of APM. + * + * @param isEnabled The `setEnabled` method in the code snippet is used to enable or disable a + * feature, and it takes a `Boolean` parameter named `isEnabled`. When this method is called with + * `true`, it enables the feature, and when called with `false`, it disables the feature. The method + * internally calls + */ @Override public void setEnabled(@NonNull Boolean isEnabled) { try { @@ -51,12 +44,12 @@ public void setEnabled(@NonNull Boolean isEnabled) { } /** - * Sets the cold app launch enabled status using the APM library. - * - * @param isEnabled The `isEnabled` parameter is a Boolean value that indicates whether cold app launch - * is enabled or not. When `isEnabled` is set to `true`, cold app launch is enabled, and when it is set - * to `false`, cold app launch is disabled. - */ + * Sets the cold app launch enabled status using the APM library. + * + * @param isEnabled The `isEnabled` parameter is a Boolean value that indicates whether cold app launch + * is enabled or not. When `isEnabled` is set to `true`, cold app launch is enabled, and when it is set + * to `false`, cold app launch is disabled. + */ @Override public void setColdAppLaunchEnabled(@NonNull Boolean isEnabled) { try { @@ -66,14 +59,14 @@ public void setColdAppLaunchEnabled(@NonNull Boolean isEnabled) { } } - /** - * The function sets the auto UI trace enabled status in an APM system, handling any exceptions that - * may occur. - * - * @param isEnabled The `isEnabled` parameter is a Boolean value that indicates whether the Auto UI - * trace feature should be enabled or disabled. When `isEnabled` is set to `true`, the Auto UI trace - * feature is enabled, and when it is set to `false`, the feature is disabled. - */ + /** + * The function sets the auto UI trace enabled status in an APM system, handling any exceptions that + * may occur. + * + * @param isEnabled The `isEnabled` parameter is a Boolean value that indicates whether the Auto UI + * trace feature should be enabled or disabled. When `isEnabled` is set to `true`, the Auto UI trace + * feature is enabled, and when it is set to `false`, the feature is disabled. + */ @Override public void setAutoUITraceEnabled(@NonNull Boolean isEnabled) { try { @@ -83,21 +76,20 @@ public void setAutoUITraceEnabled(@NonNull Boolean isEnabled) { } } - /** - * Starts an execution trace and handles the result - * using callbacks. - * - * @param id The `id` parameter is a non-null String that represents the identifier of the execution - * trace. - * @param name The `name` parameter in the `startExecutionTrace` method represents the name of the - * execution trace that will be started. It is used as a reference to identify the trace during - * execution monitoring. - * @param result The `result` parameter in the `startExecutionTrace` method is an instance of - * `ApmPigeon.Result`. This parameter is used to provide the result of the execution trace - * operation back to the caller. The `success` method of the `result` object is called with the - * - * @deprecated see {@link #startFlow} - */ + /** + * Starts an execution trace and handles the result + * using callbacks. + * + * @param id The `id` parameter is a non-null String that represents the identifier of the execution + * trace. + * @param name The `name` parameter in the `startExecutionTrace` method represents the name of the + * execution trace that will be started. It is used as a reference to identify the trace during + * execution monitoring. + * @param result The `result` parameter in the `startExecutionTrace` method is an instance of + * `ApmPigeon.Result`. This parameter is used to provide the result of the execution trace + * operation back to the caller. The `success` method of the `result` object is called with the + * @deprecated see {@link #startFlow} + */ @Override public void startExecutionTrace(@NonNull String id, @NonNull String name, ApmPigeon.Result result) { ThreadManager.runOnBackground( @@ -158,7 +150,7 @@ public void startFlow(@NonNull String name) { } } - /** + /** * Sets custom attributes for AppFlow with a given name. *
* Setting an attribute value to null will remove its corresponding key if it already exists. @@ -187,7 +179,7 @@ public void setFlowAttribute(@NonNull String name, @NonNull String key, @Nullabl } } - /** + /** * Ends AppFlow with a given name. * * @param name AppFlow name to be ended. It can not be empty string or null @@ -201,13 +193,12 @@ public void endFlow(@NonNull String name) { } } - /** + /** * Adds a new attribute to trace * * @param id String id of the trace. * @param key attribute key * @param value attribute value. Null to remove attribute - * * @deprecated see {@link #setFlowAttribute} */ @Override @@ -223,7 +214,6 @@ public void setExecutionTraceAttribute(@NonNull String id, @NonNull String key, * Ends a trace * * @param id string id of the trace. - * * @deprecated see {@link #endFlow} */ @Override @@ -276,109 +266,27 @@ public void endAppLaunch() { /** * logs network-related information - * + * * @param data Map of network data object. */ @Override public void networkLogAndroid(@NonNull Map data) { try { - APMNetworkLogger apmNetworkLogger = new APMNetworkLogger(); - final String requestUrl = (String) data.get("url"); - final String requestBody = (String) data.get("requestBody"); - final String responseBody = (String) data.get("responseBody"); - final String requestMethod = (String) data.get("method"); - //-------------------------------------------- - final String requestContentType = (String) data.get("requestContentType"); - final String responseContentType = (String) data.get("responseContentType"); - //-------------------------------------------- - final long requestBodySize = ((Number) data.get("requestBodySize")).longValue(); - final long responseBodySize = ((Number) data.get("responseBodySize")).longValue(); - //-------------------------------------------- - final String errorDomain = (String) data.get("errorDomain"); - final Integer statusCode = (Integer) data.get("responseCode"); - final long requestDuration = ((Number) data.get("duration")).longValue() / 1000; - final long requestStartTime = ((Number) data.get("startTime")).longValue() * 1000; - final String requestHeaders = (new JSONObject((HashMap) data.get("requestHeaders"))).toString(4); - final String responseHeaders = (new JSONObject((HashMap) data.get("responseHeaders"))).toString(4); - final String errorMessage; - - if (errorDomain.equals("")) { - errorMessage = null; - } else { - errorMessage = errorDomain; - } - //-------------------------------------------------- - String gqlQueryName = null; - if (data.containsKey("gqlQueryName")) { - gqlQueryName = (String) data.get("gqlQueryName"); - } - String serverErrorMessage = ""; - if (data.containsKey("serverErrorMessage")) { - serverErrorMessage = (String) data.get("serverErrorMessage"); - } - Boolean isW3cHeaderFound = null; - Number partialId = null; - Number networkStartTimeInSeconds = null; - String w3CGeneratedHeader = null; - String w3CCaughtHeader = null; - - if (data.containsKey("isW3cHeaderFound")) { - isW3cHeaderFound = (Boolean) data.get("isW3cHeaderFound"); - } - - if (data.containsKey("partialId")) { - - - partialId = ((Number) data.get("partialId")); - - } - if (data.containsKey("networkStartTimeInSeconds")) { - networkStartTimeInSeconds = ((Number) data.get("networkStartTimeInSeconds")); - } - - if (data.containsKey("w3CGeneratedHeader")) { - - w3CGeneratedHeader = (String) data.get("w3CGeneratedHeader"); - - - } - if (data.containsKey("w3CCaughtHeader")) { - w3CCaughtHeader = (String) data.get("w3CCaughtHeader"); - - } - - - APMCPNetworkLog.W3CExternalTraceAttributes w3cExternalTraceAttributes = - null; - if (isW3cHeaderFound != null) { - w3cExternalTraceAttributes = new APMCPNetworkLog.W3CExternalTraceAttributes( - isW3cHeaderFound, partialId == null ? null : partialId.longValue(), - networkStartTimeInSeconds == null ? null : networkStartTimeInSeconds.longValue(), - w3CGeneratedHeader, w3CCaughtHeader - - ); - } - - Method method = Reflection.getMethod(Class.forName("com.instabug.apm.networking.APMNetworkLogger"), "log", long.class, long.class, String.class, String.class, long.class, String.class, String.class, String.class, String.class, String.class, long.class, int.class, String.class, String.class, String.class, String.class, APMCPNetworkLog.W3CExternalTraceAttributes.class); - if (method != null) { - method.invoke(apmNetworkLogger, requestStartTime, requestDuration, requestHeaders, requestBody, requestBodySize, requestMethod, requestUrl, requestContentType, responseHeaders, responseBody, responseBodySize, statusCode, responseContentType, errorMessage, gqlQueryName, serverErrorMessage, w3cExternalTraceAttributes); - } else { - Log.e(TAG, "APMNetworkLogger.log was not found by reflection"); - } - - } catch(Exception e){ - e.printStackTrace(); - } + ApmNetworkLoggerHelper.log(data); + } catch (Exception e) { + e.printStackTrace(); } + } - /** - * This method is responsible for initiating a custom performance UI trace - * in the APM module. It takes three parameters: - * @param screenName: A string representing the name of the screen or UI element being traced. - * @param microTimeStamp: A number representing the timestamp in microseconds when the trace is started. - * @param traceId: A number representing the unique identifier for the trace. - */ + /** + * This method is responsible for initiating a custom performance UI trace + * in the APM module. It takes three parameters: + * + * @param screenName: A string representing the name of the screen or UI element being traced. + * @param microTimeStamp: A number representing the timestamp in microseconds when the trace is started. + * @param traceId: A number representing the unique identifier for the trace. + */ @Override public void startCpUiTrace(@NonNull String screenName, @NonNull Long microTimeStamp, @NonNull Long traceId) { try { @@ -389,16 +297,17 @@ public void startCpUiTrace(@NonNull String screenName, @NonNull Long microTimeSt } - /** - * This method is responsible for reporting the screen - * loading data from Dart side to Android side. It takes three parameters: - * @param startTimeStampMicro: A number representing the start timestamp in microseconds of the screen - * loading custom performance data. - * @param durationMicro: A number representing the duration in microseconds of the screen loading custom - * performance data. - * @param uiTraceId: A number representing the unique identifier for the UI trace associated with the - * screen loading. - */ + /** + * This method is responsible for reporting the screen + * loading data from Dart side to Android side. It takes three parameters: + * + * @param startTimeStampMicro: A number representing the start timestamp in microseconds of the screen + * loading custom performance data. + * @param durationMicro: A number representing the duration in microseconds of the screen loading custom + * performance data. + * @param uiTraceId: A number representing the unique identifier for the UI trace associated with the + * screen loading. + */ @Override public void reportScreenLoadingCP(@NonNull Long startTimeStampMicro, @NonNull Long durationMicro, @NonNull Long uiTraceId) { try { @@ -410,13 +319,14 @@ public void reportScreenLoadingCP(@NonNull Long startTimeStampMicro, @NonNull Lo /** - * This method is responsible for extend the end time if the screen loading custom - * trace. It takes two parameters: - * @param timeStampMicro: A number representing the timestamp in microseconds when the screen loading - * custom trace is ending. - * @param uiTraceId: A number representing the unique identifier for the UI trace associated with the - * screen loading. - */ + * This method is responsible for extend the end time if the screen loading custom + * trace. It takes two parameters: + * + * @param timeStampMicro: A number representing the timestamp in microseconds when the screen loading + * custom trace is ending. + * @param uiTraceId: A number representing the unique identifier for the UI trace associated with the + * screen loading. + */ @Override public void endScreenLoadingCP(@NonNull Long timeStampMicro, @NonNull Long uiTraceId) { try { @@ -428,7 +338,7 @@ public void endScreenLoadingCP(@NonNull Long timeStampMicro, @NonNull Long uiTra /** - * This method is used to check whether the end screen loading feature is enabled or not. + * This method is used to check whether the end screen loading feature is enabled or not. */ @Override public void isEndScreenLoadingEnabled(@NonNull ApmPigeon.Result result) { @@ -447,9 +357,9 @@ public void isEnabled(@NonNull ApmPigeon.Result result) { } } - /** - * checks whether the screen loading feature is enabled. - * */ + /** + * checks whether the screen loading feature is enabled. + */ @Override public void isScreenLoadingEnabled(@NonNull ApmPigeon.Result result) { try { diff --git a/android/src/test/java/com/instabug/flutter/ApmApiTest.java b/android/src/test/java/com/instabug/flutter/ApmApiTest.java index 725d3bd98..c6842d3cb 100644 --- a/android/src/test/java/com/instabug/flutter/ApmApiTest.java +++ b/android/src/test/java/com/instabug/flutter/ApmApiTest.java @@ -1,11 +1,12 @@ package com.instabug.flutter; +import static com.instabug.apm.networking.ApmNetworkLoggerHelper.log; +import static com.instabug.flutter.util.GlobalMocks.log; import static com.instabug.flutter.util.GlobalMocks.reflected; import static com.instabug.flutter.util.MockResult.makeResult; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockConstruction; @@ -18,7 +19,8 @@ import com.instabug.apm.configuration.cp.APMFeature; import com.instabug.apm.configuration.cp.FeatureAvailabilityCallback; import com.instabug.apm.model.ExecutionTrace; -import com.instabug.apm.networking.APMNetworkLogger; +import com.instabug.apm.networking.ApmNetworkLoggerHelper; +import com.instabug.apm.networking.mapping.NetworkRequestAttributes; import com.instabug.flutter.generated.ApmPigeon; import com.instabug.flutter.modules.ApmApi; import com.instabug.flutter.util.GlobalMocks; @@ -37,10 +39,6 @@ import java.util.HashMap; import java.util.Map; -import static com.instabug.flutter.util.GlobalMocks.reflected; -import static com.instabug.flutter.util.MockResult.makeResult; -import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -221,6 +219,8 @@ public void testEndAppLaunch() { @Test public void testNetworkLogAndroid() { + final MockedStatic mApmNetworkLoggerHelper = mockStatic((ApmNetworkLoggerHelper.class)); + Map data = new HashMap<>(); String requestUrl = "https://example.com"; String requestBody = "hi"; @@ -237,6 +237,7 @@ public void testNetworkLogAndroid() { HashMap responseHeaders = new HashMap<>(); String errorDomain = "ERROR_DOMAIN"; String serverErrorMessage = "SERVER_ERROR_MESSAGE"; + String gqlQueryName = "GQL_QUERY_NAME"; data.put("url", requestUrl); data.put("requestBody", requestBody); data.put("responseBody", responseBody); @@ -254,33 +255,12 @@ public void testNetworkLogAndroid() { data.put("duration", requestDuration); data.put("serverErrorMessage", serverErrorMessage); - MockedConstruction mAPMNetworkLogger = mockConstruction(APMNetworkLogger.class); - MockedConstruction mJSONObject = mockConstruction(JSONObject.class, (mock, context) -> when(mock.toString(anyInt())).thenReturn("{}")); - api.networkLogAndroid(data); - reflected.verify(() -> MockReflected.apmNetworkLog( - requestStartTime * 1000, - requestDuration / 1000, - "{}", - requestBody, - requestBodySize, - requestMethod, - requestUrl, - requestContentType, - "{}", - responseBody, - responseBodySize, - responseCode, - responseContentType, - errorDomain, - null, - serverErrorMessage, - null - )); - - mAPMNetworkLogger.close(); - mJSONObject.close(); + mApmNetworkLoggerHelper.verify(() -> log(data)); + + mApmNetworkLoggerHelper.close(); + } @Test