diff --git a/CHANGELOG.md b/CHANGELOG.md index 0578362db..e3e3fe250 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.20.0](https://github.com/rudderlabs/rudder-sdk-android/compare/v1.19.1...v1.20.0) (2023-10-03) + + +### Features + +* adding metrics for workmanager dbEncryption and dmt ([#329](https://github.com/rudderlabs/rudder-sdk-android/issues/329)) ([d38ad43](https://github.com/rudderlabs/rudder-sdk-android/commit/d38ad433b3dded21cbe57ce4e05699232e502954)) + + +### Bug Fixes + +* added filter for rudderstack crashes ([#325](https://github.com/rudderlabs/rudder-sdk-android/issues/325)) ([b804a32](https://github.com/rudderlabs/rudder-sdk-android/commit/b804a32b5928f8a5bab081de624e245a17cc4305)) +* expose proguard rules as part of the library to ensure safer builds ([#321](https://github.com/rudderlabs/rudder-sdk-android/issues/321)) ([46a5413](https://github.com/rudderlabs/rudder-sdk-android/commit/46a54137990b171b6430de6ca8e0fc90aa26cde0)) + ### [1.19.1](https://github.com/rudderlabs/rudder-sdk-android/compare/v1.19.0...v1.19.1) (2023-09-21) diff --git a/README.md b/README.md index 0729ce4bc..a6eb2dd5b 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,60 @@ The variable `it` contains the intialized nativeSDK object. [**Registering Lotame's onSync callback**](https://github.com/rudderlabs/rudder-integration-lotame-android#register-your-onsync-callback) shows one more example of registering a callback using `onIntegrationReady`. +#### Do I need to add anything to my ProGuard rules? + +If you are using Proguard full mode to optimize your app, add the following lines to your Android ProGuard rules: + +```java +# Reporter Module +-keep class com.rudderstack.android.ruddermetricsreporterandroid.models.LabelEntity { *; } +-keep class com.rudderstack.android.ruddermetricsreporterandroid.models.MetricEntity { *; } +-keep class com.rudderstack.android.ruddermetricsreporterandroid.models.ErrorEntity { *; } + +# Required for the usage off TypeToken class in Utils.converToMap, Utils.convertToList +-keep class com.google.gson.reflect.TypeToken { *; } +-keep class * extends com.google.gson.reflect.TypeToken + +# Required for the serialization of SourceConfig once it is downloaded. +-keep class com.google.gson.internal.LinkedTreeMap { *; } +-keep class * implements java.io.Serializable { *; } +-keep class com.rudderstack.rudderjsonadapter.RudderTypeAdapter { *; } +-keep class * extends com.rudderstack.rudderjsonadapter.RudderTypeAdapter + +# Required to ensure the DefaultPersistenceProviderFactory is not removed by Proguard +# and works as expected even when the customer is not using encryption feature. +-dontwarn net.sqlcipher.Cursor +-dontwarn net.sqlcipher.database.SQLiteDatabase$CursorFactory +-dontwarn net.sqlcipher.database.SQLiteDatabase +-dontwarn net.sqlcipher.database.SQLiteOpenHelper +-keep class com.rudderstack.android.sdk.core.persistence.DefaultPersistenceProviderFactory { *; } + +# Required for the usage of annotations across reporter and web modules +-dontwarn com.fasterxml.jackson.annotation.JsonIgnore +-dontwarn com.squareup.moshi.Json +-dontwarn com.fasterxml.jackson.annotation.JsonProperty + +# Required for Device Mode Transformations +-keep class com.rudderstack.android.sdk.core.TransformationResponse { *; } +-keep class com.rudderstack.android.sdk.core.TransformationResponseDeserializer { *; } + +# to make sure that serialized name annotations are not removed by the Proguard full mode. +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +# Required for proper serialization of the custom traits and custom context +-keep class * implements com.google.gson.JsonSerializer { *; } + +# to make sure that the customContextMap, custom traits are sent in the proper format +-keepclassmembers class com.rudderstack.android.sdk.core.RudderContext { java.util.Map customContextMap; } +-keepclassmembers class com.rudderstack.android.sdk.core.RudderTraits { java.util.Map extras; } + +# Required for DBEncryption feature using SQLCipher +-keep class net.sqlcipher.** { *; } +-keep class net.sqlcipher.database.* { *; } +``` + ## Contribute We would love to see you contribute to this project. Get more information on how to contribute [**here**](./CONTRIBUTING.md). diff --git a/core/build.gradle b/core/build.gradle index 4520085b8..daef0319e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -5,7 +5,7 @@ android { defaultConfig { minSdkVersion 19 targetSdkVersion 33 -// consumerProguardFiles 'proguard-consumer-rules.pro' + consumerProguardFiles 'proguard-consumer-rules.pro' buildConfigField("String", "VERSION_NAME", "\"${VERSION_NAME}\"") buildConfigField("String", "VERSION_CODE", "\"${VERSION_CODE}\"") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -44,7 +44,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.6.0' - implementation ('com.rudderstack.android.sdk:rudderreporter:0.2.0') + implementation ('com.rudderstack.android.sdk:rudderreporter:0.3.0') // implementation(project(path: ':rudderreporter')) // implementation(project(path: ':gsonrudderadapter')) implementation 'com.rudderstack.kotlin.sdk:gsonrudderadapter:0.2.0' diff --git a/core/proguard-consumer-rules.pro b/core/proguard-consumer-rules.pro index cd6ff6a82..ddc88ea27 100644 --- a/core/proguard-consumer-rules.pro +++ b/core/proguard-consumer-rules.pro @@ -19,7 +19,41 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -# -#-keep class com.rudderstack.android.sdk.core.* { *; } -#-keep class com.rudderstack.android.sdk.core.ecomm.* { *; } -#-keep class com.rudderstack.android.sdk.core.util.* { *; } \ No newline at end of file + +# Required for the usage off TypeToken class in Utils.converToMap, Utils.convertToList +-keep class com.google.gson.reflect.TypeToken { *; } +-keep class * extends com.google.gson.reflect.TypeToken + +# Required for the serialization of SourceConfig once it is downloaded. +-keep class com.google.gson.internal.LinkedTreeMap { *; } +-keep class * implements java.io.Serializable { *; } +-keep class com.rudderstack.rudderjsonadapter.RudderTypeAdapter { *; } +-keep class * extends com.rudderstack.rudderjsonadapter.RudderTypeAdapter + +# Required to ensure the DefaultPersistenceProviderFactory is not removed by Proguard +# and works as expected even when the customer is not using encryption feature. +-dontwarn net.sqlcipher.Cursor +-dontwarn net.sqlcipher.database.SQLiteDatabase$CursorFactory +-dontwarn net.sqlcipher.database.SQLiteDatabase +-dontwarn net.sqlcipher.database.SQLiteOpenHelper +-keep class com.rudderstack.android.sdk.core.persistence.DefaultPersistenceProviderFactory { *; } + +# Required for Device Mode Transformations +-keep class com.rudderstack.android.sdk.core.TransformationResponse { *; } +-keep class com.rudderstack.android.sdk.core.TransformationResponseDeserializer { *; } + +# to make sure that serialized name annotations in model classes are not removed by the Proguard full mode. +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +# Required for proper serialization of the custom traits and custom context +-keep class * implements com.google.gson.JsonSerializer { *; } + +# to make sure that the customContextMap, custom traits are sent in the proper format +-keepclassmembers class com.rudderstack.android.sdk.core.RudderContext { java.util.Map customContextMap; } +-keepclassmembers class com.rudderstack.android.sdk.core.RudderTraits { java.util.Map extras; } + +# Required for DBEncryption feature using SQLCipher +-keep class net.sqlcipher.** { *; } +-keep class net.sqlcipher.database.* { *; } \ No newline at end of file diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java b/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java index 24f725531..760be3a5a 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/EventRepository.java @@ -95,7 +95,7 @@ public void handleMessage(Message msg) { RudderLogger.logDebug(String.format("EventRepository: constructor: %s", this.config.toString())); try { - ReportManager.addErrorMetadata(ReportManager.METADATA_SECTION_GZIP, ReportManager.METADATA_GZIP_KEY_IS_ENABLED, + ReportManager.leaveBreadcrumb(ReportManager.METADATA_SECTION_GZIP, ReportManager.METADATA_GZIP_KEY_IS_ENABLED, _config.isGzipEnabled()); // initiate RudderPreferenceManager initiatePreferenceManager(_application); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/FlushEventsWorker.java b/core/src/main/java/com/rudderstack/android/sdk/core/FlushEventsWorker.java index eb637b7bd..2953faabf 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/FlushEventsWorker.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/FlushEventsWorker.java @@ -19,15 +19,19 @@ public FlushEventsWorker( persistenceProviderFactoryClassName = params.getInputData().getString(PERSISTENCE_PROVIDER_FACTORY_CLASS_NAME_KEY); } + private void addWorkerMetrics() { + ReportManager.incrementWorkManagerInitializationCounter(1); + } + @Override public Result doWork() { + addWorkerMetrics(); RudderFlushConfig flushConfig = RudderFlushWorkManager.getRudderFlushConfig(getApplicationContext()); if (flushConfig == null) { RudderLogger.logWarn("FlushEventsWorker: doWork: RudderFlushConfig is empty, couldn't flush the events, aborting the work"); return Result.failure(); } RudderLogger.init(flushConfig.getLogLevel()); - DBPersistentManager.DbManagerParams params = new DBPersistentManager .DbManagerParams(flushConfig.isDbEncrypted(), persistenceProviderFactoryClassName, flushConfig.getEncryptionKey()); DBPersistentManager dbManager = DBPersistentManager.getInstance((Application) getApplicationContext(), params); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/ReportManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/ReportManager.java index 8c2de4b2c..53730cd4d 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/ReportManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/ReportManager.java @@ -5,18 +5,23 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RestrictTo; import com.rudderstack.android.ruddermetricsreporterandroid.Configuration; import com.rudderstack.android.ruddermetricsreporterandroid.DefaultRudderReporter; import com.rudderstack.android.ruddermetricsreporterandroid.LibraryMetadata; import com.rudderstack.android.ruddermetricsreporterandroid.Metrics; import com.rudderstack.android.ruddermetricsreporterandroid.RudderReporter; +import com.rudderstack.android.ruddermetricsreporterandroid.error.BreadcrumbType; +import com.rudderstack.android.ruddermetricsreporterandroid.error.CrashFilter; import com.rudderstack.android.ruddermetricsreporterandroid.error.ErrorClient; import com.rudderstack.android.ruddermetricsreporterandroid.metrics.LongCounter; import com.rudderstack.gsonrudderadapter.GsonAdapter; +import java.util.Collections; import java.util.Map; +@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public class ReportManager { public static final String LABEL_TYPE_OUT_OF_MEMORY = "out_of_memory"; @@ -41,6 +46,13 @@ private ReportManager() { public static final String LABEL_TYPE_SOURCE_CONFIG_URL_INVALID = "control_plane_url_invalid"; public static final String LABEL_TYPE_DATA_PLANE_URL_INVALID = "data_plane_url_invalid"; public static final String LABEL_TYPE_SOURCE_DISABLED = "source_disabled"; + public static final String LABEL_TYPE_CREATED = "created"; + public static final String LABEL_TYPE_MIGRATE_TO_ENCRYPT = "migrate_to_encrypt"; + public static final String LABEL_TYPE_MIGRATE_TO_DECRYPT = "migrate_to_decrypt"; + public static final String LABEL_TYPE_FAIL_BAD_REQUEST = "bad_request"; + public static final String LABEL_TYPE_FAIL_WRITE_KEY = "writekey_invalid"; + public static final String LABEL_TYPE_FAIL_RESOURCE_NOT_FOUND = "resource_not_found"; + public static final String LABEL_TYPE_FAIL_MAX_RETRY = "max_retries_exhausted"; // public static final String LABEL_TYPE_WRITE_KEY_INVALID = "writekey_invalid"; private static final long METRICS_UPLOAD_INTERVAL = 30_000; @@ -51,6 +63,10 @@ private ReportManager() { private static LongCounter deviceModeEventCounter = null; private static LongCounter cloudModeEventCounter = null; + private static LongCounter dmtEventSubmittedCounter = null; + private static LongCounter dmtEventSuccessResponseCounter = null; + private static LongCounter dmtEventRetryCounter = null; + private static LongCounter dmtEventAbortCounter = null; private static LongCounter deviceModeDiscardedCounter = null; private static LongCounter cloudModeUploadSuccessCounter = null; private static LongCounter cloudModeUploadAbortCounter = null; @@ -58,6 +74,9 @@ private ReportManager() { private static LongCounter sourceConfigDownloadRetryCounter = null; private static LongCounter sourceConfigDownloadSuccessCounter = null; private static LongCounter sourceConfigDownloadAbortCounter = null; + private static LongCounter dbEncryptionCounter = null; + + private static LongCounter workManagerSuccessInitializationCounter = null; private static final String EVENTS_SUBMITTED_COUNTER_TAG = "submitted_events"; @@ -65,12 +84,20 @@ private ReportManager() { private static final String DEVICE_MODE_EVENT_COUNTER_TAG = "dm_event"; private static final String DEVICE_MODE_DISCARD_COUNTER_TAG = "dm_discard"; private static final String CLOUD_MODE_EVENT_COUNTER_TAG = "cm_event"; + private static final String DMT_SUBMITTED_COUNTER_TAG = "dmt_submitted"; + private static final String DMT_RESPONSE_COUNTER_TAG = "dmt_success"; + private static final String DMT_DISCARD_COUNTER_TAG = "dmt_discard"; + private static final String DMT_RETRY_COUNTER_TAG = "dmt_retry"; private static final String CLOUD_MODE_EVENT_UPLOAD_SUCCESS_COUNTER_TAG = "cm_attempt_success"; private static final String CLOUD_MODE_EVENT_UPLOAD_ABORT_COUNTER_TAG = "cm_attempt_abort"; private static final String CLOUD_MODE_EVENT_UPLOAD_RETRY_COUNTER_TAG = "cm_attempt_retry"; private static final String SOURCE_CONFIG_DOWNLOAD_SUCCESS_COUNTER_TAG = "sc_attempt_success"; private static final String SOURCE_CONFIG_DOWNLOAD_RETRY_COUNTER_TAG = "sc_attempt_retry"; + private static final String SOURCE_CONFIG_DOWNLOAD_ABORT_COUNTER_TAG = "sc_attempt_abort"; + private static final String FLUSH_WORKER_INIT_COUNTER_TAG = "flush_worker_call"; + private static final String ENCRYPTED_DB_COUNTER_TAG = "db_encrypt"; + private static final String METRICS_URL_DEV = "https://sdk-metrics.dev-rudder.rudderlabs.com/"; private static final String METRICS_URL_PROD = "https://sdk-metrics.rudderstack.com/"; private static Metrics metrics = null; @@ -85,7 +112,7 @@ private ReportManager() { public static void initiate(@Nullable Metrics metrics, @Nullable ErrorClient errorStatsClient) { ReportManager.metrics = metrics; ReportManager.errorStatsClient = errorStatsClient; - if(metrics != null) + if (metrics != null) createCounters(metrics); } @@ -95,6 +122,7 @@ private static void createCounters(@NonNull Metrics metrics) { ReportManager.discardedCounter = metrics.getLongCounter(EVENTS_DISCARDED_COUNTER_TAG); ReportManager.deviceModeEventCounter = metrics.getLongCounter(DEVICE_MODE_EVENT_COUNTER_TAG); ReportManager.cloudModeEventCounter = metrics.getLongCounter(CLOUD_MODE_EVENT_COUNTER_TAG); + ReportManager.dmtEventSubmittedCounter = metrics.getLongCounter(DMT_SUBMITTED_COUNTER_TAG); ReportManager.deviceModeDiscardedCounter = metrics.getLongCounter(DEVICE_MODE_DISCARD_COUNTER_TAG); cloudModeUploadSuccessCounter = metrics.getLongCounter(CLOUD_MODE_EVENT_UPLOAD_SUCCESS_COUNTER_TAG); @@ -104,6 +132,14 @@ private static void createCounters(@NonNull Metrics metrics) { sourceConfigDownloadRetryCounter = metrics.getLongCounter(SOURCE_CONFIG_DOWNLOAD_RETRY_COUNTER_TAG); sourceConfigDownloadSuccessCounter = metrics.getLongCounter(SOURCE_CONFIG_DOWNLOAD_SUCCESS_COUNTER_TAG); sourceConfigDownloadAbortCounter = metrics.getLongCounter(SOURCE_CONFIG_DOWNLOAD_ABORT_COUNTER_TAG); + dbEncryptionCounter = metrics.getLongCounter(ENCRYPTED_DB_COUNTER_TAG); + + dmtEventSubmittedCounter = metrics.getLongCounter(DMT_SUBMITTED_COUNTER_TAG); + dmtEventSuccessResponseCounter = metrics.getLongCounter(DMT_RESPONSE_COUNTER_TAG); + dmtEventRetryCounter = metrics.getLongCounter(DMT_RETRY_COUNTER_TAG); + dmtEventAbortCounter = metrics.getLongCounter(DMT_DISCARD_COUNTER_TAG); + + workManagerSuccessInitializationCounter = metrics.getLongCounter(FLUSH_WORKER_INIT_COUNTER_TAG); } private static void incrementCounter(LongCounter counter, int value, Map attributes) { @@ -134,7 +170,7 @@ static void enableStatsCollection(Application application, String writeKey, RudderLogger.logDebug("EventRepository: Metrics collection is not initialized"); return; } - if(!(statsCollection.getMetrics().isEnabled() || statsCollection.getErrors().isEnabled())) { + if (!(statsCollection.getMetrics().isEnabled() || statsCollection.getErrors().isEnabled())) { RudderLogger.logDebug("EventRepository: Stats collection is not enabled: Shutting down Stats Reporter"); rudderReporter.shutdown(); return; @@ -157,6 +193,7 @@ private static void checkAndUpdateMetricsCollection(boolean isMetricsEnabled) { } metrics.enable(isMetricsEnabled); } + @SuppressWarnings("ConstantConditions") private static void checkAndUpdateErrorsCollection(boolean isErrorsEnabled) { if (!isStatsReporterAvailable()) @@ -173,12 +210,9 @@ private static void checkAndUpdateErrorsCollection(boolean isErrorsEnabled) { private static void initiateRudderReporter(Context context, @Nullable String writeKey, boolean isMetricsEnabled, boolean isErrorsEnabled) { RudderLogger.logDebug("EventRepository: Creating RudderReporter isMetricsEnabled: " + isMetricsEnabled + " isErrorsEnabled: " + isErrorsEnabled); - String writeKeyOrBlank = writeKey == null ? "" : writeKey; if (rudderReporter == null) { rudderReporter = new DefaultRudderReporter(context, METRICS_URL_PROD, - new Configuration(new LibraryMetadata( - BuildConfig.LIBRARY_PACKAGE_NAME, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, writeKeyOrBlank - )), new GsonAdapter(), isMetricsEnabled, isErrorsEnabled); + getStatsConfig(writeKey), new GsonAdapter(), isMetricsEnabled, isErrorsEnabled); rudderReporter.getSyncer().startScheduledSyncs(METRICS_UPLOAD_INTERVAL, true, METRICS_FLUSH_COUNT); //we default to null if metrics or errors are not enabled @@ -187,6 +221,15 @@ private static void initiateRudderReporter(Context context, @Nullable String wri } } + private static Configuration getStatsConfig(@Nullable String writeKey) { + String writeKeyOrBlank = writeKey == null ? "" : writeKey; + Configuration config = new Configuration(new LibraryMetadata( + BuildConfig.LIBRARY_PACKAGE_NAME, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, writeKeyOrBlank + )); + config.setCrashFilter(CrashFilter.generateWithKeyWords(Collections.singletonList("rudderstack"))); + return config; + } + static void incrementMessageCounter(int value, Map attributes) { incrementCounter(messageCounter, value, attributes); } @@ -258,20 +301,45 @@ static void incrementCloudModeUploadAbortCounter(int value) { static void incrementCloudModeUploadAbortCounter(int value, Map attributes) { incrementCounter(cloudModeUploadAbortCounter, value, attributes); } + static void incrementDMTSubmittedCounter(int value, Map attributes) { + incrementCounter(dmtEventSubmittedCounter, value, attributes); + } + static void incrementDMTEventSuccessResponseCounter(int value, Map attributes) { + incrementCounter(dmtEventSuccessResponseCounter, value, attributes); + } + static void incrementDMTRetryCounter(int value) { + incrementCounter(dmtEventRetryCounter, value); + } + + static void incrementDMTErrorCounter(int value, Map attributes) { + incrementCounter(dmtEventAbortCounter, value, attributes); + } + + @RestrictTo(RestrictTo.Scope.LIBRARY) + public static void incrementDbEncryptionCounter(int value, Map attributes) { + incrementCounter(dbEncryptionCounter, value, attributes); + } + + static void incrementWorkManagerInitializationCounter(int value) { + incrementCounter(workManagerSuccessInitializationCounter, value); + } public static void reportError(Throwable throwable) { if (errorStatsClient != null) { errorStatsClient.notify(throwable); } } - public static void addErrorMetadata(String section, Map value) { + + public static void leaveBreadcrumb(String message, Map value) { if (errorStatsClient != null) { - errorStatsClient.addMetadata(section, value); + errorStatsClient.leaveBreadcrumb(message, value, BreadcrumbType.MANUAL); } } - public static void addErrorMetadata(String section, String key, Object value) { + + public static void leaveBreadcrumb(String message, String key, Object value) { if (errorStatsClient != null) { - errorStatsClient.addMetadata(section, key, value); + errorStatsClient.leaveBreadcrumb(message, Collections.singletonMap(key, value), + BreadcrumbType.MANUAL); } } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java index eaa4c7cf6..7fb892e16 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderDeviceModeTransformationManager.java @@ -1,5 +1,10 @@ package com.rudderstack.android.sdk.core; +import static com.rudderstack.android.sdk.core.RudderNetworkManager.NetworkResponses; +import static com.rudderstack.android.sdk.core.RudderNetworkManager.RequestMethod; +import static com.rudderstack.android.sdk.core.RudderNetworkManager.Result; +import static com.rudderstack.android.sdk.core.RudderNetworkManager.addEndPoint; + import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.rudderstack.android.sdk.core.util.MessageUploadLock; @@ -7,6 +12,7 @@ import com.rudderstack.android.sdk.core.util.RudderTraitsSerializer; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -15,11 +21,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import static com.rudderstack.android.sdk.core.RudderNetworkManager.Result; -import static com.rudderstack.android.sdk.core.RudderNetworkManager.NetworkResponses; -import static com.rudderstack.android.sdk.core.RudderNetworkManager.RequestMethod; -import static com.rudderstack.android.sdk.core.RudderNetworkManager.addEndPoint; - public class RudderDeviceModeTransformationManager { private final DBPersistentManager dbManager; @@ -98,24 +99,34 @@ public void run() { private void createMessageIdTransformationRequestMap() { for (int i = 0; i < messageIds.size(); i++) { + RudderMessage message = gson.fromJson(messages.get(i), RudderMessage.class); + reportMessageSubmittedMetric(message); messageIdTransformationRequestMap.put(messageIds.get(i), message); } } + private void reportMessageSubmittedMetric(RudderMessage message) { + ReportManager.incrementDMTSubmittedCounter(1, + Collections.singletonMap(ReportManager.LABEL_TYPE, message.getType())); + } + private boolean handleTransformationResponse(Result result, TransformationRequest transformationRequest) { if (result.status == NetworkResponses.WRITE_KEY_ERROR) { + reportWriteKeyErrorMetric(); RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Wrong WriteKey. Aborting"); return true; } else if (result.status == NetworkResponses.NETWORK_UNAVAILABLE) { RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Network unavailable. Aborting"); return true; } else if (result.status == NetworkResponses.BAD_REQUEST) { + reportBadRequestMetric(); RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Bad request, dumping back the original events to the factories"); dumpOriginalEvents(transformationRequest); } else if (result.status == NetworkResponses.ERROR) { handleError(transformationRequest); } else if (result.status == NetworkResponses.RESOURCE_NOT_FOUND) { // dumping back the original messages itself to the factories as transformation feature is not enabled + reportResourceNotFoundMetric(); handleResourceNotFound(transformationRequest); } else { handleSuccess(result); @@ -123,12 +134,29 @@ private boolean handleTransformationResponse(Result result, TransformationReques return false; } + private void reportResourceNotFoundMetric() { + ReportManager.incrementDMTErrorCounter(1, + Collections.singletonMap(ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_FAIL_RESOURCE_NOT_FOUND)); + } + + private void reportBadRequestMetric() { + ReportManager.incrementDMTErrorCounter(1, + Collections.singletonMap(ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_FAIL_BAD_REQUEST)); + } + + private void reportWriteKeyErrorMetric() { + ReportManager.incrementDMTErrorCounter(1, + Collections.singletonMap(ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_FAIL_WRITE_KEY)); + } + private void handleError(TransformationRequest transformationRequest) { int delay = Math.min((1 << retryCount) * 500, MAX_DELAY); // Exponential backoff if (retryCount++ == MAX_RETRIES) { retryCount = 0; + reportMaxRetryExceededMetric(); dumpOriginalEvents(transformationRequest); } else { + incrementRetryCountMetric(); RudderLogger.logDebug("DeviceModeTransformationManager: TransformationProcessor: Retrying in " + delay + "s"); try { Thread.sleep(delay); @@ -140,6 +168,15 @@ private void handleError(TransformationRequest transformationRequest) { } } + private void reportMaxRetryExceededMetric() { + ReportManager.incrementDMTErrorCounter(1, + Collections.singletonMap(ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_FAIL_MAX_RETRY)); + } + + private void incrementRetryCountMetric() { + ReportManager.incrementDMTRetryCounter(1); + } + private void dumpOriginalEvents(TransformationRequest transformationRequest) { deviceModeSleepCount = 0; rudderDeviceModeManager.dumpOriginalEvents(transformationRequest, true); @@ -156,6 +193,7 @@ private void handleSuccess(Result result) { deviceModeSleepCount = 0; try { TransformationResponse transformationResponse = gson.fromJson(result.response, TransformationResponse.class); + incrementDmtSuccessMetric(transformationResponse); rudderDeviceModeManager.dumpTransformedEvents(transformationResponse); completeDeviceModeEventProcessing(); } catch (Exception e) { @@ -164,6 +202,26 @@ private void handleSuccess(Result result) { } } + private void incrementDmtSuccessMetric(TransformationResponse transformationResponse) { + if(transformationResponse == null || transformationResponse.transformedBatch == null) { + return; + } + for (TransformationResponse.TransformedDestination transformedDestination : transformationResponse.transformedBatch) { + if(transformedDestination.payload == null) { + continue; + } + for (TransformationResponse.TransformedEvent transformedEvent: + transformedDestination.payload) { + if(transformedEvent.event == null) { + continue; + } + ReportManager.incrementDMTEventSuccessResponseCounter(1, + Collections.singletonMap(ReportManager.LABEL_TYPE, transformedEvent.event.getType())); + } + + } + } + private void completeDeviceModeEventProcessing() { RudderLogger.logDebug(String.format(Locale.US, "DeviceModeTransformationManager: TransformationProcessor: Updating status as DEVICE_MODE_PROCESSING DONE for events %s", messageIds)); dbManager.markDeviceModeDone(messageIds); @@ -184,7 +242,7 @@ private TransformationRequest createTransformationRequestPayload() { for (int i = 0; i < messageIds.size(); i++) { // For each message get the list of destinationIds for which transformation is enabled RudderMessage message = messageIdTransformationRequestMap.get(messageIds.get(i)); - List destinationIds = this.rudderDeviceModeManager.getTransformationEnabledDestinationIds(message); + List destinationIds = this.rudderDeviceModeManager.getTransformationEnabledDestinationIds(message); TransformationRequest.TransformationRequestEvent transformationRequestEvent = new TransformationRequest.TransformationRequestEvent(messageIds.get(i), message, destinationIds); transformationRequestEvents.add(transformationRequestEvent); diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/RudderNetworkManager.java b/core/src/main/java/com/rudderstack/android/sdk/core/RudderNetworkManager.java index 9f5267990..e917bc282 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/RudderNetworkManager.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/RudderNetworkManager.java @@ -185,7 +185,7 @@ private HttpURLConnection updateHttpConnection(String requestURL, RequestMethod return updateHttpConnection(httpConnection, requestMethod, requestPayload, isDMTRequest, null, null); } catch (Exception ex) { - ReportManager.reportError(ex); +// ReportManager.reportError(ex); RudderLogger.logError("RudderNetworkManager: sendNetworkRequest: Exception occurred while creating HttpURLConnection" + ex.getLocalizedMessage()); return null; } @@ -238,7 +238,7 @@ private HttpURLConnection updateHttpConnectionForPostRequest(HttpURLConnection h osw.flush(); return httpConnection; } catch (Exception ex) { - ReportManager.reportError(ex); +// ReportManager.reportError(ex); RudderLogger.logError("RudderNetworkManager: updateHttpConnection: Error while updating the http connection" + ex.getLocalizedMessage()); return null; } diff --git a/core/src/main/java/com/rudderstack/android/sdk/core/persistence/DefaultPersistenceProvider.java b/core/src/main/java/com/rudderstack/android/sdk/core/persistence/DefaultPersistenceProvider.java index bb2bf0f78..0760e9cc4 100644 --- a/core/src/main/java/com/rudderstack/android/sdk/core/persistence/DefaultPersistenceProvider.java +++ b/core/src/main/java/com/rudderstack/android/sdk/core/persistence/DefaultPersistenceProvider.java @@ -13,6 +13,7 @@ import net.sqlcipher.database.SQLiteDatabase; import java.io.File; +import java.util.Collections; public class DefaultPersistenceProvider implements PersistenceProvider { @@ -49,11 +50,14 @@ static class ProviderParams { public Persistence get(Persistence.DbCreateListener dbCreateListener) { if (!params.isEncrypted || params.encryptionKey == null || params.encryptedDbName == null) { - ReportManager.addErrorMetadata(ReportManager.METADATA_SECTION_PERSISTENCE, ReportManager.METADATA_PERSISTENCE_KEY_IS_ENCRYPTED, + ReportManager.leaveBreadcrumb(ReportManager.METADATA_SECTION_PERSISTENCE, ReportManager.METADATA_PERSISTENCE_KEY_IS_ENCRYPTED, true); return getDefaultPersistence(dbCreateListener); } else { - ReportManager.addErrorMetadata(ReportManager.METADATA_SECTION_PERSISTENCE, ReportManager.METADATA_PERSISTENCE_KEY_IS_ENCRYPTED, + ReportManager.incrementDbEncryptionCounter(1, Collections.singletonMap( + ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_CREATED + )); + ReportManager.leaveBreadcrumb(ReportManager.METADATA_SECTION_PERSISTENCE, ReportManager.METADATA_PERSISTENCE_KEY_IS_ENCRYPTED, false); return getEncryptedPersistence(dbCreateListener); } @@ -130,6 +134,9 @@ private void deleteEncryptedDb() { } private void migrateToDefaultDatabase(File databasePath) { + ReportManager.incrementDbEncryptionCounter(1, Collections.singletonMap( + ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_MIGRATE_TO_DECRYPT + )); File encryptedDb = application.getDatabasePath(params.encryptedDbName); String encryptedPath = encryptedDb.getAbsolutePath(); @@ -152,6 +159,10 @@ private void deleteFile(File encryptedDb) { private void migrateToEncryptedDatabase(File encryptedDbPath) { + ReportManager.incrementDbEncryptionCounter(1, Collections.singletonMap( + ReportManager.LABEL_TYPE, ReportManager.LABEL_TYPE_MIGRATE_TO_ENCRYPT + )); + SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(encryptedDbPath.getAbsolutePath(), params.encryptionKey, null); database.close(); File decryptedDb = application.getDatabasePath(params.dbName); diff --git a/gradle.properties b/gradle.properties index 7ea5502d9..136d85233 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,8 +5,8 @@ android.enableJetifier=true android.enableR8.fullMode=true kotlin.code.style=official GROUP=com.rudderstack.android.sdk -VERSION_CODE=18 -VERSION_NAME=1.19.1 +VERSION_CODE=19 +VERSION_NAME=1.20.0 POM_NAME=Rudderstack SDK for android POM_DESCRIPTION=Rudderstack SDK for android POM_ARTIFACT_ID=core diff --git a/package.json b/package.json index c966162b2..9be707432 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.19.1", + "version": "1.20.0", "dependencies": { "properties-reader": "^2.2.0" } diff --git a/sample-kotlin/build.gradle b/sample-kotlin/build.gradle index d7b77ef15..7c8ce75ab 100644 --- a/sample-kotlin/build.gradle +++ b/sample-kotlin/build.gradle @@ -30,7 +30,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "DATA_PLANE_URL", properties.getProperty('dataplaneUrl', 'https://api.rudderstack.com')) buildConfigField("String", "CONTROL_PLANE_URL", properties.getProperty('controlplaneUrl', "\"https://api.rudderstack.com\"")) - buildConfigField("String", "WRITE_KEY", properties.getProperty('writeKey', '')) + buildConfigField("String", "WRITE_KEY", properties.getProperty('writeKey', "\"\"")) } buildTypes { debug { @@ -63,6 +63,7 @@ dependencies { // Rudder Android Core SDK implementation project(':core') +// implementation 'com.rudderstack.android.sdk:core:1.19.1' implementation 'com.google.code.gson:gson:2.8.6' implementation "androidx.work:work-runtime:2.7.1"