Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Merge #6871
Browse files Browse the repository at this point in the history
6871: Add crash database to CrashReporter and add UI for showing past crashes. r=rocketsroger a=pocmo



Co-authored-by: Sebastian Kaspari <[email protected]>
  • Loading branch information
MozLando and pocmo committed May 11, 2020
2 parents aace2d8 + c49724b commit 7a60da6
Show file tree
Hide file tree
Showing 31 changed files with 560 additions and 47 deletions.
1 change: 1 addition & 0 deletions components/lib/crash/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {

implementation Dependencies.androidx_appcompat
implementation Dependencies.androidx_constraintlayout
implementation Dependencies.androidx_recyclerview

implementation project(':support-base')
implementation project(':support-ktx')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "c82f6a8cf8fbd6509f73a7d7d5ff97cd",
"identityHash": "212dfa0b59d6a78d81e65cead34d40e0",
"entities": [
{
"tableName": "crashes",
Expand Down Expand Up @@ -38,7 +38,7 @@
},
{
"tableName": "reports",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `crashUuid` TEXT NOT NULL, `service_id` TEXT NOT NULL, `report_id` TEXT NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `crash_uuid` TEXT NOT NULL, `service_id` TEXT NOT NULL, `report_id` TEXT NOT NULL)",
"fields": [
{
"fieldPath": "id",
Expand All @@ -48,7 +48,7 @@
},
{
"fieldPath": "crashUuid",
"columnName": "crashUuid",
"columnName": "crash_uuid",
"affinity": "TEXT",
"notNull": true
},
Expand Down Expand Up @@ -78,7 +78,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c82f6a8cf8fbd6509f73a7d7d5ff97cd')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '212dfa0b59d6a78d81e65cead34d40e0')"
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.lib.crash.db.CrashDatabase
import mozilla.components.lib.crash.db.insertCrashSafely
import mozilla.components.lib.crash.db.insertReportSafely
import mozilla.components.lib.crash.db.toEntity
import mozilla.components.lib.crash.db.toReportEntity
import mozilla.components.lib.crash.handler.ExceptionHandler
import mozilla.components.lib.crash.notification.CrashNotification
import mozilla.components.lib.crash.prompt.CrashPrompt
Expand Down Expand Up @@ -55,6 +60,7 @@ import mozilla.components.support.base.log.logger.Logger
*/
@Suppress("TooManyFunctions")
class CrashReporter(
context: Context,
private val services: List<CrashReporterService> = emptyList(),
private val telemetryServices: List<CrashTelemetryService> = emptyList(),
private val shouldPrompt: Prompt = Prompt.NEVER,
Expand All @@ -63,6 +69,8 @@ class CrashReporter(
private val nonFatalCrashIntent: PendingIntent? = null,
private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
) : CrashReporting {
private val database: CrashDatabase by lazy { CrashDatabase.get(context) }

internal val logger = Logger("mozac/CrashReporter")
internal val crashBreadcrumbs = BreadcrumbPriorityQueue(BREADCRUMB_MAX_NUM)

Expand Down Expand Up @@ -91,10 +99,14 @@ class CrashReporter(
fun submitReport(crash: Crash, then: () -> Unit = {}): Job {
return scope.launch {
services.forEach { service ->
when (crash) {
val reportId = when (crash) {
is Crash.NativeCodeCrash -> service.report(crash)
is Crash.UncaughtExceptionCrash -> service.report(crash)
}

if (reportId != null) {
database.crashDao().insertReportSafely(service.toReportEntity(crash, reportId))
}
}

logger.info("Crash report submitted to ${services.size} services")
Expand Down Expand Up @@ -156,6 +168,8 @@ class CrashReporter(

logger.info("Received crash: $crash")

database.crashDao().insertCrashSafely(crash.toEntity())

if (telemetryServices.isNotEmpty()) {
sendCrashTelemetry(context, crash)
}
Expand Down Expand Up @@ -230,6 +244,10 @@ class CrashReporter(
}
}

internal fun getCrashReporterServiceById(id: String): CrashReporterService {
return services.first { it.id == id }
}

enum class Prompt {
/**
* Never prompt the user. Always submit crash reports immediately.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

package mozilla.components.lib.crash.db

import android.annotation.SuppressLint
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import java.lang.Exception

/**
* Dao for saving and accessing crash related information.
Expand All @@ -34,3 +37,35 @@ internal interface CrashDao {
@Query("SELECT * FROM crashes ORDER BY created_at DESC")
fun getCrashesWithReports(): LiveData<List<CrashWithReports>>
}

/**
* Insert crash into database safely, ignoring any exceptions.
*
* When handling a crash we want to avoid causing another crash when writing to the database. In the
* case of an error we will just ignore it and continue without saving to the database.
*/
@SuppressLint("LogUsage") // We do not want to use our custom logger while handling the crash
@Suppress("TooGenericExceptionCaught")
internal fun CrashDao.insertCrashSafely(entity: CrashEntity) {
try {
insertCrash(entity)
} catch (e: Exception) {
Log.e("CrashDao", "Failed to insert crash into database", e)
}
}

/**
* Insert report into database safely, ignoring any exceptions.
*
* When handling a crash we want to avoid causing another crash when writing to the database. In the
* case of an error we will just ignore it and continue without saving to the database.
*/
@SuppressLint("LogUsage") // We do not want to use our custom logger while handling the crash
@Suppress("TooGenericExceptionCaught")
internal fun CrashDao.insertReportSafely(entity: ReportEntity) {
try {
insertReport(entity)
} catch (e: Exception) {
Log.e("CrashDao", "Failed to insert report into database", e)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package mozilla.components.lib.crash.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import mozilla.components.lib.crash.Crash
import mozilla.components.lib.crash.service.CrashReporterService

/**
Expand Down Expand Up @@ -41,3 +42,11 @@ internal data class ReportEntity(
@ColumnInfo(name = "report_id")
var reportId: String
)

internal fun CrashReporterService.toReportEntity(crash: Crash, reportId: String): ReportEntity {
return ReportEntity(
crashUuid = crash.uuid,
serviceId = id,
reportId = reportId
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ class ExceptionHandler(

try {
crashing = true
crashReporter.onCrash(context, Crash.UncaughtExceptionCrash(throwable,
crashReporter.crashBreadcrumbs.toSortedArrayList()))

crashReporter.onCrash(
context,
Crash.UncaughtExceptionCrash(
throwable = throwable,
breadcrumbs = crashReporter.crashBreadcrumbs.toSortedArrayList()
)
)

defaultExceptionHandler?.uncaughtException(thread, throwable)
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,42 @@ internal const val INFO_PREFIX = "[INFO]"
* Interface to be implemented by external services that accept crash reports.
*/
interface CrashReporterService {
/**
* A unique ID to identify this crash reporter service.
*/
val id: String

/**
* A human-readable name for this crash reporter service (to be displayed in UI).
*/
val name: String

/**
* Returns a URL to a website with the crash report if possible. Otherwise returns null.
*/
fun createCrashReportUrl(identifier: String): String?

/**
* Submits a crash report for this [Crash.UncaughtExceptionCrash].
*
* @return Unique identifier that can be used by/with this crash reporter service to find this
* crash - or null if no identifier can be provided.
* @return Unique crash report identifier that can be used by/with this crash reporter service
* to find this reported crash - or null if no identifier can be provided.
*/
fun report(crash: Crash.UncaughtExceptionCrash): String?

/**
* Submits a crash report for this [Crash.NativeCodeCrash].
*
* @return Unique identifier that can be used by/with this crash reporter service to find this
* crash - or null if no identifier can be provided.
* @return Unique crash report identifier that can be used by/with this crash reporter service
* to find this reported crash - or null if no identifier can be provided.
*/
fun report(crash: Crash.NativeCodeCrash): String?

/**
* Submits a caught exception report for this [Throwable].
*
* @return Unique identifier that can be used by/with this crash reporter service to find this
* crash - or null if no identifier can be provided.
* @return Unique crash report identifier that can be used by/with this crash reporter service
* to find this reported crash - or null if no identifier can be provided.
*/
fun report(throwable: Throwable, breadcrumbs: ArrayList<Breadcrumb>): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ class MozillaSocorroService(
private val logger = Logger("mozac/MozillaSocorroCrashHelperService")
private val startTime = System.currentTimeMillis()

override val id: String = "socorro"

override val name: String = "Socorro"

override fun createCrashReportUrl(identifier: String): String? {
return "https://crash-stats.mozilla.org/report/index/$identifier"
}

init {
if (versionName == DEFAULT_VERSION_NAME) {
try {
Expand Down Expand Up @@ -161,6 +169,7 @@ class MozillaSocorroService(
val map = parseResponse(reader)

val id = map?.get(KEY_CRASH_ID)

if (id != null) {
logger.info("Crash reported to Socorro: $id")
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ class SentryService(
private val sendEventForNativeCrashes: Boolean = false,
clientFactory: SentryClientFactory? = null
) : CrashReporterService {
override val id: String = "sentry"

override val name: String = "Sentry"

override fun createCrashReportUrl(identifier: String): String? {
val id = identifier.replace("-", "")
return "https://sentry.prod.mozaws.net/operations/samples-crash/?query=$id"
}

// Fenix perf note: Sentry init may negatively impact cold startup so it's important this is lazily init.
@VisibleForTesting(otherwise = PRIVATE)
Expand Down Expand Up @@ -73,6 +81,7 @@ class SentryService(
val eventBuilder = EventBuilder().withMessage(createMessage(crash))
.withLevel(Event.Level.FATAL)
.withSentryInterface(ExceptionInterface(crash.throwable))

client.sendEvent(eventBuilder)
client.context.clearBreadcrumbs()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.lib.crash.ui

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.lib.crash.R

/**
* Activity for displaying the list of reported crashes.
*/
abstract class AbstractCrashListActivity : AppCompatActivity() {
abstract val crashReporter: CrashReporter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setTitle(R.string.mozac_lib_crash_activity_title)
setContentView(R.layout.mozac_lib_crash_activity_crashlist)

if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.add(R.id.container, CrashListFragment())
.commit()
}
}

/**
* Gets invoked whenever the user selects a crash reporting service.
*
* @param url URL pointing to the crash report for the selected crash reporting service.
*/
abstract fun onCrashServiceSelected(url: String)
}
Loading

0 comments on commit 7a60da6

Please sign in to comment.