From 00a141f518177dc7592b6a45987b03ed7bc8e604 Mon Sep 17 00:00:00 2001 From: "yingying.chen" Date: Tue, 19 Sep 2023 09:40:38 +0100 Subject: [PATCH] feature(exitInfo) add file descriptors to metadata tests --- .../detekt-baseline.xml | 1 - .../bugsnag/android/BugsnagExitInfoPlugin.kt | 3 +- .../bugsnag/android/TombstoneEventEnhancer.kt | 26 ++++++--- .../com/bugsnag/android/TombstoneParser.kt | 17 +++++- .../android/TombstoneEventEnhancerTest.kt | 7 ++- .../bugsnag/android/TombstoneParserTest.kt | 54 +++++++++++++++---- 6 files changed, 87 insertions(+), 21 deletions(-) diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml index fa008ab2b7..5d1f9a9cde 100644 --- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml +++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml @@ -24,7 +24,6 @@ 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 add the list of open FDs to correlated reports */ private val listOpenFds: Boolean = true 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 b7fab82586..179a3ce391 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 @@ -39,9 +39,10 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( } ) } + exitInfoCallback = ExitInfoCallback( client.appContext, - TombstoneEventEnhancer(client.logger), + TombstoneEventEnhancer(client.logger, listOpenFds), 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 3fa203e2ee..20c775607d 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 @@ -6,17 +6,29 @@ import androidx.annotation.RequiresApi import com.bugsnag.android.Thread as BugsnagThread internal class TombstoneEventEnhancer( - private val logger: Logger + private val logger: Logger, + private val listOpenFds: Boolean ) : (Event, ApplicationExitInfo) -> Unit { @RequiresApi(Build.VERSION_CODES.R) override fun invoke(event: Event, exitInfo: ApplicationExitInfo) { try { - TombstoneParser(logger).parse(exitInfo) { thread -> - mergeThreadIntoEvent( - thread, - event - ) - } + TombstoneParser(logger).parse( + exitInfo, + listOpenFds, + threadConsumer = { thread -> + mergeThreadIntoEvent( + thread, + event + ) + }, + { fd, path, owner -> + val fdInfo = if (owner.isNotEmpty()) mapOf( + "path" to path, + "owner" to owner + ) else mapOf("path" to path) + event.addMetadata("Open FileDescriptors", fd.toString(), fdInfo) + } + ) } catch (ex: Exception) { logger.w("could not parse tombstone file", ex) } 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 4968f268dc..9040edbb88 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 @@ -15,18 +15,33 @@ internal class TombstoneParser( @RequiresApi(Build.VERSION_CODES.R) fun parse( exitInfo: ApplicationExitInfo, - threadConsumer: (BugsnagThread) -> Unit + listOpenFds: Boolean, + threadConsumer: (BugsnagThread) -> Unit, + fileDescriptorConsumer: (Int, String, String) -> Unit ) { try { val trace: Tombstone = exitInfo.traceInputStream?.use { Tombstone.newBuilder().mergeFrom(it).build() } ?: return extractTombstoneThreads(trace.threadsMap.values, threadConsumer) + + if (listOpenFds) { + extractTombstoneFd(trace.openFdsList, fileDescriptorConsumer) + } } catch (ex: Throwable) { logger.w("Tombstone input stream threw an Exception", ex) } } + private fun extractTombstoneFd( + fdsList: List, + fDConsumer: (Int, String, String) -> Unit + ) { + fdsList.forEach { fd -> + fDConsumer(fd.fd, fd.path, fd.owner) + } + } + private fun extractTombstoneThreads( values: Collection, threadConsumer: (BugsnagThread) -> 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 c48c475acf..ddf5a91408 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.assertNull import org.junit.Test import org.mockito.Mockito.mock import org.mockito.Mockito.`when` @@ -12,7 +13,7 @@ internal class TombstoneEventEnhancerTest { private val logger = mock(Logger::class.java) - private val tombstoneEventEnhancer = TombstoneEventEnhancer(logger) + private val tombstoneEventEnhancer = TombstoneEventEnhancer(logger, true) @Test fun testTombstoneEnhancer() { @@ -49,5 +50,9 @@ internal class TombstoneEventEnhancerTest { assertEquals(667096L, testThread.stacktrace.first().lineNumber) assertEquals("__rt_sigtimedwait", testThread.stacktrace.first().method) assertEquals("__start_thread", testThread.stacktrace.last().method) + + val firstFd = event.getMetadata("Open FileDescriptors")!!["0"] as Map<*, *> + assertEquals("/dev/null", firstFd["path"]) + assertNull(firstFd["owner"]) } } 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 300d7430f8..242627faf9 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 @@ -23,9 +23,18 @@ internal class TombstoneParserTest { val file = this.javaClass.getResourceAsStream("/tombstone_01.pb") `when`(exitInfo.traceInputStream).thenReturn(file) val threads = mutableListOf() - TombstoneParser(logger).parse(exitInfo) { - threads.add(it) - } + 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, + ) + ) + }) assertEquals("30640", threads.first().id) assertEquals("30639", threads.last().id) @@ -38,17 +47,33 @@ internal class TombstoneParserTest { assertEquals(8L, firstStackFrame.first().symbolAddress) assertEquals(0L, firstStackFrame.first().loadAddress) assertEquals("01331f74b0bb2cb958bdc15282b8ec7b", firstStackFrame.first().codeIdentifier) + + assertEquals(145, fileDescriptors.size) + val firstFileDescriptor = fileDescriptors.first() + assertEquals(0, firstFileDescriptor["fd"]) + assertEquals("/dev/null", firstFileDescriptor["path"]) + assertEquals("", firstFileDescriptor["owner"]) } @Test fun parseNullInputStream() { `when`(exitInfo.traceInputStream).thenReturn(null) val threads = mutableListOf() - TombstoneParser(logger).parse(exitInfo) { - threads.add(it) - } + 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, + ) + ) + }) verify(exitInfo, times(1)).traceInputStream assertEquals(0, threads.size) + assertEquals(0, fileDescriptors.size) } @Test @@ -56,11 +81,20 @@ internal class TombstoneParserTest { val junkData = ByteArray(128) { it.toByte() } `when`(exitInfo.traceInputStream).thenReturn(junkData.inputStream()) val threads = mutableListOf() - TombstoneParser(logger).parse(exitInfo) { - threads.add(it) - } - + 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, + ) + ) + }) verify(exitInfo, times(1)).traceInputStream assertEquals(0, threads.size) + assertEquals(0, fileDescriptors.size) } }