Merge pull request #193 from zcash/feature/integrate-bugsnag

Feature/integrate bugsnag
This commit is contained in:
Kevin Gorham 2020-08-13 23:51:29 -04:00 committed by GitHub
commit a6cf504972
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 77 additions and 44 deletions

View File

@ -5,7 +5,6 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
//apply plugin: 'com.github.ben-manes.versions'
archivesBaseName = 'zcash-android-wallet'
@ -27,6 +26,7 @@ android {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
testInstrumentationRunnerArguments clearPackageData: 'true'
multiDexEnabled true
resValue 'string', 'bugsnag_api_key', "${(project.findProperty('BUGSNAG_API_KEY') ?: System.getenv('BUGSNAG_API_KEY')) ?: ''}"
}
flavorDimensions 'network'
productFlavors {
@ -145,8 +145,8 @@ dependencies {
implementation Deps.JavaX.JAVA_ANNOTATION
// Analytics (for dogfooding/crash-reporting/feedback only on internal team builds)
implementation Deps.Analytics.CRASHLYTICS
implementation Deps.Analytics.MIXPANEL
implementation Deps.Analytics.BUGSNAG
// Misc.
implementation Deps.Misc.LOTTIE

View File

@ -33,8 +33,6 @@
<!-- Firebase options -->
<meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" />
<!-- All reporting is opt-in, only -->
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<!-- Mixpanel options -->
<meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" />

View File

@ -9,7 +9,6 @@ import cash.z.ecc.android.feedback.*
import cash.z.ecc.android.sdk.ext.SilentTwig
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
@ -50,7 +49,6 @@ class AppModule {
): FeedbackCoordinator {
return preferences.getBoolean(FeedbackCoordinator.ENABLED, false).let { isEnabled ->
// observe nothing unless feedback is enabled
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(isEnabled)
Twig.plant(if (isEnabled) TroubleshootingTwig() else SilentTwig())
FeedbackCoordinator(feedback, if (isEnabled) defaultObservers else setOf())
}
@ -79,5 +77,5 @@ class AppModule {
@Provides
@Singleton
@IntoSet
fun provideFeedbackCrashlytics(): FeedbackCoordinator.FeedbackObserver = FeedbackCrashlytics()
fun provideFeedbackBugsnag(): FeedbackCoordinator.FeedbackObserver = FeedbackBugsnag()
}

View File

@ -8,7 +8,7 @@ import dagger.Reusable
@Module
class InitializerModule {
private val host = "lightwalletd.z.cash"
private val host = "lightwalletd.electriccoin.co"
private val port = 9067
@Provides

View File

@ -0,0 +1,56 @@
package cash.z.ecc.android.feedback
import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.sdk.ext.twig
import com.bugsnag.android.Bugsnag
import com.bugsnag.android.Configuration
class FeedbackBugsnag : FeedbackCoordinator.FeedbackObserver {
var isInitialized = false
override fun initialize(): FeedbackCoordinator.FeedbackObserver = apply {
ZcashWalletApp.instance.let { appContext ->
appContext.getString(R.string.bugsnag_api_key)
.takeUnless { it.isNullOrEmpty() }?.let { apiKey ->
twig("starting bugsnag")
val config = Configuration(apiKey)
Bugsnag.start(appContext, config)
isInitialized = true
} ?: onInitError()
}
}
private fun onInitError() {
twig("Warning: Failed to load bugsnag because the API key was missing!")
}
/**
* Report non-fatal crashes because fatal ones already get reported by default.
*/
override fun onAction(action: Feedback.Action) {
if (!isInitialized) return
when (action) {
is Feedback.Crash -> action.exception
is Feedback.NonFatal -> action.exception
is Report.Error.NonFatal.Reorg -> ReorgException(
action.errorHeight,
action.rewindHeight,
action.toString()
)
else -> null
}?.let { exception ->
val details = kotlin.runCatching { action.toMap() }.getOrElse { mapOf() }
Bugsnag.notify(exception) { event ->
if (details.isNotEmpty()) event.addMetadata("errorDetails", details)
true
}
}
}
private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) :
Throwable(reorgMesssage)
}

View File

@ -1,26 +0,0 @@
package cash.z.ecc.android.feedback
import com.google.firebase.crashlytics.FirebaseCrashlytics
class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver {
/**
* Report non-fatal crashes because fatal ones already get reported by default.
*/
override fun onAction(action: Feedback.Action) {
var exception: Throwable? = null
exception = when (action) {
is Feedback.Crash -> action.exception
is Feedback.NonFatal -> action.exception
is Report.Error.NonFatal.Reorg -> ReorgException(
action.errorHeight,
action.rewindHeight,
action.toString()
)
else -> null
}
exception?.let { FirebaseCrashlytics.getInstance().recordException(it) }
}
private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) :
Throwable(reorgMesssage)
}

View File

@ -11,8 +11,10 @@ class FeedbackFile(fileName: String = "user_log.txt") :
val file = File("${ZcashWalletApp.instance.filesDir}/logs", fileName)
private val format = SimpleDateFormat("MM-dd HH:mm:ss.SSS")
init {
if (!file.parentFile.exists()) file.parentFile.mkdirs()
override fun initialize(): FeedbackCoordinator.FeedbackObserver = apply {
file.parentFile?.apply {
if (!exists()) mkdirs()
}
}
override fun onMetric(metric: Feedback.Metric) {

View File

@ -7,8 +7,12 @@ import com.mixpanel.android.mpmetrics.MixpanelAPI
class FeedbackMixpanel : FeedbackCoordinator.FeedbackObserver {
private val mixpanel =
MixpanelAPI.getInstance(ZcashWalletApp.instance, R.string.mixpanel_project.toAppString())
private lateinit var mixpanel: MixpanelAPI
override fun initialize(): FeedbackCoordinator.FeedbackObserver = apply {
mixpanel =
MixpanelAPI.getInstance(ZcashWalletApp.instance, R.string.mixpanel_project.toAppString())
}
override fun onMetric(metric: Feedback.Metric) {
track(metric.key, metric.toMap())

View File

@ -442,7 +442,7 @@ class MainActivity : AppCompatActivity() {
}
}
}
twig("MainActivity has received an error${if (notified) " and notified the user" else ""} and reported it to crashlytics and mixpanel.")
twig("MainActivity has received an error${if (notified) " and notified the user" else ""} and reported it to bugsnag and mixpanel.")
feedback.report(error)
return true
}

View File

@ -23,7 +23,6 @@ import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.send.SendViewModel
import com.google.common.util.concurrent.ListenableFuture
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.launch
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

View File

@ -13,7 +13,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
classpath 'io.fabric.tools:gradle:1.31.2'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1'
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.7.5'
}
}

View File

@ -62,7 +62,7 @@ object Deps {
val STUB = "io.grpc:grpc-stub:$version"
}
object Analytics { // for dogfooding/crash-reporting/feedback only on internal team builds
val CRASHLYTICS = "com.google.firebase:firebase-crashlytics:17.0.1"
val BUGSNAG = "com.bugsnag:bugsnag-android:5.0.1"
val MIXPANEL = "com.mixpanel.android:mixpanel-android:5.6.3"
}
object JavaX {
@ -80,7 +80,7 @@ object Deps {
object Zcash {
const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0"
const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.0-beta09"
object Sdk : Version("1.1.0-beta03") {
object Sdk : Version("1.1.0-beta04") {
val MAINNET = "cash.z.ecc.android:sdk-mainnet:$version"
val TESTNET = "cash.z.ecc.android:sdk-testnet:$version"
}

View File

@ -38,6 +38,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
private val jobs = CompositeJob()
val observers = mutableSetOf<FeedbackObserver>()
/**
* Wait for any in-flight listeners to complete.
*/
@ -83,7 +84,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
*/
fun addObserver(observer: FeedbackObserver) {
feedback.onStart {
observers += observer
observers += observer.initialize()
observeMetrics(observer::onMetric)
observeActions(observer::onAction)
}
@ -119,6 +120,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
}
interface FeedbackObserver {
fun initialize(): FeedbackObserver { return this }
fun onMetric(metric: Feedback.Metric) {}
fun onAction(action: Feedback.Action) {}
fun flush() {}