Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@
}

## END enums

## START napier
-keep class io.github.aakira.napier.* {
*;
}
56 changes: 38 additions & 18 deletions app/src/main/kotlin/nl/q42/template/logging/CrashlyticsLogger.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package nl.q42.template.logging

import android.util.Log
import com.google.firebase.Firebase
import com.google.firebase.crashlytics.crashlytics
import io.github.aakira.napier.Antilog
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.LogLevel
import nl.q42.template.BuildConfig
import io.github.aakira.napier.Napier

/** A value suitable for Crashlytics
* This value is used to truncate the user-defined message and the exception message
* In theory, the message sent to Crashlytics could therefore be 2x this value
*/
private const val MAX_CHARS_IN_LOG = 1200
private const val DEFAULT_TAG = "AppLogger"

/** A Crashlytics logger. The name Antilog might be an unfortunate choice by the Napier library;
* this is not a stub
*/
class CrashlyticsLogger : Antilog() {

private val logcatLogger = DebugAntilog()

override fun performLog(
priority: LogLevel,
tag: String?,
Expand All @@ -28,12 +27,13 @@ class CrashlyticsLogger : Antilog() {
) {
if (message == null && throwable == null) return

val safeTag = tag ?: DEFAULT_TAG

if (BuildConfig.DEBUG || priority > LogLevel.DEBUG) {
// also send to logcat
logcatLogger.log(priority, tag, throwable, message)
logToLogcat(priority, safeTag, message, throwable)
}

val limitedMessage = message?.take(MAX_CHARS_IN_LOG) ?: "(no message)" // to avoid OutOfMemoryError's
val limitedMessage = message?.take(MAX_CHARS_IN_LOG) ?: "(no message)" // to avoid OutOfMemoryError's

// at least one of message or throwable is not null
if (priority < LogLevel.ERROR) {
Expand All @@ -43,7 +43,29 @@ class CrashlyticsLogger : Antilog() {
Firebase.crashlytics.log(limitedMessage + errorMessage)
} else {
Firebase.crashlytics.log("recordException with message: $limitedMessage")
Firebase.crashlytics.recordException(throwable ?: buildCrashlyticsSyntheticException(limitedMessage))
Firebase.crashlytics.recordException(
throwable ?: buildCrashlyticsSyntheticException(
limitedMessage,
safeTag
)
)
}
}

private fun logToLogcat(
priority: LogLevel,
tag: String,
message: String?,
throwable: Throwable?
) {
val msg = message ?: ""
when (priority) {
LogLevel.VERBOSE -> Log.v(tag, msg, throwable)
LogLevel.DEBUG -> Log.d(tag, msg, throwable)
LogLevel.INFO -> Log.i(tag, msg, throwable)
LogLevel.WARNING -> Log.w(tag, msg, throwable)
LogLevel.ERROR -> Log.e(tag, msg, throwable)
LogLevel.ASSERT -> Log.wtf(tag, msg, throwable)
}
}

Expand All @@ -53,30 +75,28 @@ class CrashlyticsLogger : Antilog() {
* This is a workaround for the fact that Crashlytics groups errors by stacktrace
* [https://stackoverflow.com/a/59779764](https://stackoverflow.com/a/59779764)
*/
private fun buildCrashlyticsSyntheticException(message: String): Exception {
private fun buildCrashlyticsSyntheticException(message: String, tag: String): Exception {
val stackTrace = Thread.currentThread().stackTrace
val numToRemove = 9
val lastToRemove = stackTrace.getOrNull(numToRemove - 1)
if (lastToRemove == null) {
logcatLogger.log(priority = LogLevel.ERROR, tag = null, throwable = null,
message = "Got unexpected stacktrace while logging a message: ${stackTrace.contentToString()}"
)
Log.e(tag, "Got unexpected stacktrace while logging a message: ${stackTrace.contentToString()}")
return SyntheticException(message, stackTrace)
}
if (lastToRemove.className != io.github.aakira.napier.Napier::class.java.name || lastToRemove.methodName != "e\$default"){
logcatLogger.log(priority = LogLevel.ERROR, tag = null, throwable = null,
message = "Got unexpected stacktrace: class: ${lastToRemove.className}, method: ${lastToRemove.methodName}"
if (lastToRemove.className != Napier::class.java.name || lastToRemove.methodName != "e\$default") {
Log.e(
tag,
"Got unexpected stacktrace: class: ${lastToRemove.className}, method: ${lastToRemove.methodName}"
)
}
val abbreviatedStackTrace = stackTrace.takeLast(stackTrace.size - numToRemove).toTypedArray()
return SyntheticException(message, abbreviatedStackTrace)
}

}

class SyntheticException(
message: String,
private val abbreviatedStackTrace: Array<StackTraceElement>
message: String,
private val abbreviatedStackTrace: Array<StackTraceElement>
) : Exception(message) {
override fun getStackTrace(): Array<StackTraceElement> {
return abbreviatedStackTrace
Expand Down