Skip to content

Commit

Permalink
[refactor]: crashes are also saved in external files now
Browse files Browse the repository at this point in the history
  • Loading branch information
F0x1d committed Dec 17, 2023
1 parent 24dbd61 commit 0e0fb9b
Show file tree
Hide file tree
Showing 12 changed files with 293 additions and 37 deletions.
198 changes: 198 additions & 0 deletions app/schemas/com.f0x1d.logfox.database.AppDatabase/14.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{
"formatVersion": 1,
"database": {
"version": 14,
"identityHash": "f57da737dc1a5f965ff6519dfb74afac",
"entities": [
{
"tableName": "AppCrash",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`app_name` TEXT, `package_name` TEXT NOT NULL, `crash_type` INTEGER NOT NULL, `date_and_time` INTEGER NOT NULL, `log` TEXT NOT NULL, `log_file` TEXT, `log_dump_file` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "appName",
"columnName": "app_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "crashType",
"columnName": "crash_type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "dateAndTime",
"columnName": "date_and_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "log",
"columnName": "log",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "logFile",
"columnName": "log_file",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "logDumpFile",
"columnName": "log_dump_file",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_AppCrash_date_and_time",
"unique": false,
"columnNames": [
"date_and_time"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_AppCrash_date_and_time` ON `${TABLE_NAME}` (`date_and_time`)"
}
],
"foreignKeys": []
},
{
"tableName": "LogRecording",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `date_and_time` INTEGER NOT NULL, `file` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "dateAndTime",
"columnName": "date_and_time",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "file",
"columnName": "file",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "UserFilter",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`including` INTEGER NOT NULL, `allowed_levels` TEXT NOT NULL, `uid` TEXT, `pid` TEXT, `tid` TEXT, `package_name` TEXT, `tag` TEXT, `content` TEXT, `enabled` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
"fields": [
{
"fieldPath": "including",
"columnName": "including",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "allowedLevels",
"columnName": "allowed_levels",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "uid",
"columnName": "uid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "pid",
"columnName": "pid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tid",
"columnName": "tid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "packageName",
"columnName": "package_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "content",
"columnName": "content",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"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, 'f57da737dc1a5f965ff6519dfb74afac')"
]
}
}
13 changes: 11 additions & 2 deletions app/src/main/java/com/f0x1d/logfox/database/AppDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.f0x1d.logfox.database.entity.AllowedLevelsConverter
import com.f0x1d.logfox.database.entity.AppCrash
import com.f0x1d.logfox.database.entity.AppCrashDao
import com.f0x1d.logfox.database.entity.CrashTypeConverter
import com.f0x1d.logfox.database.entity.FileConverter
import com.f0x1d.logfox.database.entity.LogRecording
import com.f0x1d.logfox.database.entity.LogRecordingDao
import com.f0x1d.logfox.database.entity.UserFilter
Expand All @@ -24,16 +25,24 @@ import com.f0x1d.logfox.database.entity.UserFilterDao
LogRecording::class,
UserFilter::class
],
version = 13,
version = 14,
autoMigrations = [
AutoMigration(
from = 12,
to = 13,
spec = AppDatabase.Companion.AutoMigration12_13::class
),
AutoMigration(
from = 13,
to = 14
)
]
)
@TypeConverters(CrashTypeConverter::class, AllowedLevelsConverter::class)
@TypeConverters(
CrashTypeConverter::class,
AllowedLevelsConverter::class,
FileConverter::class
)
abstract class AppDatabase: RoomDatabase() {

companion object {
Expand Down
21 changes: 18 additions & 3 deletions app/src/main/java/com/f0x1d/logfox/database/entity/AppCrash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@ data class AppCrash(
@ColumnInfo(name = "package_name") val packageName: String,
@ColumnInfo(name = "crash_type") val crashType: CrashType,
@ColumnInfo(name = "date_and_time", index = true) val dateAndTime: Long,
@ColumnInfo(name = "log") val log: String,
@ColumnInfo(name = "log_dump_file") val logDumpFile: String? = null,
@ColumnInfo(name = "log") @Deprecated("Use logFile") val log: String = "",
@ColumnInfo(name = "log_file") val logFile: File? = null,
@ColumnInfo(name = "log_dump_file") val logDumpFile: File? = null,
@PrimaryKey(autoGenerate = true) val id: Long = 0
) {
val notificationId get() = (if (id == 0L) dateAndTime else id).toInt()

fun deleteDumpFile() = logDumpFile?.let { File(it).delete() }
fun deleteLogFile() = logFile?.delete()
fun deleteDumpFile() = logDumpFile?.delete()

fun deleteAssociatedFiles() {
deleteLogFile()
deleteDumpFile()
}
}

@Dao
Expand Down Expand Up @@ -73,4 +80,12 @@ class CrashTypeConverter {

@TypeConverter
fun fromCrashType(value: CrashType) = value.ordinal
}

class FileConverter {
@TypeConverter
fun toFile(value: String) = File(value)

@TypeConverter
fun fromFile(value: File) = value.absolutePath
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import com.f0x1d.logfox.extensions.context.notificationManagerCompat
import com.f0x1d.logfox.receiver.CopyReceiver

@SuppressLint("MissingPermission")
fun Context.sendErrorNotification(appCrash: AppCrash) = doIfPermitted {
fun Context.sendErrorNotification(appCrash: AppCrash, crashLog: String?) = doIfPermitted {
notify(
appCrash.packageName,
appCrash.notificationId,
NotificationCompat.Builder(this@sendErrorNotification, LogFoxApp.CRASHES_CHANNEL_ID)
.setContentTitle(getString(R.string.app_crashed, appCrash.appName ?: appCrash.packageName))
.setContentText(appCrash.log)
.setContentText(crashLog ?: appCrash.log)
.setSmallIcon(R.drawable.ic_android_notification)
.setStyle(NotificationCompat.BigTextStyle().bigText(appCrash.log))
.setStyle(NotificationCompat.BigTextStyle().bigText(crashLog ?: appCrash.log))
.apply {
if (appCrash.id != 0L) setContentIntent(
NavDeepLinkBuilder(this@sendErrorNotification)
Expand All @@ -40,7 +40,7 @@ fun Context.sendErrorNotification(appCrash: AppCrash) = doIfPermitted {
R.drawable.ic_copy,
getString(android.R.string.copy),
makeBroadcastPendingIntent(COPY_CRASH_INTENT_ID, CopyReceiver::class.java, bundleOf(
Intent.EXTRA_TEXT to appCrash.log,
Intent.EXTRA_TEXT to (crashLog ?: appCrash.log),
CopyReceiver.EXTRA_PACKAGE_NAME to appCrash.packageName,
CopyReceiver.EXTRA_NOTIFICATION_ID to appCrash.notificationId
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.f0x1d.logfox.extensions.logline.filterAndSearch
import com.f0x1d.logfox.extensions.notifications.cancelAllCrashNotifications
import com.f0x1d.logfox.extensions.notifications.cancelCrashNotificationFor
import com.f0x1d.logfox.extensions.notifications.sendErrorNotification
import com.f0x1d.logfox.model.LogLine
import com.f0x1d.logfox.repository.logging.base.LoggingHelperItemsRepository
import com.f0x1d.logfox.repository.logging.readers.crashes.ANRDetector
import com.f0x1d.logfox.repository.logging.readers.crashes.DumpCollector
Expand All @@ -27,6 +28,9 @@ class CrashesRepository @Inject constructor(
private val dumpCollector: DumpCollector
): LoggingHelperItemsRepository<AppCrash>(), SharedPreferences.OnSharedPreferenceChangeListener {

private val logsDir = File(context.filesDir.absolutePath + "/crashes").apply {
if (!exists()) mkdirs()
}
private val logDumpsDir = File(context.filesDir.absolutePath + "/dumps").apply {
if (!exists()) mkdirs()
}
Expand All @@ -49,40 +53,51 @@ class CrashesRepository @Inject constructor(

fun deleteAllByPackageName(appCrash: AppCrash) = runOnAppScope {
database.appCrashDao().getAllByPackageName(appCrash.packageName).forEach {
it.deleteDumpFile()
it.deleteAssociatedFiles()
context.cancelCrashNotificationFor(it)
}

database.appCrashDao().deleteByPackageName(appCrash.packageName)
}

private suspend fun collectCrash(it: AppCrash) {
private suspend fun collectCrash(it: AppCrash, lines: List<LogLine>) {
val crashLog = lines.joinToString("\n") {
it.content
}

val sendNotificationIfNeeded = { appCrash: AppCrash ->
if (appPreferences.showingNotificationsFor(appCrash.crashType)) {
context.sendErrorNotification(appCrash, crashLog)
}
}

database.appCrashDao().getAllByDateAndTime(it.dateAndTime).filter { crash ->
crash.packageName == it.packageName
}.also {
if (it.isNotEmpty()) return
}

val sendNotificationIfNeeded = { appCrash: AppCrash ->
if (appPreferences.showingNotificationsFor(appCrash.crashType)) {
context.sendErrorNotification(appCrash)
}
val logFile = File(logDumpsDir, "${it.dateAndTime}-crash.log").apply {
writeText(crashLog)
}

val logDump = dumpCollector
.logsDump
.filterAndSearch(database.userFilterDao().getAll())
.joinToString("\n") { logLine -> logLine.original }
.joinToString("\n") { it.original }

val logDumpFile = when (logDump.isNotEmpty()) {
true -> File(logDumpsDir, "${it.notificationId}-dump.txt").apply {
true -> File(logDumpsDir, "${it.dateAndTime}-dump.log").apply {
writeText(logDump)
}

else -> null
}

val appCrash = it.copy(logDumpFile = logDumpFile?.absolutePath)
val appCrash = it.copy(
logFile = logFile,
logDumpFile = logDumpFile
)

if (appPreferences.collectingFor(appCrash.crashType)) {
val appCrashWithId = appCrash.copy(
Expand All @@ -98,15 +113,15 @@ class CrashesRepository @Inject constructor(
override suspend fun updateInternal(item: AppCrash) = database.appCrashDao().update(item)

override suspend fun deleteInternal(item: AppCrash) {
item.deleteDumpFile()
item.deleteAssociatedFiles()
database.appCrashDao().delete(item)

context.cancelCrashNotificationFor(item)
}

override suspend fun clearInternal() {
database.appCrashDao().getAll().forEach {
it.deleteDumpFile()
it.deleteAssociatedFiles()
}
database.appCrashDao().deleteAll()

Expand Down
Loading

0 comments on commit 0e0fb9b

Please sign in to comment.