xref: /aosp_15_r20/external/leakcanary2/docs/snippets/bugsnag-uploader.md (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1```kotlin
2import android.app.Application
3import com.bugsnag.android.Bugsnag
4import com.bugsnag.android.Configuration
5import com.bugsnag.android.ErrorTypes
6import com.bugsnag.android.Event
7import com.bugsnag.android.ThreadSendPolicy
8import shark.HeapAnalysis
9import shark.HeapAnalysisFailure
10import shark.HeapAnalysisSuccess
11import shark.Leak
12import shark.LeakTrace
13import shark.LeakTraceReference
14import shark.LibraryLeak
15
16class BugsnagLeakUploader(applicationContext: Application) {
17
18  private val bugsnagClient = Bugsnag.start(
19    applicationContext,
20    Configuration("YOUR_BUGSNAG_API_KEY").apply {
21      enabledErrorTypes = ErrorTypes(
22        anrs = false,
23        ndkCrashes = false,
24        unhandledExceptions = false,
25        unhandledRejections = false
26      )
27      sendThreads = ThreadSendPolicy.NEVER
28    }
29  )
30
31  fun upload(heapAnalysis: HeapAnalysis) {
32    when (heapAnalysis) {
33      is HeapAnalysisSuccess -> {
34        val allLeakTraces = heapAnalysis
35          .allLeaks
36          .toList()
37          .flatMap { leak ->
38            leak.leakTraces.map { leakTrace -> leak to leakTrace }
39          }
40        if (allLeakTraces.isEmpty()) {
41          // Track how often we perform a heap analysis that yields no result.
42          bugsnagClient.notify(NoLeakException()) { event ->
43            event.addHeapAnalysis(heapAnalysis)
44            true
45          }
46        } else {
47          allLeakTraces.forEach { (leak, leakTrace) ->
48            val message = "Memory leak: ${leak.shortDescription}. See LEAK tab."
49            val exception = leakTrace.asFakeException(message)
50            bugsnagClient.notify(exception) { event ->
51              event.addHeapAnalysis(heapAnalysis)
52              event.addLeak(leak)
53              event.addLeakTrace(leakTrace)
54              event.groupingHash = leak.signature
55              true
56            }
57          }
58        }
59      }
60      is HeapAnalysisFailure -> {
61        // Please file any reported failure to
62        // https://github.com/square/leakcanary/issues
63        bugsnagClient.notify(heapAnalysis.exception)
64      }
65    }
66  }
67
68  class NoLeakException : RuntimeException()
69
70  private fun Event.addHeapAnalysis(heapAnalysis: HeapAnalysisSuccess) {
71    addMetadata("Leak", "heapDumpPath", heapAnalysis.heapDumpFile.absolutePath)
72    heapAnalysis.metadata.forEach { (key, value) ->
73      addMetadata("Leak", key, value)
74    }
75    addMetadata("Leak", "analysisDurationMs", heapAnalysis.analysisDurationMillis)
76  }
77
78  private fun Event.addLeak(leak: Leak) {
79    addMetadata("Leak", "libraryLeak", leak is LibraryLeak)
80    if (leak is LibraryLeak) {
81      addMetadata("Leak", "libraryLeakPattern", leak.pattern.toString())
82      addMetadata("Leak", "libraryLeakDescription", leak.description)
83    }
84  }
85
86  private fun Event.addLeakTrace(leakTrace: LeakTrace) {
87    addMetadata("Leak", "retainedHeapByteSize", leakTrace.retainedHeapByteSize)
88    addMetadata("Leak", "signature", leakTrace.signature)
89    addMetadata("Leak", "leakTrace", leakTrace.toString())
90  }
91
92  private fun LeakTrace.asFakeException(message: String): RuntimeException {
93    val exception = RuntimeException(message)
94    val stackTrace = mutableListOf<StackTraceElement>()
95    stackTrace.add(StackTraceElement("GcRoot", gcRootType.name, "GcRoot.kt", 42))
96    for (cause in referencePath) {
97      stackTrace.add(buildStackTraceElement(cause))
98    }
99    exception.stackTrace = stackTrace.toTypedArray()
100    return exception
101  }
102
103  private fun buildStackTraceElement(reference: LeakTraceReference): StackTraceElement {
104    val file = reference.owningClassName.substringAfterLast(".") + ".kt"
105    return StackTraceElement(reference.owningClassName, reference.referenceDisplayName, file, 42)
106  }
107}
108```
109