Logging: Send failures.

Also improved crash reporting.
This commit is contained in:
Kevin Gorham 2020-02-12 08:01:08 -05:00
parent f02021709a
commit 8434e23014
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
4 changed files with 50 additions and 9 deletions

View File

@ -72,7 +72,8 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider {
inner class ExceptionReporter(private val ogHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread?, e: Throwable?) {
twig("Uncaught Exception: $e caused by: ${e?.cause}")
coordinator.feedback.report(e)
// these are the only reported crashes that are considered fatal
coordinator.feedback.report(e, true)
coordinator.flush()
// can do this if necessary but first verify that we need it
runBlocking {

View File

@ -3,6 +3,27 @@ package cash.z.ecc.android.feedback
import cash.z.ecc.android.ZcashWalletApp
object Report {
object Send {
class SubmitFailure(private val errorCode: Int?, private val errorMessage: String?) : Feedback.Funnel("send.failure.submit") {
override fun toMap(): MutableMap<String, Any> {
return super.toMap().apply {
put("error.code", errorCode ?: -1)
put("error.message", errorMessage ?: "None")
}
}
}
class EncodingFailure(private val errorCode: Int?, private val errorMessage: String?) : Feedback.Funnel("send.failure.submit") {
override fun toMap(): MutableMap<String, Any> {
return super.toMap().apply {
put("error.code", errorCode ?: -1)
put("error.message", errorMessage ?: "None")
}
}
}
}
enum class NonUserAction(override val key: String, val description: String) : Feedback.Action {
FEEDBACK_STARTED("action.feedback.start", "feedback started"),
FEEDBACK_STOPPED("action.feedback.stop", "feedback stopped"),

View File

@ -44,7 +44,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
}
binding.textConfirmation.text =
"Sending ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${sendViewModel.toAddress.toAbbreviatedAddress()}"
sendViewModel.memo?.trim()?.isNotEmpty()?.let { hasMemo ->
sendViewModel.memo.trim().isNotEmpty().let { hasMemo ->
binding.radioIncludeAddress.isChecked = hasMemo
binding.radioIncludeAddress.goneIf(!hasMemo)
}
@ -91,6 +91,15 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
pendingTransaction.isCreating() -> "Creating transaction . . ."
else -> "Transaction updated!".also { twig("Unhandled TX state: $pendingTransaction") }
}
// TODO: make this error tracking easier to use and more spiffy
if (pendingTransaction?.isFailedSubmit() == true) {
sendViewModel.feedback.report(Report.Send.SubmitFailure(pendingTransaction?.errorCode, pendingTransaction?.errorMessage))
}
if (pendingTransaction?.isFailedEncoding() == true) {
sendViewModel.feedback.report(Report.Send.EncodingFailure(pendingTransaction?.errorCode, pendingTransaction?.errorMessage))
}
twig("Pending TX (id: ${pendingTransaction?.id} Updated with message: $message")
binding.textStatus.apply {
text = "$message"
@ -106,7 +115,9 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
sendViewModel.reset()
}
} catch(t: Throwable) {
twig("ERROR: error while handling pending transaction update! $t")
val message = "ERROR: error while handling pending transaction update! $t"
twig(message)
Crashlytics.log(message)
Crashlytics.logException(t)
}
}

View File

@ -6,10 +6,8 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import java.io.OutputStreamWriter
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.StringBuilder
import kotlin.coroutines.coroutineContext
class Feedback(capacity: Int = 256) {
@ -145,8 +143,8 @@ class Feedback(capacity: Int = 256) {
*
* @param error the uncaught exception that occurred.
*/
fun report(error: Throwable?): Feedback {
return report(Crash(error))
fun report(error: Throwable?, fatal: Boolean = false): Feedback {
return report(Crash(error, fatal))
}
/**
@ -199,6 +197,14 @@ class Feedback(capacity: Int = 256) {
}
}
abstract class Funnel(override val key: String) : Action {
override fun toMap(): MutableMap<String, Any> {
return mutableMapOf(
"key" to key
)
}
}
interface Keyed<T> {
val key: T
}
@ -225,17 +231,19 @@ class Feedback(capacity: Int = 256) {
}
}
data class Crash(val error: Throwable?) : Action {
data class Crash(val error: Throwable? = null, val fatal: Boolean = true) : Action {
override val key: String = "crash"
override fun toMap(): Map<String, Any> {
return mutableMapOf<String, Any>(
"fatal" to fatal,
"message" to (error?.message ?: "None"),
"cause" to (error?.cause?.toString() ?: "None"),
"cause.cause" to (error?.cause?.cause?.toString() ?: "None"),
"cause.cause.cause" to (error?.cause?.cause?.cause?.toString() ?: "None")
).apply { putAll(super.toMap()); putAll(error.stacktraceToMap()) }
}
override fun toString() = "App crashed due to: $error"
override fun toString() = "App ${if (fatal) "crashed due to" else "caught error"}: $error"
}
}