Commit b45d5c2f authored by MozLando's avatar MozLando
Browse files

Merge #6828



6828: Issue #1705 - lib-crash: Add database storage for crashes. r=rocketsroger a=pocmo

This patch adds the database classes to store information about crashes on disk. In this state it is not being used yet. Landing this separately to continue working on the UI and the integration. I'm not 100% happy with that part yet and therefore want to continue iterating. However the database piece seems to be stable enough and probably won't change.

Co-authored-by: default avatarSebastian Kaspari <s.kaspari@gmail.com>
parents 6129f0c6 5da82d47
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ plugins {
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion config.compileSdkVersion
@@ -32,6 +33,12 @@ android {
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    kapt {
        arguments {
            arg("room.schemaLocation", "$projectDir/schemas".toString())
        }
    }

    buildTypes {
        release {
            minifyEnabled false
@@ -51,6 +58,9 @@ dependencies {
    implementation project(':support-ktx')
    implementation project(':support-utils')

    implementation Dependencies.androidx_room_runtime
    kapt Dependencies.androidx_room_compiler

    // We only compile against Sentry, GeckoView, and Glean. It's up to the app to add those dependencies if it wants to
    // send crash reports to Socorro (GV) or Sentry.
    compileOnly Dependencies.thirdparty_sentry
+84 −0
Original line number Diff line number Diff line
{
  "formatVersion": 1,
  "database": {
    "version": 1,
    "identityHash": "c82f6a8cf8fbd6509f73a7d7d5ff97cd",
    "entities": [
      {
        "tableName": "crashes",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `stacktrace` TEXT NOT NULL, `created_at` INTEGER NOT NULL, PRIMARY KEY(`uuid`))",
        "fields": [
          {
            "fieldPath": "uuid",
            "columnName": "uuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "stacktrace",
            "columnName": "stacktrace",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "createdAt",
            "columnName": "created_at",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "uuid"
          ],
          "autoGenerate": false
        },
        "indices": [],
        "foreignKeys": []
      },
      {
        "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)",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": false
          },
          {
            "fieldPath": "crashUuid",
            "columnName": "crashUuid",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "serviceId",
            "columnName": "service_id",
            "affinity": "TEXT",
            "notNull": true
          },
          {
            "fieldPath": "reportId",
            "columnName": "report_id",
            "affinity": "TEXT",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": true
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "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')"
    ]
  }
}
 No newline at end of file
+36 −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.db

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction

/**
 * Dao for saving and accessing crash related information.
 */
@Dao
internal interface CrashDao {
    /**
     * Inserts a crash into the database.
     */
    @Insert
    fun insertCrash(crash: CrashEntity): Long

    /**
     * Inserts a report to the database.
     */
    @Insert
    fun insertReport(report: ReportEntity): Long

    /**
     * Returns saved crashes with their reports.
     */
    @Transaction
    @Query("SELECT * FROM crashes ORDER BY created_at DESC")
    fun getCrashesWithReports(): LiveData<List<CrashWithReports>>
}
+40 −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.db

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

/**
 * Internal database for storing collections and their tabs.
 */
@Database(
    entities = [CrashEntity::class, ReportEntity::class],
    version = 1
)
internal abstract class CrashDatabase : RoomDatabase() {
    abstract fun crashDao(): CrashDao

    companion object {
        @Volatile private var instance: CrashDatabase? = null

        @Synchronized
        fun get(context: Context): CrashDatabase {
            instance?.let { return it }

            return Room.databaseBuilder(context, CrashDatabase::class.java, "crashes")
                // We are allowing main thread queries here since we need to write to disk blocking
                // in a crash event before the process gets shutdown. At this point the app already
                // crashed and temporarily blocking the UI thread is not that problematic anymore.
                .allowMainThreadQueries()
                .build()
                .also {
                    instance = it
                }
        }
    }
}
+54 −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.db

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import mozilla.components.lib.crash.Crash
import mozilla.components.support.base.ext.getStacktraceAsString

/**
 * Database entity modeling a crash that has happened.
 */
@Entity(
    tableName = "crashes"
)
internal data class CrashEntity(
    /**
     * Generated UUID for this crash.
     */
    @PrimaryKey
    @ColumnInfo(name = "uuid")
    var uuid: String,

    /**
     * The stacktrace of the crash (if this crash was caused by an exception/throwable): otherwise
     * a string describing the type of crash.
     */
    @ColumnInfo(name = "stacktrace")
    var stacktrace: String,

    /**
     * Timestamp (in milliseconds) of when the crash happened.
     */
    @ColumnInfo(name = "created_at")
    var createdAt: Long
)

internal fun Crash.toEntity(): CrashEntity {
    return CrashEntity(
        uuid = uuid,
        stacktrace = getStacktrace(),
        createdAt = System.currentTimeMillis()
    )
}

internal fun Crash.getStacktrace(): String {
    return when (this) {
        is Crash.NativeCodeCrash -> "<native crash>"
        is Crash.UncaughtExceptionCrash -> throwable.getStacktraceAsString()
    }
}
Loading