Skip to content
Snippets Groups Projects
Commit ca76d806 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Further improved CrashHandler

parent 51e8c775
No related branches found
No related tags found
No related merge requests found
Showing
with 234 additions and 96 deletions
......@@ -18,7 +18,10 @@ class QuasseldroidNG : Application() {
}
override fun onCreate() {
CrashHandler.init(this, buildConfig = BuildConfig::class.java)
CrashHandler.init(
application = this,
buildConfig = BuildConfig::class.java
)
super.onCreate()
// Init compatibility utils
......
......@@ -20,6 +20,7 @@ import de.kuschku.libquassel.session.SessionManager
import de.kuschku.libquassel.session.SocketAddress
import de.kuschku.libquassel.util.compatibility.LoggingHandler
import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
import de.kuschku.malheur.CrashHandler
import de.kuschku.quasseldroid_ng.Keys
import de.kuschku.quasseldroid_ng.R
import de.kuschku.quasseldroid_ng.persistence.AccountDatabase
......@@ -111,6 +112,17 @@ class ChatActivity : ServiceBoundActivity() {
true
)
}
CrashHandler.handle(
IllegalArgumentException(
"WRONG!",
RuntimeException(
"WRONG!",
NullPointerException(
"Super wrong!"
)
)
)
)
}
}
})
......
......@@ -11,5 +11,6 @@ data class CrashContext(
val throwable: Throwable,
val startTime: Date,
val crashTime: Date,
val buildConfig: Class<*>?
val buildConfig: Class<*>?,
val stackTraces: Map<Thread, Array<StackTraceElement>>?
)
......@@ -20,6 +20,8 @@ object CrashHandler {
val reportCollector = ReportCollector(application)
myHandler = Thread.UncaughtExceptionHandler { activeThread, throwable ->
val crashTime = Date()
val stackTraces = Thread.getAllStackTraces()
Thread {
try {
val json = gson.toJson(reportCollector.collect(CrashContext(
......@@ -28,11 +30,13 @@ object CrashHandler {
crashingThread = activeThread,
throwable = throwable,
startTime = startTime,
crashTime = Date(),
buildConfig = buildConfig
crashTime = crashTime,
buildConfig = buildConfig,
stackTraces = stackTraces
), config))
println(json)
} catch (e: Throwable) {
e.printStackTrace()
originalHandler?.uncaughtException(activeThread, throwable)
}
}.start()
......@@ -40,5 +44,9 @@ object CrashHandler {
Thread.setDefaultUncaughtExceptionHandler(myHandler)
}
fun handle(throwable: Throwable) {
myHandler?.uncaughtException(Thread.currentThread(), throwable)
}
var myHandler: Thread.UncaughtExceptionHandler? = null
}
......@@ -11,12 +11,6 @@ class ConfigurationCollector(private val application: Application) :
private val configValueInfo = mutableMapOf<String, SparseArray<String>>()
private val configurationFields = listOf(
FieldDefinition(
fieldName = "mcc"
),
FieldDefinition(
fieldName = "mnc"
),
FieldDefinition(
fieldName = "screenHeightDp"
),
......
package de.kuschku.malheur.collectors
import android.app.Application
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.CrashConfig
import de.kuschku.malheur.data.CrashInfo
import de.kuschku.malheur.data.ExceptionInfo
import de.kuschku.malheur.data.ThreadInfo
import de.kuschku.malheur.util.printStackTraceToString
import java.text.SimpleDateFormat
import java.util.*
class CrashCollector(application: Application) : Collector<CrashInfo, CrashConfig> {
private val configurationCollector = ConfigurationCollector(application)
private val isoFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.US)
import java.io.PrintWriter
import java.io.StringWriter
class CrashCollector : Collector<CrashInfo, CrashConfig> {
override fun collect(context: CrashContext, config: CrashConfig) = CrashInfo(
cause = collectIf(config.cause) {
ExceptionInfo(context.throwable)
},
exception = collectIf(config.exception) {
context.throwable.printStackTraceToString()
},
activeThread = collectIf(config.activeThread) {
ThreadInfo(context.crashingThread)
},
startTime = collectIf(config.startTime) {
isoFormatter.format(context.startTime)
},
crashTime = collectIf(config.crashTime) {
isoFormatter.format(context.crashTime)
},
configuration = configurationCollector.collectIf(context, config.configuration)
val result = StringWriter()
val printWriter = PrintWriter(result)
context.throwable.printStackTrace(printWriter)
result.toString()
}
)
}
......@@ -7,12 +7,15 @@ import android.provider.Settings
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.DeviceConfig
import de.kuschku.malheur.data.DeviceInfo
import de.kuschku.malheur.util.readProcInfo
import de.kuschku.malheur.util.reflectionCollectConstants
import java.io.File
class DeviceCollector(private val application: Application) : Collector<DeviceInfo, DeviceConfig> {
private val displayCollector = DisplayCollector(application)
@SuppressLint("HardwareIds")
override fun collect(context: CrashContext, config: DeviceConfig) = DeviceInfo(
override fun collect(context: CrashContext, config: DeviceConfig): DeviceInfo {
return DeviceInfo(
build = collectIf(config.build) {
reflectionCollectConstants(Build::class.java)
},
......@@ -24,6 +27,18 @@ class DeviceCollector(private val application: Application) : Collector<DeviceIn
},
processor = collectIf(config.processor) {
readProcInfo()
}
},
display = displayCollector.collectIf(context, config.display)
)
}
private fun readProcInfo() = File("/proc/cpuinfo")
.bufferedReader(Charsets.UTF_8)
.lineSequence()
.map { line -> line.split(":") }
.filter { split -> split.size == 2 }
.map { (key, value) -> key.trim() to value.trim() }
.filter { (key, _) -> key == "Hardware" }
.map { (_, value) -> value }
.firstOrNull()
}
package de.kuschku.malheur.collectors
import android.app.Application
import android.content.Context
import android.os.Build
import android.util.SparseArray
import android.view.Display
import android.view.WindowManager
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.data.DisplayInfo
import de.kuschku.malheur.data.MetricsInfo
import de.kuschku.malheur.util.getMetrics
import java.lang.reflect.Modifier
class DisplayCollector(application: Application) :
Collector<DisplayInfo, Boolean> {
private val windowManager = application.getSystemService(
Context.WINDOW_SERVICE) as WindowManager
@Suppress("DEPRECATION")
override fun collect(context: CrashContext, config: Boolean): DisplayInfo? {
val display = windowManager.defaultDisplay
val hdrCapabilities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val capabilitiesEnum = getHdrCapabilitiesEnum()
display.hdrCapabilities.supportedHdrTypes.map(capabilitiesEnum::get)
} else {
null
}
return DisplayInfo(
width = display.width,
height = display.height,
pixelFormat = display.pixelFormat,
refreshRate = display.refreshRate,
hdr = hdrCapabilities,
isWideGamut = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
display.isWideColorGamut
} else {
null
},
metrics = MetricsInfo(display.getMetrics())
)
}
private fun getHdrCapabilitiesEnum(): SparseArray<String> {
val hdrCapabilityEnums = SparseArray<String>()
Display.HdrCapabilities::class.java.declaredFields.filter {
Modifier.isStatic(it.modifiers)
}.filter {
it.name.startsWith("HDR_TYPE_")
}.filter {
it.type == Int::class.java
}.forEach {
try {
val value = it.getInt(null)
hdrCapabilityEnums.put(value, it.name.substring("HDR_TYPE_".length))
} catch (e: IllegalAccessException) {
}
}
return hdrCapabilityEnums
}
}
......@@ -5,13 +5,16 @@ import android.os.Debug
import android.os.Environment
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.EnvConfig
import de.kuschku.malheur.data.EnvInfo
import de.kuschku.malheur.data.MemoryInfo
import de.kuschku.malheur.util.reflectionCollectGetters
import java.io.File
class EnvCollector(application: Application) : Collector<Map<String, Any?>, EnvConfig> {
override fun collect(context: CrashContext, config: EnvConfig) = mapOf(
"paths" to collectIf(config.paths) {
class EnvCollector(application: Application) : Collector<EnvInfo, EnvConfig> {
private val configurationCollector = ConfigurationCollector(application)
override fun collect(context: CrashContext, config: EnvConfig) = EnvInfo(
paths = collectIf(config.paths) {
reflectionCollectGetters(
Environment::class.java)?.map { (key, value) ->
key to if (value is File) {
......@@ -21,10 +24,17 @@ class EnvCollector(application: Application) : Collector<Map<String, Any?>, EnvC
}
}?.toMap()
},
"memory" to collectIf(config.memory) {
memory = collectIf(config.memory) {
val memoryInfo = Debug.MemoryInfo()
Debug.getMemoryInfo(memoryInfo)
MemoryInfo(memoryInfo)
},
configuration = configurationCollector.collectIf(context, config.configuration),
startTime = collectIf(config.startTime) {
context.startTime.time
},
crashTime = collectIf(config.crashTime) {
context.crashTime.time
}
)
}
package de.kuschku.malheur.collectors
import android.app.Application
import android.os.Process
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.LogConfig
import de.kuschku.malheur.util.readLogCat
import java.text.SimpleDateFormat
import java.util.*
class LogCollector(application: Application) : Collector<Map<String, List<String>>, LogConfig> {
class LogCollector : Collector<Map<String, List<String>>, LogConfig> {
private val logcatTimeFormatter = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
private val pid = Process.myPid().toString()
override fun collect(context: CrashContext, config: LogConfig): Map<String, List<String>> {
val since = logcatTimeFormatter.format(context.startTime)
return config.buffers.map { buffer ->
buffer to readLogCat(since, buffer, pid)
buffer to readLogCat(since, buffer)
}.toMap()
}
private fun readLogCat(since: String, buffer: String) = ProcessBuilder()
.command("logcat", "-t", since, "-b", buffer)
.redirectErrorStream(true)
.start()
.inputStream
.bufferedReader(Charsets.UTF_8)
.readLines()
}
......@@ -4,24 +4,18 @@ import android.app.Application
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.config.ReportConfig
import de.kuschku.malheur.data.Report
import de.kuschku.malheur.data.ThreadInfo
class ReportCollector(application: Application) : Collector<Report, ReportConfig> {
private val logcatCollector = LogCollector(application)
private val crashCollector = CrashCollector(application)
private val crashCollector = CrashCollector()
private val threadCollector = ThreadCollector()
private val logcatCollector = LogCollector()
private val applicationCollector = AppCollector(application)
private val deviceCollector = DeviceCollector(application)
private val environmentCollector = EnvCollector(application)
override fun collect(context: CrashContext, config: ReportConfig) = Report(
crash = crashCollector.collectIf(context, config.crash),
threads = collectIf(config.threads) {
Thread.getAllStackTraces()
.filterKeys { it != Thread.currentThread() }
.map { (thread, stackTrace) ->
ThreadInfo(thread, stackTrace)
}
},
threads = threadCollector.collectIf(context, config.threads),
logcat = logcatCollector.collectIf(context, config.logcat),
application = applicationCollector.collectIf(context, config.application),
device = deviceCollector.collectIf(context, config.device),
......
package de.kuschku.malheur.collectors
import de.kuschku.malheur.CrashContext
import de.kuschku.malheur.CrashHandler
import de.kuschku.malheur.config.ThreadConfig
import de.kuschku.malheur.data.ThreadInfo
import de.kuschku.malheur.data.ThreadsInfo
import de.kuschku.malheur.data.TraceElement
class ThreadCollector : Collector<ThreadsInfo, ThreadConfig> {
override fun collect(context: CrashContext, config: ThreadConfig) = ThreadsInfo(
crashed = context.stackTraces?.filterKeys {
it == context.crashingThread
}?.map { (thread, stackTrace) ->
threadToInfo(thread, stackTrace)
}?.firstOrNull(),
others = context.stackTraces?.filterKeys {
it != Thread.currentThread() && it != context.crashingThread
}?.map { (thread, stackTrace) ->
threadToInfo(thread, stackTrace)
}
)
private fun threadToInfo(thread: Thread, stackTrace: Array<StackTraceElement>) = ThreadInfo(
id = thread.id,
name = thread.name,
group = thread.threadGroup?.name,
status = thread.state?.name,
stackTrace = ArrayList(sanitize(stackTrace.map(::TraceElement))),
isDaemon = thread.isDaemon,
priority = thread.priority
)
private fun sanitize(list: List<TraceElement>): List<TraceElement> {
var idx = 0
while (idx < list.size) {
val traceElement = list[idx]
if (traceElement.className == CrashHandler::class.java.canonicalName)
break
idx++
}
while (idx < list.size) {
val traceElement = list[idx]
if (traceElement.className != CrashHandler::class.java.canonicalName)
break
idx++
}
val after = mutableListOf<TraceElement>()
while (idx < list.size) {
val traceElement = list[idx]
after.add(traceElement)
idx++
}
return if (after.size > 0) {
after
} else list
}
}
......@@ -2,9 +2,5 @@ package de.kuschku.malheur.config
data class CrashConfig(
val cause: Boolean = true,
val exception: Boolean = true,
val activeThread: Boolean = true,
val startTime: Boolean = true,
val crashTime: Boolean = true,
val configuration: Boolean = true
val exception: Boolean = true
)
......@@ -5,5 +5,6 @@ data class DeviceConfig(
val version: Boolean = true,
val installationId: Boolean = true,
val processor: Boolean = true,
val runtime: Boolean = true
val runtime: Boolean = true,
val display: Boolean = true
)
......@@ -2,5 +2,8 @@ package de.kuschku.malheur.config
data class EnvConfig(
val paths: Boolean = true,
val memory: Boolean = true
val memory: Boolean = true,
val configuration: Boolean = true,
val startTime: Boolean = true,
val crashTime: Boolean = true
)
......@@ -2,7 +2,7 @@ package de.kuschku.malheur.config
data class ReportConfig(
val crash: CrashConfig? = CrashConfig(),
val threads: Boolean = true,
val threads: ThreadConfig? = ThreadConfig(),
val logcat: LogConfig? = LogConfig(),
val application: AppConfig? = AppConfig(),
val device: DeviceConfig? = DeviceConfig(),
......
package de.kuschku.malheur.config
data class ThreadConfig(
val current: Boolean = true,
val others: Boolean = true
)
......@@ -2,9 +2,5 @@ package de.kuschku.malheur.data
data class CrashInfo(
val cause: ExceptionInfo?,
val exception: String?,
val activeThread: ThreadInfo?,
val startTime: String?,
val crashTime: String?,
val configuration: Map<String, Any?>?
val exception: String?
)
......@@ -4,5 +4,6 @@ data class DeviceInfo(
val build: Map<String, Any?>?,
val version: Map<String, Any?>?,
val installationId: String?,
val processor: String?
val processor: String?,
val display: DisplayInfo?
)
package de.kuschku.malheur.data
import android.view.Display
import de.kuschku.malheur.util.getMetrics
data class DisplayInfo(
val width: Int,
val height: Int,
val pixelFormat: Int,
val refreshRate: Float,
val isHdr: Boolean,
val isWideGamut: Boolean,
val hdr: List<String>?,
val isWideGamut: Boolean?,
val metrics: MetricsInfo
) {
constructor(display: Display) : this(
width = display.width,
height = display.height,
pixelFormat = display.pixelFormat,
refreshRate = display.refreshRate,
isHdr = display.isHdr,
isWideGamut = display.isWideColorGamut,
metrics = MetricsInfo(display.getMetrics())
)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment