Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uploading leaks to Sentry #2119

Open
marandaneto opened this issue Apr 30, 2021 · 5 comments
Open

Uploading leaks to Sentry #2119

marandaneto opened this issue Apr 30, 2021 · 5 comments

Comments

@marandaneto
Copy link

Hey, first of all, big thanks for the lib.

It'd be nice to mention Sentry as a compatible leak uploader as well in the docs: https://square.github.io/leakcanary/recipes/

Steps:

Install the Sentry Android SDK: https://docs.sentry.io/platforms/android/#install

Create the SentryLeakUploader (Recipe):

import io.sentry.Sentry
import io.sentry.SentryEvent
import leakcanary.DefaultOnHeapAnalyzedListener
import leakcanary.OnHeapAnalyzedListener
import shark.HeapAnalysis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.Leak
import shark.LeakTrace
import shark.LibraryLeak

class SentryLeakUploader : OnHeapAnalyzedListener {

    private val defaultLeakListener = DefaultOnHeapAnalyzedListener.create()

    override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
        // Delegate to default behavior (notification and saving result)
        defaultLeakListener.onHeapAnalyzed(heapAnalysis)

        when (heapAnalysis) {
            is HeapAnalysisSuccess -> {
                val allLeakTraces = heapAnalysis
                        .allLeaks
                        .toList()
                        .flatMap { leak ->
                            leak.leakTraces.map { leakTrace -> leak to leakTrace }
                        }

                allLeakTraces.forEach { (leak, leakTrace) ->
                    val exception = FakeReportingException(leak.shortDescription)
                    val event = SentryEvent(exception).apply {
                        val leakContexts = mutableMapOf<String, Any>()
                        addHeapAnalysis(heapAnalysis, leakContexts)
                        addLeak(leak, leakContexts)
                        addLeakTrace(leakTrace, leakContexts)
                        contexts["Leak"] = leakContexts

                        // grouping leaks
                        fingerprints = listOf(leak.signature)
                    }
                    Sentry.captureEvent(event)
                }

            }
            is HeapAnalysisFailure -> {
                // Please file any reported failure to
                // https://github.com/square/leakcanary/issues
                Sentry.captureException(heapAnalysis.exception)
            }
        }
    }

    private fun addHeapAnalysis(heapAnalysis: HeapAnalysisSuccess, leakContexts: MutableMap<String, Any>) {
        leakContexts["heapDumpPath"] = heapAnalysis.heapDumpFile.absolutePath
        heapAnalysis.metadata.forEach { (key, value) ->
            leakContexts[key] = value
        }
        leakContexts["analysisDurationMs"] = heapAnalysis.analysisDurationMillis
    }

    private fun addLeak(leak: Leak, leakContexts: MutableMap<String, Any>) {
        leakContexts["libraryLeak"] = leak is LibraryLeak
        if (leak is LibraryLeak) {
            leakContexts["libraryLeakPattern"] = leak.pattern.toString()
            leakContexts["libraryLeakDescription"] = leak.description
        }
    }

    private fun addLeakTrace(leakTrace: LeakTrace, leakContexts: MutableMap<String, Any>) {
        leakTrace.retainedHeapByteSize?.let {
            leakContexts["retainedHeapByteSize"] = it
        }
        leakContexts["signature"] = leakTrace.signature
        leakContexts["leakTrace"] = leakTrace.toString()
    }

    class FakeReportingException(message: String) : RuntimeException(message)
}

Result:

Screenshot 2021-04-30 at 11 47 19

@pyricau
Copy link
Member

pyricau commented Apr 30, 2021

@marandaneto thx!

I wonder if there's a way to document this without copy pasting all of the code twice.

Maybe all this should move into sample projects..

@marandaneto
Copy link
Author

@pyricau indeed, or move them to a leakcanary gist and link to them, it'd also work.

@anjalsaneen
Copy link

