diff --git a/.github/workflows/browerstack.yml b/.github/workflows/browerstack.yml index cd85f3afa..a34ddb1f1 100644 --- a/.github/workflows/browerstack.yml +++ b/.github/workflows/browerstack.yml @@ -36,6 +36,14 @@ jobs: run: | git config --global url."https://${{ secrets.GH_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" + - name: Setup Sentry CLI + uses: mathieu-bour/setup-sentry-cli@v1 + with: + version: latest + token: ${{ SECRETS.SENTRY_TOKEN }} # from GitHub secrets + organization: getlantern + project: android + - name: Setup JDK 17 uses: actions/setup-java@v3 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1e0f394c..dbc54fd5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,8 +8,6 @@ permissions: env: GOPRIVATE: github.com/getlantern S3_BUCKET: lantern - DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }} - DATADOG_SITE: datadoghq.eu jobs: set-version: runs-on: @@ -84,6 +82,14 @@ jobs: run: | git config --global url."https://${{ secrets.GH_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" + - name: Setup Sentry CLI + uses: mathieu-bour/setup-sentry-cli@v1 + with: + version: latest + token: ${{ SECRETS.SENTRY_TOKEN }} # from GitHub secrets + organization: getlantern + project: android + - name: Setup JDK 11 uses: actions/setup-java@v3 with: @@ -118,16 +124,9 @@ jobs: fileDir: './android/app' encodedString: ${{ secrets.KEYSTORE }} - - name: Install Datadog CI - run: npm install -g @datadog/datadog-ci - - name: Build Android installers run: make package-android env: - DD_APPLICATION_ID: ${{ secrets.DD_APPLICATION_ID }} - DD_CLIENT_TOKEN: ${{ secrets.DD_CLIENT_TOKEN }} - DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }} - DATADOG_SITE: datadoghq.eu INTERSTITIAL_AD_UNIT: "${{ secrets.INTERSTITIAL_AD_UNIT_ID }}" VERSION: "${{ env.version }}" diff --git a/Makefile b/Makefile index 3ca47d731..f5d3497a6 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,6 @@ ADB := $(call get-command,adb) OPENSSL := $(call get-command,openssl) GMSAAS := $(call get-command,gmsaas) SENTRY := $(call get-command,sentry-cli) -DATADOGCI := $(call get-command,datadog-ci) BASE64 := $(call get-command,base64) GIT_REVISION_SHORTCODE := $(shell git rev-parse --short HEAD) @@ -90,7 +89,6 @@ PROD_BASE_NAME ?= $(INSTALLER_NAME) ## secrets Keys INTERSTITIAL_AD_UNIT=ca-app-pub-2685698271254859/9922829329 ## vault secrets -VAULT_DD_SECRETS_PATH ?= secret/apps/datadog/android VAULT_ADS_SECRETS_PATH ?= secret/googleAds ## vault keys @@ -245,10 +243,6 @@ require-magick: require-sentry: @if [[ -z "$(SENTRY)" ]]; then echo 'Missing "sentry-cli" command. See sentry.io for installation instructions.'; exit 1; fi -.PHONY: require-datadog-ci -require-datadog-ci: - @if [[ -z "$(DATADOGCI)" ]]; then echo 'Missing "datadog-ci" command. See https://www.npmjs.com/package/@datadog/datadog-ci for installation instructions.'; exit 1; fi - release-autoupdate: require-version @TAG_COMMIT=$$(git rev-list --abbrev-commit -1 $(TAG)) && \ if [[ -z "$$TAG_COMMIT" ]]; then \ @@ -299,7 +293,7 @@ do-android-debug: $(MOBILE_SOURCES) $(MOBILE_ANDROID_LIB) echo "Value of DART_DEFINES is: $$DART_DEFINES" && \ CI="$$CI" && \ echo "Value of CI is: $$CI" && \ - $(GRADLE) -Pdart-defines="$$DART_DEFINES" -PlanternVersion=$(DEBUG_VERSION) -PddClientToken=$$DD_CLIENT_TOKEN -PddApplicationID=$$DD_APPLICATION_ID \ + $(GRADLE) -Pdart-defines="$$DART_DEFINES" -PlanternVersion=$(DEBUG_VERSION) \ -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) \ -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) \ -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) \ @@ -314,7 +308,7 @@ $(MOBILE_DEBUG_APK): $(MOBILE_SOURCES) $(GO_SOURCES) make do-android-debug && \ cp $(MOBILE_ANDROID_DEBUG) $(MOBILE_DEBUG_APK) -$(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-datadog-ci +$(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-sentry echo $(MOBILE_ANDROID_LIB) && \ mkdir -p ~/.gradle && \ ln -fs $(MOBILE_DIR)/gradle.properties . && \ @@ -328,13 +322,12 @@ $(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) req -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) \ -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) \ -PversionCode=$(VERSION_CODE) -PdevelopmentMode=$(DEVELOPMENT_MODE) -b $(MOBILE_DIR)/app/build.gradle assembleProdSideload && \ - DATADOG_API_KEY=4901456bb88bbf1dc7799eab7d4f71ae DATADOG_SITE=datadoghq.eu datadog-ci flutter-symbols upload --service-name lantern-android --dart-symbols-location build/app/intermediates/merged_native_libs/prodSideload/out/lib \ - --android-mapping-location build/app/outputs/mapping/prodSideload/mapping.txt --android-mapping --ios-dsyms && \ + sentry-cli upload-dif --wait -o getlantern -p android build/app/intermediates/merged_native_libs/prodSideload/out/lib && \ cp $(MOBILE_ANDROID_RELEASE) $(MOBILE_RELEASE_APK) && \ cat $(MOBILE_RELEASE_APK) | bzip2 > lantern_update_android_arm.bz2 -$(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-datadog-ci +$(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-sentry @mkdir -p ~/.gradle && \ ln -fs $(MOBILE_DIR)/gradle.properties . && \ COUNTRY="$$COUNTRY" && \ @@ -344,6 +337,7 @@ $(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require- $(GRADLE) -PlanternVersion=$$VERSION -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" \ -PddClientToken=$(DD_CLIENT_TOKEN) -PddApplicationID=$(DD_APPLICATION_ID) -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) \ -Pcountry=$(COUNTRY) -PplayVersion=true -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -b $(MOBILE_DIR)/app/build.gradle bundlePlay && \ + sentry-cli upload-dif --wait -o getlantern -p android build/app/intermediates/merged_native_libs/prodPlay/out/lib && \ cp $(MOBILE_ANDROID_BUNDLE) $(MOBILE_BUNDLE) android-debug: $(MOBILE_DEBUG_APK) diff --git a/android/app/build.gradle b/android/app/build.gradle index 418f9fde8..45a8c0cd6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -6,7 +6,7 @@ plugins { id 'kotlin-parcelize' id 'kotlin-kapt' id 'com.google.protobuf' - id("com.datadoghq.dd-sdk-android-gradle-plugin") version "1.10.0" + id "io.sentry.android.gradle" version "3.13.0" } def localProperties = new Properties() @@ -180,8 +180,6 @@ android { buildConfigField "boolean", "STICKY_CONFIG", getBoolean("stickyConfig") buildConfigField "boolean", "STAGING", getBoolean("useStaging") buildConfigField "boolean", "PLAY_VERSION", getBoolean("playVersion") - buildConfigField "String", "DD_CLIENT_TOKEN", ddClientToken() - buildConfigField "String", "DD_APPLICATION_ID", ddApplicationID() buildConfigField "String", "COUNTRY", userCountry() buildConfigField "String", "PAYMENT_PROVIDER", paymentProvider() buildConfigField "String", "PRO_SERVER_URL", proServerUrl() @@ -323,22 +321,6 @@ def getInt(name) { return value.toInteger() } -def ddApplicationID() { - def value = project.getProperties().get("ddApplicationID") - if (value == null || !value?.trim()) { - return "\"\"" - } - return String.format("\"%s\"", value) -} - -def ddClientToken() { - def value = project.getProperties().get("ddClientToken") - if (value == null || !value?.trim()) { - return "\"\"" - } - return String.format("\"%s\"", value) -} - def userCountry() { def value = project.getProperties().get("country") if (value == null || !value?.trim()) { @@ -473,3 +455,32 @@ dependencies { } apply plugin: 'com.google.gms.google-services' + +sentry { + + // Disables or enables the handling of Proguard mapping for Sentry. + // If enabled the plugin will generate a UUID and will take care of + // uploading the mapping to Sentry. If disabled, all the logic + // related to proguard mapping will be excluded. + // Default is enabled. + includeProguardMapping = true + + + // Whether the plugin should attempt to auto-upload the mapping file to Sentry or not. + // If disabled the plugin will run a dry-run and just generate a UUID. + // The mapping file has to be uploaded manually via sentry-cli in this case. + // Default is enabled. + autoUploadProguardMapping = true + + // Disables or enables the automatic configuration of Native Symbols + // for Sentry. This executes sentry-cli automatically so + // you don't need to do it manually. + // Default is disabled. + uploadNativeSymbols = true + + // Does or doesn't include the source code of native code for Sentry. + // This executes sentry-cli with the --include-sources param. automatically so + // you don't need to do it manually. + // Default is disabled. + includeNativeSources = false +} diff --git a/android/app/libs/liblantern-all.aar b/android/app/libs/liblantern-all.aar index ad352be94..fd4be06b0 100644 --- a/android/app/libs/liblantern-all.aar +++ b/android/app/libs/liblantern-all.aar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c50fcd8ea9c441c0a81ee74117613ad6cbe32d90a1ed6dfba06e6207b8b71fe2 -size 78316381 +oid sha256:091cd2629b6eff2c058dcdf883fb75fa200f00a7cf768e6eae0b2d6b064f926f +size 77696936 diff --git a/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt b/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt index 58841b846..1c9292d3a 100644 --- a/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt +++ b/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt @@ -24,7 +24,6 @@ import io.lantern.model.BaseModel import io.lantern.model.Vpn import org.getlantern.lantern.BuildConfig import org.getlantern.lantern.LanternApp -import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.model.Bandwidth import org.getlantern.lantern.model.Stats import org.getlantern.lantern.model.Utils @@ -386,7 +385,6 @@ abstract class SessionManager(application: Application) : Session { override fun setCountry(country: String) { prefs.edit().putString(GEO_COUNTRY_CODE, country).apply() - Datadog.setCountry(country) } private val hasUpdatedStats = AtomicBoolean() diff --git a/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt b/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt index 1ad9ca633..afdfa7c53 100644 --- a/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt +++ b/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt @@ -23,7 +23,6 @@ import org.getlantern.lantern.MainActivity import org.getlantern.lantern.R import org.getlantern.lantern.activity.FreeKassaActivity_ import org.getlantern.lantern.activity.WebViewActivity_ -import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternHttpClient.ProCallback import org.getlantern.lantern.model.LanternHttpClient.ProUserCallback @@ -182,10 +181,6 @@ class SessionModel( } } - "trackUserAction" -> { - Datadog.trackUserClick(call.argument("message")!!) - } - "acceptTerms" -> { LanternApp.getSession().acceptTerms() } diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt b/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt index 730fea353..65864e666 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/LanternApp.kt @@ -3,7 +3,6 @@ package org.getlantern.lantern import android.app.Application import android.content.Context import android.os.StrictMode -import android.util.Log import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDex import org.getlantern.lantern.model.InAppBilling @@ -11,7 +10,7 @@ import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternSessionManager import org.getlantern.lantern.util.debugOnly import org.getlantern.lantern.util.LanternProxySelector -import org.getlantern.mobilesdk.Logger +import org.getlantern.lantern.util.SentryUtil import org.getlantern.mobilesdk.util.HttpClient open class LanternApp : Application() { @@ -40,13 +39,14 @@ open class LanternApp : Application() { override fun onCreate() { super.onCreate() + SentryUtil.enableGoPanicEnrichment(this) // Necessary to locate a back arrow resource we use from the // support library. See http://stackoverflow.com/questions/37615470/support-library-vectordrawable-resourcesnotfoundexception AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) appContext = applicationContext session = LanternSessionManager(this) - + LanternProxySelector(session) if (session.isPlayVersion) inAppBilling = InAppBilling(this) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt index c61b116b1..5791393db 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt @@ -25,7 +25,6 @@ import io.lantern.model.VpnModel import kotlinx.coroutines.* import okhttp3.Response import org.getlantern.lantern.activity.WebViewActivity_ -import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.event.EventManager import org.getlantern.lantern.model.AccountInitializationStatus import org.getlantern.lantern.model.Bandwidth @@ -79,7 +78,6 @@ class MainActivity : override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { val start = System.currentTimeMillis() super.configureFlutterEngine(flutterEngine) - FlutterEngineCache.getInstance().put("datadoghq_engine", flutterEngine) messagingModel = MessagingModel(this, flutterEngine) vpnModel = VpnModel(this, flutterEngine, ::switchLantern) sessionModel = SessionModel(this, flutterEngine) @@ -89,7 +87,6 @@ class MainActivity : eventManager = object : EventManager("lantern_event_channel", flutterEngine) { override fun onListen(event: Event) { if (LanternApp.getSession().lanternDidStart()) { - flutterNavigation.invokeMethod("initDatadog", null) fetchLoConf() Logger.debug( TAG, @@ -305,7 +302,7 @@ class MainActivity : private fun updateUserData() { lanternClient.userData(object : ProUserCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Datadog.addError("Unable to fetch user data: $error", throwable) + Logger.error(TAG, "Unable to fetch user data: $error", throwable) } override fun onSuccess(response: Response, user: ProUser?) { @@ -330,7 +327,7 @@ class MainActivity : private fun updatePlans() { lanternClient.getPlans(object : PlansCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Datadog.addError("Unable to fetch user plans: $error", throwable) + Logger.error(TAG, "Unable to fetch user plans: $error", throwable) } override fun onSuccess(proPlans: Map) { @@ -347,7 +344,7 @@ class MainActivity : private fun updatePaymentMethods() { lanternClient.plansV3(object : PlansV3Callback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Datadog.addError("Unable to fetch payment methods: $error", throwable) + Logger.error(TAG, "Unable to fetch payment methods: $error", throwable) } override fun onSuccess( diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/datadog/Datadog.kt b/android/app/src/main/kotlin/org/getlantern/lantern/datadog/Datadog.kt deleted file mode 100644 index c98085d2f..000000000 --- a/android/app/src/main/kotlin/org/getlantern/lantern/datadog/Datadog.kt +++ /dev/null @@ -1,151 +0,0 @@ -package org.getlantern.lantern.datadog - -import android.util.Log -import com.datadog.android.DatadogSite -import com.datadog.android.core.configuration.BatchSize -import com.datadog.android.core.configuration.Configuration -import com.datadog.android.core.configuration.Credentials -import com.datadog.android.core.configuration.UploadFrequency -import com.datadog.android.privacy.TrackingConsent -import com.datadog.android.rum.GlobalRum -import com.datadog.android.rum.RumActionType -import com.datadog.android.rum.RumErrorSource -import com.datadog.android.rum.RumMonitor -import com.datadog.android.rum.tracking.ActivityViewTrackingStrategy -import org.getlantern.lantern.LanternApp -import org.getlantern.mobilesdk.Logger -import java.net.InetSocketAddress -import java.net.Proxy -import java.net.URI -import java.util.concurrent.atomic.AtomicBoolean -import com.datadog.android.Datadog as DatadogMain - -object Datadog { - private val tracedHosts = - listOf( - "datadoghq.eu", - "127.0.0.1", - "iantem.io", - "getlantern.org", - "getiantem.org", - "lantern.io", - ) - private val initialized = AtomicBoolean() - private lateinit var datadogConfig: Configuration - - fun initialize() { - if (initialized.get()) return - - DatadogMain.setVerbosity(Log.VERBOSE) - datadogConfig = createDatadogConfiguration() - - val datadogCredentials = - Credentials( - clientToken = "puba617ab01333a95a25a9d3709f04e1654", - envName = "prod", - rumApplicationId = "f8eabf3c-5db3-4f7e-8e6a-5a72433b46d2", - variant = "release", - serviceName = "lantern-android", - ) - - DatadogMain.initialize( - LanternApp.getAppContext(), - credentials = datadogCredentials, - configuration = datadogConfig, - TrackingConsent.GRANTED, - ) - - DatadogMain.setUserInfo( - id = LanternApp.getSession().userId().toString(), - ) - - GlobalRum.registerIfAbsent { - RumMonitor.Builder().build() - } - val session = LanternApp.getSession() - setCountry(session.countryCode) - initialized.set(true) - - // For some reason, sessions don't show up in DataDog RUM until we register a user action - // of some sort. So, here we fire the custom action "started" to get data to start flowing. - GlobalRum.get().addUserAction(RumActionType.CUSTOM, "started", emptyMap()) - } - - fun setCountry(country: String) { - GlobalRum.addAttribute("lantern.country_code", country) - } - - fun addError( - message: String, - throwable: Throwable? = null, - attributes: Map = emptyMap(), - ) { - Logger.e(TAG, message, throwable) - GlobalRum.get().addError(message, RumErrorSource.SOURCE, throwable, attributes) - } - - // trackUserAction is used to track specific user actions (such as taps, clicks, and scrolls) - // with RumMonitor - private fun trackUserAction( - actionType: RumActionType, - name: String, - actionAttributes: Map = emptyMap(), - ) { - GlobalRum.get().addUserAction(actionType, name, actionAttributes) - } - - // trackUserClick is used to track user clicks with RumMonitor - fun trackUserClick( - name: String, - actionAttributes: Map = emptyMap(), - ) { - trackUserAction(RumActionType.CLICK, name, actionAttributes) - } - - // trackUserTap is used to track user taps with RumMonitor - fun trackUserTap( - name: String, - actionAttributes: Map = emptyMap(), - ) { - trackUserAction(RumActionType.TAP, name, actionAttributes) - } - - private fun createDatadogConfiguration(): Configuration { - val session = LanternApp.getSession() - val hTTPAddr = session.hTTPAddr - val uri = URI("http://" + hTTPAddr) - return Configuration.Builder( - logsEnabled = true, - tracesEnabled = true, - crashReportsEnabled = true, - rumEnabled = true, - ) - .setBatchSize(BatchSize.SMALL) - .setProxy( - Proxy( - Proxy.Type.HTTP, - InetSocketAddress( - "127.0.0.1", - uri.getPort(), - ), - ), - null, - ) - .sampleRumSessions(100f) - .setUploadFrequency(UploadFrequency.FREQUENT) - .useSite(DatadogSite.EU1) - .trackBackgroundRumEvents(true) - .trackInteractions() - .trackLongTasks() - .setFirstPartyHosts(tracedHosts) - .useViewTrackingStrategy( - ActivityViewTrackingStrategy( - trackExtras = false, - componentPredicate = FlutterExcludingComponentPredicate(), - ), - ) - .build() - } - - private val TAG = Datadog::class.java.name -} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/datadog/FlutterExcludingComponentPredicate.kt b/android/app/src/main/kotlin/org/getlantern/lantern/datadog/FlutterExcludingComponentPredicate.kt deleted file mode 100644 index 49d227731..000000000 --- a/android/app/src/main/kotlin/org/getlantern/lantern/datadog/FlutterExcludingComponentPredicate.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.getlantern.lantern.datadog - -import android.app.Activity -import com.datadog.android.rum.tracking.AcceptAllActivities -import com.datadog.android.rum.tracking.ComponentPredicate -import io.flutter.embedding.android.FlutterActivity - -class FlutterExcludingComponentPredicate : ComponentPredicate { - val innerPredicate = AcceptAllActivities() - - override fun accept(component: Activity): Boolean { - if (component is FlutterActivity) { - return false - } - - return innerPredicate.accept(component) - } - - override fun getViewName(component: Activity): String? { - return innerPredicate.getViewName(component) - } -} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt b/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt index a49cd12ba..ac39595ae 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt @@ -13,7 +13,6 @@ import org.androidannotations.annotations.EService import org.getlantern.lantern.BuildConfig import org.getlantern.lantern.LanternApp import org.getlantern.lantern.R -import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.model.AccountInitializationStatus import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternStatus @@ -110,8 +109,6 @@ open class LanternService : Service(), Runnable { createUser(0) } - Datadog.initialize() - if (!BuildConfig.PLAY_VERSION && !BuildConfig.DEVELOPMENT_MODE) { // check if an update is available autoUpdater.checkForUpdates() diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt index d116a0dac..02d20db40 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/PaymentsUtil.kt @@ -17,7 +17,6 @@ import okhttp3.FormBody import okhttp3.Response import org.getlantern.lantern.LanternApp import org.getlantern.lantern.R -import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternHttpClient.ProCallback import org.getlantern.lantern.model.LanternSessionManager @@ -41,10 +40,6 @@ class PaymentsUtil(private val activity: Activity) { methodCallResult: MethodChannel.Result, ) { try { - Datadog.trackUserClick("submitStripePayment", mapOf( - "email" to email, - "planID" to planID, - )) val date = expirationDate.split("/").toTypedArray() val card: CardParams = CardParams( cardNumber.replace("[\\s]", ""), @@ -69,7 +64,7 @@ class PaymentsUtil(private val activity: Activity) { override fun onError(@NonNull error: Exception) { dialog?.dismiss() - Datadog.addError("Error submitting to Stripe: $error") + Logger.error(TAG, "Error submitting to Stripe: $error") methodCallResult.error( "errorSubmittingToStripe", error.getLocalizedMessage(), @@ -80,7 +75,7 @@ class PaymentsUtil(private val activity: Activity) { ) } catch (t: Throwable) { dialog?.dismiss() - Datadog.addError("Error submitting to Stripe", t) + Logger.error(TAG, "Error submitting to Stripe", t) methodCallResult.error( "errorSubmittingToStripe", activity.getString(R.string.error_making_purchase), @@ -96,10 +91,6 @@ class PaymentsUtil(private val activity: Activity) { methodCallResult: MethodChannel.Result, ) { try { - Datadog.trackUserClick("submitBitcoinPayment", mapOf( - "email" to email, - "planID" to planID, - )) val provider = PaymentProvider.BTCPay.toString().lowercase() val params = mutableMapOf( "email" to email, @@ -111,7 +102,7 @@ class PaymentsUtil(private val activity: Activity) { LanternHttpClient.createProUrl("/payment-redirect", params), object : ProCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Datadog.addError("BTCPay is unavailable", throwable) + Logger.error(TAG, "BTCPay is unavailable", throwable) methodCallResult.error( "unknownError", "BTCPay is unavailable", // This error message is localized Flutter-side @@ -130,7 +121,6 @@ class PaymentsUtil(private val activity: Activity) { }, ) } catch (t: Throwable) { - Datadog.addError("BTCPay is unavailable", t) methodCallResult.error( "unknownError", "BTCPay is unavailable", // This error message is localized Flutter-side @@ -142,9 +132,6 @@ class PaymentsUtil(private val activity: Activity) { // Handles Google Play transactions fun submitGooglePlayPayment(planID: String, methodCallResult: MethodChannel.Result) { val inAppBilling = LanternApp.getInAppBilling() - Datadog.trackUserClick("googlePlayPayment", mapOf( - "planID" to planID, - )) if (inAppBilling == null) { Logger.error(TAG, "Missing inAppBilling") @@ -169,7 +156,6 @@ class PaymentsUtil(private val activity: Activity) { activity.resources.getString(R.string.error_making_purchase), null, ) - Datadog.addError("Google Play: error making purchase") return } @@ -213,7 +199,6 @@ class PaymentsUtil(private val activity: Activity) { formBody, object : ProCallback { override fun onFailure(throwable: Throwable?, error: ProError?) { - Datadog.addError("Error retrieving referral code: $error", throwable) if (error != null && error.message != null) { methodCallResult.error( "unknownError", @@ -235,7 +220,6 @@ class PaymentsUtil(private val activity: Activity) { }, ) } catch (t: Throwable) { - Datadog.addError("Unable to apply referral code", t) methodCallResult.error( "unknownError", "Something went wrong while applying your referral code", @@ -328,11 +312,6 @@ class PaymentsUtil(private val activity: Activity) { override fun onFailure(t: Throwable?, error: ProError?) { Logger.e(TAG, "Error with purchase request: $error") - Datadog.addError("Error with purchase request: $error", t, mapOf( - "provider" to provider.toString().lowercase(), - "plan" to planID, - "deviceName" to session.deviceName(), - )) dialog?.dismiss() methodCallResult.error( "errorMakingPurchase", diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/SentryUtil.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/SentryUtil.kt new file mode 100644 index 000000000..6bb485387 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/SentryUtil.kt @@ -0,0 +1,56 @@ +package org.getlantern.lantern.util + +import android.content.Context +import android.os.Process +import io.sentry.SentryOptions +import io.sentry.android.core.SentryAndroid +import org.getlantern.mobilesdk.Logger +import java.io.BufferedReader +import java.io.InputStreamReader + +object SentryUtil { + private val TAG = SentryUtil::class.java.name + + /** + * Enables enrichment of sentry crash reports with the most recent Go panic from logcat. + * Keep in mind that Sentry only finds panics the next time that it runs after the process + * actually panicked. So, we can safely exclude logs from our current run. + * + * Keep in mind also that there's no guarantee that the panic log in question belongs to our + * specific panic, we're just picking up the most recent panic log information. + */ + @JvmStatic + fun enableGoPanicEnrichment(ctx: Context) { + SentryAndroid.init(ctx) { options -> + options.beforeSend = SentryOptions.BeforeSendCallback { event, _ -> + // enable enrichment only for exceptions related to OS signals like SIGABRT + if (event.exceptions?.firstOrNull()?.type?.startsWith("SIG") == true) { + val myPid = Process.myPid().toString() + val goErrorLog = StringBuilder() + val process = Runtime.getRuntime().exec( + "logcat -d -v brief" + ) + BufferedReader(InputStreamReader(process.inputStream)).use { reader -> + reader.forEachLine { line -> + if (!line.contains(myPid) && line.startsWith("E/Go ")) { + if (line.contains("panic: ")) { + // this is the first line of the most recent panic, remove old rows + // from what must be prior panics + goErrorLog.clear() + } + goErrorLog.appendLine(line) + } + } + } + + if (goErrorLog.isNotEmpty()) { + Logger.debug(TAG, "Attaching latestgopanic to event") + event.setExtra("latestgopanic", goErrorLog.toString()) + } + } + + event + } + } + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt index 9b0e29f72..70f551a93 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt @@ -7,7 +7,6 @@ import android.content.ServiceConnection import android.net.VpnService import android.os.IBinder import org.getlantern.lantern.LanternApp -import org.getlantern.lantern.datadog.Datadog import org.getlantern.lantern.service.LanternService_ import org.getlantern.mobilesdk.Logger @@ -59,7 +58,6 @@ class LanternVpnService : VpnService(), Runnable { return START_STICKY } return if (intent.action == ACTION_DISCONNECT) { - Datadog.trackUserTap("switchVPN", mapOf("status" to "disconnect")) stop() START_NOT_STICKY } else { @@ -71,7 +69,6 @@ class LanternVpnService : VpnService(), Runnable { private fun connect() { Logger.d(TAG, "connect") - Datadog.trackUserTap("switchVPN", mapOf("status" to "connect")) Thread(this, "VpnService").start() } diff --git a/lib/ad_helper.dart b/lib/ad_helper.dart index 9802104ec..c9b6f12e4 100644 --- a/lib/ad_helper.dart +++ b/lib/ad_helper.dart @@ -11,7 +11,6 @@ import 'package:clever_ads_solutions/public/MediationManager.dart'; import 'package:flutter/foundation.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:logger/logger.dart'; -import 'package:lantern/common/datadog.dart'; import 'package:lantern/replica/common.dart'; enum AdType { Google, CAS } @@ -107,20 +106,13 @@ class AdHelper { ad.fullScreenContentCallback = FullScreenContentCallback( onAdClicked: (ad) { logger.i('[Ads Manager] onAdClicked callback'); - Datadog.trackUserTap( - 'User tapped on interstitial ad', googleAttributes); }, onAdShowedFullScreenContent: (ad) { logger.i('[Ads Manager] Showing Ads'); - Datadog.trackUserCustom( - 'User shown interstitial ad', googleAttributes); }, onAdFailedToShowFullScreenContent: (ad, error) { logger.i( '[Ads Manager] onAdFailedToShowFullScreenContent callback'); - Datadog.addError( - 'Ad failed to show full screen content: $error', - attributes: googleAttributes); //if ads fail to load let user turn on VPN _postShowingAds(); }, @@ -131,13 +123,10 @@ class AdHelper { ); _interstitialAd = ad; logger.i('[Ads Manager] to loaded $ad'); - Datadog.trackUserCustom('Interstitial ad loaded', googleAttributes); }, onAdFailedToLoad: (err) { _failedLoadAttempts++; // increment the count on failure logger.i('[Ads Manager] failed to load $err'); - Datadog.addError('failed to load interstitial ad: $err', - attributes: googleAttributes); _postShowingAds(); }, ), @@ -207,7 +196,6 @@ class AdHelper { if (casMediationManager != null) { await casMediationManager!.loadInterstitial(); logger.i('[Ads Manager] Request: Initiating CAS Interstitial loading.'); - Datadog.trackUserCustom('Interstitial ad loaded', casAttributes); } } @@ -220,16 +208,12 @@ class AdHelper { void _onCASAdShowFailed() { logger.e('[Ads Manager] Error: CAS Interstitial failed to display.'); - Datadog.addError('Failed to display interstitial ad', - attributes: casAttributes); _failedCASLoadAttempts++; _postShowingAds(); // Reload or decide the next action } void _onCASAdClosedOrComplete() { logger.i('[Ads Manager] Completion: CAS Interstitial closed or completed.'); - Datadog.trackUserCustom( - 'Interstitial ad closed or completed', casAttributes); // Reset the counter when the ad successfully shows and closes/completes _failedCASLoadAttempts = 0; _postShowingAds(); @@ -289,14 +273,11 @@ class InterstitialListenerWrapper extends AdCallback { onFailed.call(); logger.i( '[CASIntegrationHelper] - InterstitialListenerWrapper onShowFailed-:$message'); - Datadog.addError('Interstitial ad onShowFailed: $message', - attributes: casAttributes); } @override void onShown() { // Called when ad is shown. logger.i('[CASIntegrationHelper] - InterstitialListenerWrapper onShown'); - Datadog.trackUserCustom('User shown interstitial ad', casAttributes); } } diff --git a/lib/catcher_setup.dart b/lib/catcher_setup.dart new file mode 100644 index 000000000..ee141d83e --- /dev/null +++ b/lib/catcher_setup.dart @@ -0,0 +1,44 @@ +import 'package:catcher_2/catcher_2.dart'; +import 'package:flutter/material.dart'; +import 'package:sentry/sentry.dart'; + +final debugOption = Catcher2Options( + SilentReportMode(), + [ + ConsoleHandler( + enableApplicationParameters: true, + enableDeviceParameters: true, + enableCustomParameters: true, + enableStackTrace: true, + ), + ], +); + +final releaseOption = Catcher2Options( + SilentReportMode(), + [ + ConsoleHandler( + enableApplicationParameters: true, + enableDeviceParameters: true, + enableCustomParameters: true, + enableStackTrace: true, + ), + SentryHandler( + SentryClient( + SentryOptions( + dsn: + 'https://4753d78f885f4b79a497435907ce4210@o75725.ingest.sentry.io/5850353', + ), + ), + printLogs: true, + ), + ], +); + +Catcher2 setupCatcherAndRun(StatelessWidget root) { + return Catcher2( + rootWidget: root, + debugConfig: debugOption, + releaseConfig: releaseOption, + ); +} diff --git a/lib/common/common.dart b/lib/common/common.dart index db237aa49..64a1b3506 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -28,11 +28,8 @@ export 'package:provider/provider.dart'; export 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; export 'package:stop_watch_timer/stop_watch_timer.dart'; export 'package:wakelock/wakelock.dart'; -export 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart'; -export 'package:datadog_tracking_http_client/datadog_tracking_http_client.dart'; export 'add_nonbreaking_spaces.dart'; -export 'datadog.dart'; export 'disable_back_button.dart'; export 'iterable_extension.dart'; export 'list_subscriber.dart'; diff --git a/lib/common/datadog.dart b/lib/common/datadog.dart deleted file mode 100644 index 6fbee1efa..000000000 --- a/lib/common/datadog.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:datadog_flutter_plugin/datadog_flutter_plugin.dart'; -import 'package:datadog_tracking_http_client/datadog_tracking_http_client.dart'; - -class Datadog { - static final DatadogSdk _instance = DatadogSdk.instance; - - static trackUserTap(String message, [Map attributes = const {}]) { - _instance.rum?.addUserAction(RumUserActionType.tap, message, attributes); - } - - static trackUserCustom(String message, [Map attributes = const {}]) { - _instance.rum?.addUserAction(RumUserActionType.custom, message, attributes); - } - - // Notifies Datadog that an Exception or Error [error] occurred in the currently - // presented View - static addError( - Object error, { - StackTrace? st, - Map attributes = const {}, - }) { - _instance.rum?.addErrorInfo( - error.toString(), - RumErrorSource.source, - stackTrace: st, - attributes: attributes, - ); - } -} diff --git a/lib/home.dart b/lib/home.dart index 9a407c52a..eb8babf8e 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -97,12 +97,6 @@ class _HomePageState extends State { Future _handleNativeNavigationRequest(MethodCall methodCall) async { switch (methodCall.method) { - case 'initDatadog': - final config = DdSdkExistingConfiguration( - loggingConfiguration: LoggingConfiguration(), - detectLongTasks: true, - )..enableHttpTracking(); - await DatadogSdk.instance.attachToExisting(config); case 'openConversation': final contact = Contact.fromBuffer(methodCall.arguments as Uint8List); await _context!.router.push(Conversation(contactId: contact.contactId)); diff --git a/lib/main.dart b/lib/main.dart index e96ba72f6..f4f137df9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter_driver/driver_extension.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:lantern/app.dart'; +import 'package:lantern/catcher_setup.dart'; import 'package:lantern/common/common.dart'; Future main() async { @@ -14,7 +15,7 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); await _initGoogleMobileAds(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - runApp(LanternApp()); + setupCatcherAndRun(LanternApp()); } Future _initGoogleMobileAds() async { diff --git a/pubspec.lock b/pubspec.lock index fb56a78dc..16831dac9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -217,6 +217,15 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + catcher_2: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: "7be5bb3ea4867f0ab549443511478303e54db4b7" + url: "https://github.com/ThexXTURBOXx/catcher.git" + source: git + version: "1.0.0-alpha.2" characters: dependency: transitive description: @@ -261,10 +270,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -337,22 +346,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.1" - datadog_flutter_plugin: - dependency: "direct main" - description: - name: datadog_flutter_plugin - sha256: "2db71372f12f250a549f249046912594f15bf59113dc64a7e2fd5ae85ad90729" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - datadog_tracking_http_client: - dependency: "direct main" - description: - name: datadog_tracking_http_client - sha256: "7274fee927d8ec012a6c4fd3e3333616035236875129f83525fa0bbbe2a66bbb" - url: "https://pub.dev" - source: hosted - version: "1.4.0" dbus: dependency: transitive description: @@ -784,10 +777,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" io: dependency: transitive description: @@ -844,6 +837,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + mailer: + dependency: transitive + description: + name: mailer + sha256: "57f6dd1496699999a7bfd0aa6be0645384f477f4823e16d4321c40a434346382" + url: "https://pub.dev" + source: hosted + version: "6.0.1" markdown: dependency: transitive description: @@ -856,18 +857,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -1180,6 +1181,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.8" + sentry: + dependency: "direct main" + description: + name: sentry + sha256: "830667eadc0398fea3a3424ed1b74568e2db603a42758d0922e2f2974ce55a60" + url: "https://pub.dev" + source: hosted + version: "7.10.1" share_plus: dependency: "direct main" description: @@ -1325,10 +1334,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sprintf: dependency: transitive description: @@ -1429,26 +1438,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" url: "https://pub.dev" source: hosted - version: "1.24.1" + version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" test_core: dependency: transitive description: name: test_core - sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.5.3" timezone: dependency: transitive description: @@ -1473,6 +1482,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" url_launcher: dependency: "direct main" description: @@ -1637,10 +1654,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6deed8ed625c52864792459709183da231ebf66ff0cf09e69b573227c377efe + sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f url: "https://pub.dev" source: hosted - version: "11.3.0" + version: "11.7.1" wakelock: dependency: "direct main" description: @@ -1689,6 +1706,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1794,5 +1819,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.3 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 1ca26afbe..2192591fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,12 @@ dependencies: google_fonts: flutter_switch: ^0.3.2 flag: ^7.0.0 + # from sdk. See https://github.com/jhomlala/catcher/pull/234 for more details + catcher_2: + git: + url: https://github.com/ThexXTURBOXx/catcher.git + + sentry: ^7.10.1 badges: ^3.1.1 dotted_border: ^2.0.0+3 styled_text: ^8.1.0 @@ -95,8 +101,6 @@ dependencies: device_info_plus: ^9.0.3 flutter_mailer: ^2.0.0 fluttertoast: ^8.2.2 - datadog_flutter_plugin: ^1.4.0 - datadog_tracking_http_client: ^1.4.0 # Package information package_info_plus: ^4.1.0