From 64ba4cbdcc1b5e46e4c0009fa75536ce09c77173 Mon Sep 17 00:00:00 2001 From: YYChen01988 Date: Tue, 19 Nov 2024 15:11:44 +0000 Subject: [PATCH] feat(ExitInfo)added DiscardEventCallback --- .../java/com/bugsnag/android/EventStore.kt | 18 +++++++++ .../detekt-baseline.xml | 2 + .../android/ApplicationExitInfoMatcher.kt | 40 +++++++++++++++++++ .../bugsnag/android/BugsnagExitInfoPlugin.kt | 31 ++++++++------ .../com/bugsnag/android/ExitInfoCallback.kt | 18 ++------- .../com/bugsnag/android/InternalHooks.java | 6 ++- .../bugsnag/android/ExitInfoCallbackTest.kt | 2 +- 7 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ApplicationExitInfoMatcher.kt diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt index 74da941fa5..12174aaa8c 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.kt @@ -41,6 +41,7 @@ internal class EventStore( override val logger: Logger var onEventStoreEmptyCallback: () -> Unit = {} + var onDiscardEventCallback: (EventPayload) -> Unit = {} private var isEmptyEventCallbackCalled: Boolean = false /** @@ -56,6 +57,7 @@ internal class EventStore( TaskType.ERROR_REQUEST, Runnable { flushLaunchCrashReport() + findStoredFiles().map { discardEvents(it) } notifyEventQueueEmpty() } ) @@ -141,6 +143,7 @@ internal class EventStore( logger.d("No regular events to flush to Bugsnag.") } flushReports(storedFiles) + storedFiles.map { discardEvents(it) } notifyEventQueueEmpty() } ) @@ -196,11 +199,13 @@ internal class EventStore( logger.w( "Discarding over-sized event (${eventFile.length()}) after failed delivery" ) + discardEvents(eventFile) deleteStoredFiles(setOf(eventFile)) } else if (isTooOld(eventFile)) { logger.w( "Discarding historical event (from ${getCreationDate(eventFile)}) after failed delivery" ) + discardEvents(eventFile) deleteStoredFiles(setOf(eventFile)) } else { cancelQueuedFiles(setOf(eventFile)) @@ -273,6 +278,19 @@ internal class EventStore( } } + private fun discardEvents(eventFile: File) { + val eventFilenameInfo = fromFile(eventFile, config) + onDiscardEventCallback( + EventPayload( + eventFilenameInfo.apiKey, + null, + eventFile, + notifier, + config + ) + ) + } + companion object { private const val LAUNCH_CRASH_TIMEOUT_MS: Long = 2000 val EVENT_COMPARATOR: Comparator = Comparator { lhs, rhs -> diff --git a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml index 43db92ae23..4662598825 100644 --- a/bugsnag-plugin-android-exitinfo/detekt-baseline.xml +++ b/bugsnag-plugin-android-exitinfo/detekt-baseline.xml @@ -9,7 +9,9 @@ MagicNumber:BugsnagExitInfoPlugin.kt$BugsnagExitInfoPlugin$100 MagicNumber:TraceParser.kt$TraceParser$16 MagicNumber:TraceParser.kt$TraceParser$3 + MaxLineLength:ExitInfoCallback.kt$ExitInfoCallback$val MaxLineLength:ExitInfoCallback.kt$ExitInfoCallback$val allExitInfo: List<ApplicationExitInfo> = am.getHistoricalProcessExitReasons(context.packageName, 0, MAX_EXIT_INFO) + MaxLineLength:ExitInfoCallbackTest.kt$ExitInfoCallbackTest$exitInfoCallback = ExitInfoCallback(context, nativeEnhancer, anrEventEnhancer, null, ApplicationExitInfoMatcher(context, 100)) MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so " MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so (Java_com_example_bugsnag_android_BaseCrashyActivity_anrFromCXX+20" MaxLineLength:TraceParserInvalidStackframesTest.kt$TraceParserInvalidStackframesTest.Companion$"#01 pc 0000000000000c5c /data/app/~~sKQbJGqVJA5glcnvEjZCMg==/com.example.bugsnag.android-fVuoJh5GpAL7sRAeI3vjSw==/lib/arm64/libentrypoint.so (Java_com_example_bugsnag_android_BaseCrashyActivity_anrFromCXX+20) (" diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ApplicationExitInfoMatcher.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ApplicationExitInfoMatcher.kt new file mode 100644 index 0000000000..ef35b09376 --- /dev/null +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ApplicationExitInfoMatcher.kt @@ -0,0 +1,40 @@ +package com.bugsnag.android + +import android.app.ActivityManager +import android.app.ApplicationExitInfo +import android.content.Context +import android.os.Build +import androidx.annotation.RequiresApi + +@RequiresApi(Build.VERSION_CODES.R) +internal class ApplicationExitInfoMatcher( + private val context: Context, + private val pid: Int +) { + fun matchExitInfo(event: Event): ApplicationExitInfo? { + val am: ActivityManager = + context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val allExitInfo: List = + am.getHistoricalProcessExitReasons(context.packageName, 0, MAX_EXIT_INFO) + val sessionIdBytes: ByteArray = + event.session?.id?.toByteArray() ?: return null + val exitInfo: ApplicationExitInfo = + findExitInfoBySessionId(allExitInfo, sessionIdBytes) + ?: findExitInfoByPid(allExitInfo) ?: return null + return exitInfo + } + + internal fun findExitInfoBySessionId( + allExitInfo: List, + sessionIdBytes: ByteArray + ) = allExitInfo.find { + it.processStateSummary?.contentEquals(sessionIdBytes) == true + } + + internal fun findExitInfoByPid(allExitInfo: List) = + allExitInfo.find { it.pid == pid } + + internal companion object { + private const val MAX_EXIT_INFO = 100 + } +} 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 3d116fb0e8..12767bfc30 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 @@ -27,11 +27,6 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( ) } - val exitInfoCallback = createExitInfoCallback(client) - client.addOnSend(exitInfoCallback) - } - - private fun createExitInfoCallback(client: Client): ExitInfoCallback { val tombstoneEventEnhancer = TombstoneEventEnhancer( client.logger, configuration.listOpenFds, @@ -47,11 +42,17 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( addAllExitInfoAtFirstRun(client, exitInfoPluginStore) exitInfoPluginStore.currentPid = Process.myPid() + val exitInfoMatcher = ApplicationExitInfoMatcher( + context = client.appContext, + pid = exitInfoPluginStore.previousPid + ) + val exitInfoCallback = createExitInfoCallback( client, - exitInfoPluginStore.previousPid, exitInfoPluginStore, + exitInfoPluginStore, tombstoneEventEnhancer, - traceEventEnhancer + traceEventEnhancer, + exitInfoMatcher ) InternalHooks.setEventStoreEmptyCallback(client) { synthesizeNewEvents( @@ -61,7 +62,13 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( traceEventEnhancer ) } - return exitInfoCallback + + InternalHooks.setDiscardEventCallback(client) { eventPayload -> + val exitInfo = eventPayload.event?.let { exitInfoMatcher.matchExitInfo(it) } + exitInfo?.let { exitInfoPluginStore.addExitInfoKey(ExitInfoKey(exitInfo)) } + } + + client.addOnSend(exitInfoCallback) } private fun addAllExitInfoAtFirstRun( @@ -85,16 +92,16 @@ class BugsnagExitInfoPlugin @JvmOverloads constructor( private fun createExitInfoCallback( client: Client, - oldPid: Int?, exitInfoPluginStore: ExitInfoPluginStore, tombstoneEventEnhancer: TombstoneEventEnhancer, - traceEventEnhancer: TraceEventEnhancer + traceEventEnhancer: TraceEventEnhancer, + applicationExitInfoMatcher: ApplicationExitInfoMatcher ): ExitInfoCallback = ExitInfoCallback( client.appContext, - oldPid, tombstoneEventEnhancer, traceEventEnhancer, - exitInfoPluginStore + exitInfoPluginStore, + applicationExitInfoMatcher ) private fun synthesizeNewEvents( diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt index 6c401f851d..1d865d5a6e 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/ExitInfoCallback.kt @@ -24,18 +24,18 @@ import androidx.annotation.RequiresApi @RequiresApi(Build.VERSION_CODES.R) internal class ExitInfoCallback( private val context: Context, - private val pid: Int?, private val nativeEnhancer: (Event, ApplicationExitInfo) -> Unit, private val anrEventEnhancer: (Event, ApplicationExitInfo) -> Unit, - private val exitInfoPluginStore: ExitInfoPluginStore? + private val exitInfoPluginStore: ExitInfoPluginStore?, + private val applicationExitInfoMatcher: ApplicationExitInfoMatcher?, ) : OnSendCallback { override fun onSend(event: Event): Boolean { val am: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val allExitInfo: List = am.getHistoricalProcessExitReasons(context.packageName, 0, MAX_EXIT_INFO) val sessionIdBytes: ByteArray = event.session?.id?.toByteArray() ?: return true - val exitInfo: ApplicationExitInfo = findExitInfoBySessionId(allExitInfo, sessionIdBytes) - ?: findExitInfoByPid(allExitInfo) ?: return true + val exitInfo: ApplicationExitInfo = applicationExitInfoMatcher?.findExitInfoBySessionId(allExitInfo, sessionIdBytes) + ?: applicationExitInfoMatcher?.findExitInfoByPid(allExitInfo) ?: return true exitInfoPluginStore?.addExitInfoKey(ExitInfoKey(exitInfo.pid, exitInfo.timestamp)) try { @@ -102,16 +102,6 @@ internal class ExitInfoCallback( else -> "unknown importance (${exitInfo.importance})" } - private fun findExitInfoBySessionId( - allExitInfo: List, - sessionIdBytes: ByteArray - ) = allExitInfo.find { - it.processStateSummary?.contentEquals(sessionIdBytes) == true - } - - private fun findExitInfoByPid(allExitInfo: List) = - allExitInfo.find { it.pid == pid } - internal companion object { private const val MAX_EXIT_INFO = 100 private const val IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170 diff --git a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/InternalHooks.java b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/InternalHooks.java index f2ea073632..7c899a87f5 100644 --- a/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/InternalHooks.java +++ b/bugsnag-plugin-android-exitinfo/src/main/java/com/bugsnag/android/InternalHooks.java @@ -6,6 +6,7 @@ import kotlin.Unit; import kotlin.jvm.functions.Function0; +import kotlin.jvm.functions.Function1; class InternalHooks { @@ -16,6 +17,10 @@ public static void setEventStoreEmptyCallback(Client client, Function0 cal client.eventStore.setOnEventStoreEmptyCallback(callback); } + public static void setDiscardEventCallback(Client client, Function1 callback) { + client.eventStore.setOnDiscardEventCallback(callback); + } + static void deliver(@NonNull Client client, @NonNull Event event) { client.deliveryDelegate.deliver(event); } @@ -37,5 +42,4 @@ static Event createEmptyCrash(long exitInfoTimeStamp) { event.updateSeverityReason(SeverityReason.REASON_SIGNAL); return event; } - } diff --git a/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/ExitInfoCallbackTest.kt b/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/ExitInfoCallbackTest.kt index d3654f2c60..9591624444 100644 --- a/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/ExitInfoCallbackTest.kt +++ b/bugsnag-plugin-android-exitinfo/src/test/java/com/bugsnag/android/ExitInfoCallbackTest.kt @@ -45,7 +45,7 @@ internal class ExitInfoCallbackTest { @Before fun setUp() { - exitInfoCallback = ExitInfoCallback(context, 100, nativeEnhancer, anrEventEnhancer, null) + exitInfoCallback = ExitInfoCallback(context, nativeEnhancer, anrEventEnhancer, null, ApplicationExitInfoMatcher(context, 100)) exitInfos = listOf(exitInfo1) `when`(context.getSystemService(any())).thenReturn(am) `when`(am.getHistoricalProcessExitReasons(any(), anyInt(), anyInt()))