Anyone who wants to log in on firebase can use this code. Here we are converting leak traces to stack traces. Feel free for improvements and suggestions

                 allLeakTraces.forEach { (leak, leakTrace) ->
                    val exception = MemoryLeakException("Memory Leak found in ${leakTrace.leakingObject.className}")
                    val stackTraceElements = Array<StackTraceElement?>(leakTrace.referencePath.size) { null }
                    val referenceMaps = mutableMapOf<String, Any>()

                    leakTrace.referencePath.reversed().forEachIndexed { index, leakTraceReference ->
                        val stackTraceElement = StackTraceElement(
                            leakTraceReference.originObject.className,
                            leakTraceReference.referenceName,
                            leakTraceReference.referenceDisplayName,
                            1
                        )
                        stackTraceElements[index] = stackTraceElement
                    }

                    exception.stackTrace = stackTraceElements
                    FirebaseCrashlytics.getInstance().recordException(exception) // 
                }

                 class MemoryLeakException(message: String) : RuntimeException(message)

Screenshot 2021-10-18 at 1 58 10 PM

@pyricau
Copy link
Member

pyricau commented Dec 15, 2021

This doesn't look as nice in Firebase :)

Heh, some day we should really just build a docker image for a leakcanary backend that can run on cloud.

@msfjarvis
Copy link
Contributor

Updated the Sentry recipe for LeakCanary 2.10

import io.sentry.Sentry
import io.sentry.SentryEvent
import leakcanary.EventListener
import leakcanary.EventListener.Event
import shark.HeapAnalysisSuccess
import shark.Leak
import shark.LeakTrace
import shark.LibraryLeak

object SentryLeakUploader : EventListener {

  override fun onEvent(event: Event) {
    when (event) {
      is Event.HeapAnalysisDone<*> -> {
        if (event.heapAnalysis !is HeapAnalysisSuccess) return
        val heapAnalysis = event.heapAnalysis as HeapAnalysisSuccess
        val allLeakTraces =
          heapAnalysis.allLeaks.toList().flatMap { leak ->
            leak.leakTraces.map { leakTrace -> leak to leakTrace }
          }

        allLeakTraces.forEach { (leak, leakTrace) ->
          val exception = FakeReportingException(leak.shortDescription)
          val sentryEvent =
            SentryEvent(exception).apply {
              val leakContexts = mutableMapOf<String, Any>()
              addHeapAnalysis(heapAnalysis, leakContexts)
              addLeak(leak, leakContexts)
              addLeakTrace(leakTrace, leakContexts)
              contexts["Leak"] = leakContexts
              // grouping leaks
              fingerprints = listOf(leak.signature)
            }
          Sentry.captureEvent(sentryEvent)
        }
      }
      else -> {}
    }
  }

  private fun addHeapAnalysis(
    heapAnalysis: HeapAnalysisSuccess,
    leakContexts: MutableMap<String, Any>
  ) {
    leakContexts["heapDumpPath"] = heapAnalysis.heapDumpFile.absolutePath
    heapAnalysis.metadata.forEach { (key, value) -> leakContexts[key] = value }
    leakContexts["analysisDurationMs"] = heapAnalysis.analysisDurationMillis
  }

  private fun addLeak(leak: Leak, leakContexts: MutableMap<String, Any>) {
    leakContexts["libraryLeak"] = leak is LibraryLeak
    if (leak is LibraryLeak) {
      leakContexts["libraryLeakPattern"] = leak.pattern.toString()
      leakContexts["libraryLeakDescription"] = leak.description
    }
  }

  private fun addLeakTrace(leakTrace: LeakTrace, leakContexts: MutableMap<String, Any>) {
    leakTrace.retainedHeapByteSize?.let { leakContexts["retainedHeapByteSize"] = it }
    leakContexts["signature"] = leakTrace.signature
    leakContexts["leakTrace"] = leakTrace.toString()
  }

  class FakeReportingException(message: String) : RuntimeException(message)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants