Commit 77641319 authored by Roger Yang's avatar Roger Yang
Browse files

Closes #3448: Add breadcrumbs support in lib-crash

parent 8de2afcd
Loading
Loading
Loading
Loading
+94 −0
Original line number Diff line number Diff line
/* 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

import android.os.Parcelable
import kotlinx.android.parcel.Parcelize

/**
 * Represents a single crash breadcrumb.
 */
@Parcelize
data class Breadcrumb(
    /**
     * Message of the crash breadcrumb.
     */
    val message: String = "",

    /**
     * Data related to the crash breadcrumb.
     */
    val data: Map<String, String> = emptyMap(),

    /**
     * Category of the crash breadcrumb.
     */
    val category: String = "",

    /**
     * Level of the crash breadcrumb.
     */
    val level: Level = Level.DEBUG,

    /**
     * Type of the crash breadcrumb.
     */
    val type: Type = Type.DEFAULT
) : Parcelable {
    /**
     * Crash breadcrumb priority level.
     */
    enum class Level {
        /**
         * DEBUG level.
         */
        DEBUG,

        /**
         * INFO level.
         */
        INFO,

        /**
         * WARNING level.
         */
        WARNING,

        /**
         * ERROR level.
         */
        ERROR,

        /**
         * CRITICAL level.
         */
        CRITICAL
    }

    /**
     * Crash breadcrumb type.
     */
    enum class Type {
        /**
         * DEFAULT type.
         */
        DEFAULT,

        /**
         * HTTP type.
         */
        HTTP,

        /**
         * NAVIGATION type.
         */
        NAVIGATION,

        /**
         * USER type.
         */
        USER
    }
}
+16 −7
Original line number Diff line number Diff line
@@ -14,9 +14,12 @@ private const val INTENT_CRASH = "mozilla.components.lib.crash.CRASH"
// Uncaught exception crash intent extras
private const val INTENT_EXCEPTION = "exception"

// Breadcrumbs intent extras
private const val INTENT_BREADCRUMBS = "breadcrumbs"

// Native code crash intent extras (Mirroring GeckoView values)
private const val INTENT_MINIDUMP_PATH = "minidumpPath"
private const val INTENT_EXTEAS_PATH = "extrasPath"
private const val INTENT_EXTRAS_PATH = "extrasPath"
private const val INTENT_MINIDUMP_SUCCESS = "minidumpSuccess"
private const val INTENT_FATAL = "fatal"

@@ -30,15 +33,18 @@ sealed class Crash {
     * @property throwable The [Throwable] that caused the crash.
     */
    data class UncaughtExceptionCrash(
        val throwable: Throwable
        val throwable: Throwable,
        val breadcrumbs: ArrayList<Breadcrumb>
    ) : Crash() {
        override fun toBundle() = Bundle().apply {
            putSerializable(INTENT_EXCEPTION, throwable as Serializable)
            putParcelableArrayList(INTENT_BREADCRUMBS, breadcrumbs)
        }

        companion object {
            internal fun fromBundle(bundle: Bundle) = UncaughtExceptionCrash(
                bundle.getSerializable(INTENT_EXCEPTION) as Throwable
                bundle.getSerializable(INTENT_EXCEPTION) as Throwable,
                bundle.getParcelableArrayList(INTENT_BREADCRUMBS) ?: arrayListOf()
            )
        }
    }
@@ -60,21 +66,24 @@ sealed class Crash {
        val minidumpPath: String,
        val minidumpSuccess: Boolean,
        val extrasPath: String,
        val isFatal: Boolean
        val isFatal: Boolean,
        val breadcrumbs: ArrayList<Breadcrumb>
    ) : Crash() {
        override fun toBundle() = Bundle().apply {
            putString(INTENT_MINIDUMP_PATH, minidumpPath)
            putBoolean(INTENT_MINIDUMP_SUCCESS, minidumpSuccess)
            putString(INTENT_EXTEAS_PATH, extrasPath)
            putString(INTENT_EXTRAS_PATH, extrasPath)
            putBoolean(INTENT_FATAL, isFatal)
            putParcelableArrayList(INTENT_BREADCRUMBS, breadcrumbs)
        }

        companion object {
            internal fun fromBundle(bundle: Bundle) = NativeCodeCrash(
                bundle.getString(INTENT_MINIDUMP_PATH, ""),
                bundle.getBoolean(INTENT_MINIDUMP_SUCCESS, false),
                bundle.getString(INTENT_EXTEAS_PATH, ""),
                bundle.getBoolean(INTENT_FATAL, false)
                bundle.getString(INTENT_EXTRAS_PATH, ""),
                bundle.getBoolean(INTENT_FATAL, false),
                bundle.getParcelableArrayList(INTENT_BREADCRUMBS) ?: arrayListOf()
            )
        }
    }
+20 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ class CrashReporter(
    private val nonFatalCrashIntent: PendingIntent? = null
) {
    internal val logger = Logger("mozac/CrashReporter")
    internal val crashBreadcrumbs = arrayListOf<Breadcrumb>()

    init {
        if (services.isEmpty()) {
@@ -85,6 +86,22 @@ class CrashReporter(
        logger.info("Crash report submitted to ${services.size} services")
    }

    /**
     * Add a crash breadcrumb to all registered services with breadcrumb support.
     *
     * ```Kotlin
     *   crashReporter.recordCrashBreadcrumb(
     *       Breadcrumb("Settings button clicked", data, "UI", Level.INFO, Type.USER)
     *   )
     * ```
     */
    fun recordCrashBreadcrumb(breadcrumb: Breadcrumb) {
        if (crashBreadcrumbs.size >= BREADCRUMB_MAX_NUM) {
            crashBreadcrumbs.removeAt(0)
        }
        crashBreadcrumbs.add(breadcrumb)
    }

    internal fun onCrash(context: Context, crash: Crash) {
        if (!enabled) {
            return
@@ -174,6 +191,9 @@ class CrashReporter(
    )

    companion object {
        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
        internal const val BREADCRUMB_MAX_NUM = 20

        @Volatile
        private var instance: CrashReporter? = null

+2 −1
Original line number Diff line number Diff line
@@ -27,7 +27,8 @@ class ExceptionHandler(
        try {
            crashing = true

            crashReporter.onCrash(context, Crash.UncaughtExceptionCrash(throwable))
            crashReporter.onCrash(context, Crash.UncaughtExceptionCrash(throwable,
                    crashReporter.crashBreadcrumbs))

            defaultExceptionHandler?.uncaughtException(thread, throwable)
        } finally {
+3 −13
Original line number Diff line number Diff line
@@ -28,11 +28,6 @@ import kotlin.coroutines.EmptyCoroutineContext

class SendCrashReportService : Service() {
    private val crashReporter: CrashReporter by lazy { CrashReporter.requireInstance }
    private val logger by lazy { CrashReporter
            .requireInstance
            .logger
    }

    private var reporterCoroutineContext: CoroutineContext = EmptyCoroutineContext

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
@@ -52,14 +47,9 @@ class SendCrashReportService : Service() {
            startForeground(notificationId, notification)
        }

        intent.extras?.let { extras ->
            val crash = Crash.NativeCodeCrash.fromBundle(extras)
        NotificationManagerCompat.from(this).cancel(this, NOTIFICATION_TAG)

            sendCrashReport(crash) {
        crashReporter.submitReport(Crash.fromIntent(intent))
        stopSelf()
            }
        } ?: logger.error("Received intent with null extras")

        return START_NOT_STICKY
    }
Loading