docs/snippets/bugsnag-uploader.md
import android.app.Application
import com.bugsnag.android.Bugsnag
import com.bugsnag.android.Configuration
import com.bugsnag.android.ErrorTypes
import com.bugsnag.android.Event
import com.bugsnag.android.ThreadSendPolicy
import shark.HeapAnalysis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.Leak
import shark.LeakTrace
import shark.LeakTraceReference
import shark.LibraryLeak
class BugsnagLeakUploader(applicationContext: Application) {
private val bugsnagClient = Bugsnag.start(
applicationContext,
Configuration("YOUR_BUGSNAG_API_KEY").apply {
enabledErrorTypes = ErrorTypes(
anrs = false,
ndkCrashes = false,
unhandledExceptions = false,
unhandledRejections = false
)
sendThreads = ThreadSendPolicy.NEVER
}
)
fun upload(heapAnalysis: HeapAnalysis) {
when (heapAnalysis) {
is HeapAnalysisSuccess -> {
val allLeakTraces = heapAnalysis
.allLeaks
.toList()
.flatMap { leak ->
leak.leakTraces.map { leakTrace -> leak to leakTrace }
}
if (allLeakTraces.isEmpty()) {
// Track how often we perform a heap analysis that yields no result.
bugsnagClient.notify(NoLeakException()) { event ->
event.addHeapAnalysis(heapAnalysis)
true
}
} else {
allLeakTraces.forEach { (leak, leakTrace) ->
val message = "Memory leak: ${leak.shortDescription}. See LEAK tab."
val exception = leakTrace.asFakeException(message)
bugsnagClient.notify(exception) { event ->
event.addHeapAnalysis(heapAnalysis)
event.addLeak(leak)
event.addLeakTrace(leakTrace)
event.groupingHash = leak.signature
true
}
}
}
}
is HeapAnalysisFailure -> {
// Please file any reported failure to
// https://github.com/square/leakcanary/issues
bugsnagClient.notify(heapAnalysis.exception)
}
}
}
class NoLeakException : RuntimeException()
private fun Event.addHeapAnalysis(heapAnalysis: HeapAnalysisSuccess) {
addMetadata("Leak", "heapDumpPath", heapAnalysis.heapDumpFile.absolutePath)
heapAnalysis.metadata.forEach { (key, value) ->
addMetadata("Leak", key, value)
}
addMetadata("Leak", "analysisDurationMs", heapAnalysis.analysisDurationMillis)
}
private fun Event.addLeak(leak: Leak) {
addMetadata("Leak", "libraryLeak", leak is LibraryLeak)
if (leak is LibraryLeak) {
addMetadata("Leak", "libraryLeakPattern", leak.pattern.toString())
addMetadata("Leak", "libraryLeakDescription", leak.description)
}
}
private fun Event.addLeakTrace(leakTrace: LeakTrace) {
addMetadata("Leak", "retainedHeapByteSize", leakTrace.retainedHeapByteSize)
addMetadata("Leak", "signature", leakTrace.signature)
addMetadata("Leak", "leakTrace", leakTrace.toString())
}
private fun LeakTrace.asFakeException(message: String): RuntimeException {
val exception = RuntimeException(message)
val stackTrace = mutableListOf<StackTraceElement>()
stackTrace.add(StackTraceElement("GcRoot", gcRootType.name, "GcRoot.kt", 42))
for (cause in referencePath) {
stackTrace.add(buildStackTraceElement(cause))
}
exception.stackTrace = stackTrace.toTypedArray()
return exception
}
private fun buildStackTraceElement(reference: LeakTraceReference): StackTraceElement {
val file = reference.owningClassName.substringAfterLast(".") + ".kt"
return StackTraceElement(reference.owningClassName, reference.referenceDisplayName, file, 42)
}
}