From c8d19403c34d326bbbb033ef4754d1313da68001 Mon Sep 17 00:00:00 2001 From: "yingying.chen" Date: Wed, 20 Sep 2023 13:50:40 +0100 Subject: [PATCH] feature(exitInfo) add log messages to metadata --- .../detekt-baseline.xml | 7 +- .../bugsnag/android/BugsnagExitInfoPlugin.kt | 4 +- .../bugsnag/android/TombstoneEventEnhancer.kt | 7 +- .../com/bugsnag/android/TombstoneParser.kt | 35 ++++++- .../android/TombstoneEventEnhancerTest.kt | 11 ++- .../bugsnag/android/TombstoneParserTest.kt | 94 +++++++++++++------ 6 files changed, 122 insertions(+), 36 deletions(-) diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml index 5d1f9a9cde..de065f8ca6 100644 --- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml +++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml @@ -2,6 +2,12 @@ + LongParameterList:TombstoneParser.kt$TombstoneParser$( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, fileDescriptorConsumer: (Int, String, String) -> Unit, logcatConsumer: (String) -> Unit ) + MagicNumber:TombstoneParser.kt$TombstoneParser$3 + MagicNumber:TombstoneParser.kt$TombstoneParser$4 + MagicNumber:TombstoneParser.kt$TombstoneParser$5 + MagicNumber:TombstoneParser.kt$TombstoneParser$6 + MagicNumber:TombstoneParser.kt$TombstoneParser$7 MagicNumber:TraceParser.kt$TraceParser$16 MagicNumber:TraceParser.kt$TraceParser$3 MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so " @@ -24,6 +30,5 @@ NestedBlockDepth:TraceParser.kt$TraceParser$private fun parseThreadAttributes(line: String) ReturnCount:TraceParser.kt$TraceParser$@VisibleForTesting internal fun parseNativeFrame(line: String): Stackframe? SwallowedException:ExitInfoCallback.kt$ExitInfoCallback$exc: Throwable - UnusedPrivateProperty:BugsnagExitInfoPlugin.kt$BugsnagExitInfoPlugin$/** * Whether to report stored logcat messages metadata */ private val includeLogcat: Boolean = false diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt index 179a3ce391..c79ad11854 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/BugsnagExitInfoPlugin.kt @@ -16,7 +16,7 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( /** * Whether to report stored logcat messages metadata */ - private val includeLogcat: Boolean = false, + private val includeLogcat: Boolean = true, /** * Turn of event correlation based on the processStateSummary field, this can @@ -42,7 +42,7 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( exitInfoCallback = ExitInfoCallback( client.appContext, - TombstoneEventEnhancer(client.logger, listOpenFds), + TombstoneEventEnhancer(client.logger, listOpenFds, includeLogcat), TraceEventEnhancer(client.logger, client.immutableConfig.projectPackages) ) client.addOnSend(exitInfoCallback) diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneEventEnhancer.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneEventEnhancer.kt index 20c775607d..cb2ba90a84 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneEventEnhancer.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneEventEnhancer.kt @@ -7,7 +7,8 @@ import com.bugsnag.android.Thread as BugsnagThread internal class TombstoneEventEnhancer( private val logger: Logger, - private val listOpenFds: Boolean + private val listOpenFds: Boolean, + private val includeLogcat: Boolean ) : (Event, ApplicationExitInfo) -> Unit { @RequiresApi(Build.VERSION_CODES.R) override fun invoke(event: Event, exitInfo: ApplicationExitInfo) { @@ -15,6 +16,7 @@ internal class TombstoneEventEnhancer( TombstoneParser(logger).parse( exitInfo, listOpenFds, + includeLogcat, threadConsumer = { thread -> mergeThreadIntoEvent( thread, @@ -27,6 +29,9 @@ internal class TombstoneEventEnhancer( "owner" to owner ) else mapOf("path" to path) event.addMetadata("Open FileDescriptors", fd.toString(), fdInfo) + }, + { log -> + event.addMetadata("Log Messages", "Log Messages", log) } ) } catch (ex: Exception) { diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneParser.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneParser.kt index 9040edbb88..b821ad5680 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneParser.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/TombstoneParser.kt @@ -16,8 +16,10 @@ internal class TombstoneParser( fun parse( exitInfo: ApplicationExitInfo, listOpenFds: Boolean, + includeLogcat: Boolean, threadConsumer: (BugsnagThread) -> Unit, - fileDescriptorConsumer: (Int, String, String) -> Unit + fileDescriptorConsumer: (Int, String, String) -> Unit, + logcatConsumer: (String) -> Unit ) { try { val trace: Tombstone = exitInfo.traceInputStream?.use { @@ -28,11 +30,42 @@ internal class TombstoneParser( if (listOpenFds) { extractTombstoneFd(trace.openFdsList, fileDescriptorConsumer) } + + if (includeLogcat) { + extractTombstoneLogBuffers(trace.logBuffersList, logcatConsumer) + } } catch (ex: Throwable) { logger.w("Tombstone input stream threw an Exception", ex) } } + private fun extractTombstoneLogBuffers( + logBuffersList: List, + logcatConsumer: (String) -> Unit + ) { + val newLogList = mutableListOf() + var priorityType: String + logBuffersList.forEach { logs -> + logs.logsList.forEach { + priorityType = when (it.priority) { + 2 -> "VERBOSE" + 3 -> "DEBUG" + 4 -> "INFO" + 5 -> "WARN" + 6 -> "ERROR" + 7 -> "ASSERT" + else -> it.priority.toString() + } + + newLogList.add( + 0, + "\n${it.timestamp} ${it.tid} ${it.tag} $priorityType ${it.message}" + ) + } + } + logcatConsumer(newLogList.toString()) + } + private fun extractTombstoneFd( fdsList: List, fDConsumer: (Int, String, String) -> Unit diff --git a/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneEventEnhancerTest.kt b/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneEventEnhancerTest.kt index ddf5a91408..b2507c3b88 100644 --- a/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneEventEnhancerTest.kt +++ b/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneEventEnhancerTest.kt @@ -3,6 +3,7 @@ package com.bugsnag.android import android.app.ApplicationExitInfo import com.bugsnag.android.internal.ImmutableConfig import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test import org.mockito.Mockito.mock @@ -13,7 +14,11 @@ internal class TombstoneEventEnhancerTest { private val logger = mock(Logger::class.java) - private val tombstoneEventEnhancer = TombstoneEventEnhancer(logger, true) + private val tombstoneEventEnhancer = TombstoneEventEnhancer( + logger = logger, + listOpenFds = true, + includeLogcat = true + ) @Test fun testTombstoneEnhancer() { @@ -54,5 +59,9 @@ internal class TombstoneEventEnhancerTest { val firstFd = event.getMetadata("Open FileDescriptors")!!["0"] as Map<*, *> assertEquals("/dev/null", firstFd["path"]) assertNull(firstFd["owner"]) + + val logMetadata = event.getMetadata("Log Messages") + val logMessage = logMetadata!!["Log Messages"] + assertNotNull(logMessage) } } diff --git a/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneParserTest.kt b/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneParserTest.kt index 242627faf9..a09aa23e4e 100644 --- a/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneParserTest.kt +++ b/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/TombstoneParserTest.kt @@ -24,17 +24,27 @@ internal class TombstoneParserTest { `when`(exitInfo.traceInputStream).thenReturn(file) val threads = mutableListOf() val fileDescriptors = ArrayList>() - TombstoneParser(logger).parse(exitInfo, true, { thread -> - threads.add(thread) - }, { fd, path, owner -> - fileDescriptors.add( - mapOf( - "fd" to fd, - "path" to path, - "owner" to owner, + val logList = mutableListOf() + TombstoneParser(logger).parse( + exitInfo = exitInfo, + listOpenFds = true, + includeLogcat = true, + threadConsumer = { thread -> + threads.add(thread) + }, + fileDescriptorConsumer = { fd, path, owner -> + fileDescriptors.add( + mapOf( + "fd" to fd, + "path" to path, + "owner" to owner, + ) ) - ) - }) + }, + logcatConsumer = { logMessage -> + logList.add(0, logMessage) + } + ) assertEquals("30640", threads.first().id) assertEquals("30639", threads.last().id) @@ -53,6 +63,8 @@ internal class TombstoneParserTest { assertEquals(0, firstFileDescriptor["fd"]) assertEquals("/dev/null", firstFileDescriptor["path"]) assertEquals("", firstFileDescriptor["owner"]) + + assertEquals(1, logList.size) } @Test @@ -60,20 +72,31 @@ internal class TombstoneParserTest { `when`(exitInfo.traceInputStream).thenReturn(null) val threads = mutableListOf() val fileDescriptors = ArrayList>() - TombstoneParser(logger).parse(exitInfo, true, { thread -> - threads.add(thread) - }, { fd, path, owner -> - fileDescriptors.add( - mapOf( - "fd" to fd, - "path" to path, - "owner" to owner, + val logList = mutableListOf() + TombstoneParser(logger).parse( + exitInfo = exitInfo, + listOpenFds = true, + includeLogcat = true, + threadConsumer = { thread -> + threads.add(thread) + }, + fileDescriptorConsumer = { fd, path, owner -> + fileDescriptors.add( + mapOf( + "fd" to fd, + "path" to path, + "owner" to owner, + ) ) - ) - }) + }, + logcatConsumer = { logMessage -> + logList.add(0, logMessage) + } + ) verify(exitInfo, times(1)).traceInputStream assertEquals(0, threads.size) assertEquals(0, fileDescriptors.size) + assertEquals(0, logList.size) } @Test @@ -82,19 +105,30 @@ internal class TombstoneParserTest { `when`(exitInfo.traceInputStream).thenReturn(junkData.inputStream()) val threads = mutableListOf() val fileDescriptors = ArrayList>() - TombstoneParser(logger).parse(exitInfo, true, { thread -> - threads.add(thread) - }, { fd, path, owner -> - fileDescriptors.add( - mapOf( - "fd" to fd, - "path" to path, - "owner" to owner, + val logList = mutableListOf() + TombstoneParser(logger).parse( + exitInfo = exitInfo, + listOpenFds = true, + includeLogcat = true, + threadConsumer = { thread -> + threads.add(thread) + }, + fileDescriptorConsumer = { fd, path, owner -> + fileDescriptors.add( + mapOf( + "fd" to fd, + "path" to path, + "owner" to owner, + ) ) - ) - }) + }, + logcatConsumer = { logMessage -> + logList.add(0, logMessage) + } + ) verify(exitInfo, times(1)).traceInputStream assertEquals(0, threads.size) assertEquals(0, fileDescriptors.size) + assertEquals(0, logList.size) } }