CrashesGuide

Mobile Crash Reporting Guide

October 20, 202517 min read
Crash Reporting Guide

Crashes destroy user trust. One crash causes 25% of users to uninstall. Crash reporting is your only window into what happened when your app broke. Learn to detect, capture, analyze, and fix crashes quickly.

Why Crash Reporting Matters

Users won't tell you your app crashed. You'll only know through:

  • • User reviews ("It keeps crashing")
  • • Uninstalls (no feedback)
  • • App Store rating drops
  • • Crash reports (if you implemented them)

The last option is your only way to proactively identify and fix crashes.

The Crash Lifecycle

Understanding the full lifecycle helps you optimize each step:

Crash Occurs → Detection → Capture → Upload → Analysis → Symbolication → Prioritization → Assignment → Resolution → Verification

Each step is critical for effective crash management.

Crash Types

Native Crashes: Segmentation faults, null pointer dereferences, memory corruption

Exceptions: Unhandled exceptions in managed code (Java, Kotlin, Swift)

ANRs: Application Not Responding - when main thread blocks for 5+ seconds

Each type requires different handling and analysis approaches.

Stack Trace Symbolication

Raw crash reports contain memory addresses instead of method names:

0x00012345 MyClass.someMethod()

0x00012346 MyClass.anotherMethod()

Symbolication converts these addresses back to human-readable method names using symbol files. This is critical for understanding crashes.

Understanding Mobile Crashes

Crash vs Error vs Exception

Crash: The entire app stops working and force-closes. User sees the "App stopped working" dialog, all code execution halts, and the app must be restarted.

Error: Something goes wrong, but the app continues. The user may not notice, an error handler catches and responds, and the app recovers.

Exception: A runtime error that would crash if not caught. Similar to errors, but more serious and often indicates a programming mistake that must be handled explicitly.

Common Mobile Crash Types

Native Crashes (30%): Memory access violations, segmentation faults, stack overflows

Unhandled Exceptions (40%): Null pointer dereferences, array out of bounds, type cast errors

Out of Memory (15%): Heap exhaustion, memory leaks

Watchdog Terminations (10%): Main thread blocked, high CPU usage

Other (5%): Thread deadlocks, system permissions violations

iOS Crash Reporting Implementation

Implement custom crash handlers in Swift to capture both uncaught exceptions and native signals:

import Foundation

class iOSCrashHandler {
    static let shared = iOSCrashHandler()

    func setupCrashHandler() {
        NSSetUncaughtExceptionHandler { exception in
            self.handleException(exception)
        }

        let signals = [SIGABRT, SIGILL, SIGSEGV, SIGFPE, SIGBUS, SIGPIPE]
        for signal in signals {
            signal(signal, signalHandler)
        }
    }

    private func handleException(_ exception: NSException) {
        let crashInfo: [String: Any] = [
            "exception_name": exception.name.rawValue,
            "call_stack": exception.callStackSymbols,
            "timestamp": ISO8601DateFormatter().string(from: Date()),
            "app_version": Bundle.main.appVersion
        ]

        saveCrashReport(crashInfo)
        transmitCrashReport(crashInfo)
        sleep(1)
        abort()
    }
}

Android Crash Reporting Implementation

Set up custom crash handlers in Kotlin to capture uncaught exceptions and ANRs:

import kotlin.system.exitProcess

class AndroidCrashHandler : Thread.UncaughtExceptionHandler {
    private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()

    override fun uncaughtException(thread: Thread, exception: Throwable) {
        try {
            val crashReport = mapOf(
                "timestamp" to System.currentTimeMillis(),
                "thread_name" to thread.name,
                "exception_type" to exception.javaClass.name,
                "message" to exception.message,
                "stack_trace" to getStackTraceString(exception),
                "app_version" to BuildConfig.VERSION_NAME
            )

            saveCrashReport(crashReport)
            transmitCrashReport(crashReport)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        defaultHandler?.uncaughtException(thread, exception)
    }
}

React Native and Flutter Crash Reporting

React Native: Capture JavaScript errors using ErrorUtils.setGlobalHandler() to intercept both fatal and non-fatal errors before the app crashes.

ErrorUtils.setGlobalHandler((error, isFatal) => {
  const crashReport = {
    type: 'javascript',
    isFatal,
    error: error.toString(),
    stack: error.stack,
    timestamp: new Date().toISOString()
  };

  if (isFatal) {
    transmitCrashReport(crashReport);
  } else {
    queueCrashReport(crashReport);
  }
});

Flutter: Handle Dart exceptions and isolate errors for comprehensive crash coverage.

Stack Trace Symbolication

Raw crash reports contain memory addresses instead of method names. Symbolication converts addresses to human-readable method names:

Before Symbolication:

0x1000a2e0c MyApp + 659980

0x1000a2b10 MyApp + 659216

After Symbolication:

PaymentViewController.processPayment() (PaymentViewController.swift:145)

PaymentAPI.submitPayment() (PaymentAPI.swift:89)

Automate symbol uploads in your CI/CD pipeline to ensure every release has symbols for deobfuscation.

ProGuard/R8 Deobfuscation (Android)

When you release a ProGuard-obfuscated app, method names become obscured (e.g., a(), b()). Store mapping files and use them to deobfuscate stack traces:

// build.gradle
android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

// Keep and upload mapping.txt for deobfuscation

ANR Detection (Android)

ANRs (Application Not Responding) occur when the main thread is blocked for 5+ seconds. Detect and report them:

class ANRDetector(private val context: Context) {
    private val mainHandler = Handler(Looper.getMainLooper())

    fun startMonitoring() {
        val watchdogThread = Thread {
            while (true) {
                val isResponsive = AtomicBoolean(false)
                mainHandler.post { isResponsive.set(true) }
                Thread.sleep(5000)

                if (!isResponsive.get()) {
                    reportANR()
                }
            }
        }
        watchdogThread.isDaemon = true
        watchdogThread.start()
    }
}

Crash Analytics & Prioritization

Key Metrics:

  • • Crash-free sessions: % of sessions without crashes
  • • Affected users: How many unique users hit this crash
  • • Occurrence rate: Is it getting worse or better
  • • OS/Device distribution: Which devices are affected
  • • User retention impact: Does this crash cause uninstalls

Crash Management Workflow

  1. 1. Detect new crashes in your dashboard
  2. 2. Prioritize based on affected users and frequency
  3. 3. Assign to appropriate developer
  4. 4. Reproduce locally or in QA
  5. 5. Fix the root cause
  6. 6. Test the fix thoroughly
  7. 7. Release in next app version
  8. 8. Monitor crash rate trending down

Best Practices

  • Automate symbolication: Upload symbols with each release
  • Set up alerts: Get notified of new critical crashes
  • Track trends: Monitor crash-free session percentage
  • Integrate with issue tracker: Create tickets from crashes
  • Include context: Collect logs alongside crash data
  • Respect privacy: Never log PII in crash reports

Error Monitoring: Catching Problems Before Crashes

The best crashes to fix are the ones that never happen. Use error monitoring to catch and handle exceptions before they crash your app:

// Differentiate between errors and crashes
do {
    try processPayment()
} catch let error as PaymentError {
    logger.error("Payment failed",
        properties: ["error": error.message])
    showErrorAlert("Payment failed. Retry?")
} catch {
    logger.error("Unexpected error",
        properties: ["error": error.localizedDescription])
    throw error  // Let crash handler catch
}

Monitor patterns of errors that precede crashes—often, errors can be fixed to prevent crashes altogether.

Advanced Crash Investigation

Understanding Stack Traces: A detailed stack trace tells the complete story of what was happening when the crash occurred.

Example Stack Trace Analysis:

Thread: main | Time: 2025-01-20 14:32:15 UTC

0 PaymentProcessor.processPayment() → line 145: card?.validate()

1 PaymentViewController.submitPaymentForm() → line 89

2 UIButton.onTapped() → line 23

Root cause: The card object is nil, causing a null pointer exception. The crash likely originated from failed network request handling.

Breadcrumbs: Include detailed logs of events before the crash—network activity, user actions, state changes. This context is invaluable for diagnosis.

Tool Comparison: Crash Reporting Solutions

Feature Crashlytics Sentry Bugsnag Logtrics
iOS Support
Android Support
Native Crashes
Crash Clustering
Integrated with Logs
Integrated Analytics

Logtrics for Crash Reporting

Logtrics provides AI-powered crash reporting that not only captures crashes but automatically analyzes them to provide root cause analysis and actionable fixes—connecting crashes with complete context including logs, user analytics, and device information.

  • AI Root Cause Analysis: Automatically identify what caused the crash with intelligent insights and step-by-step fixes
  • Complete Context: See logs and app events that led to the crash
  • Integrated Analytics: Understand user impact and identify patterns
  • Intelligent Grouping: Automatically group similar crashes for faster diagnosis
  • Real-time Alerts: Get notified of critical crashes immediately
  • Privacy-First: On-device processing respects user privacy

Join our Early Access

Key Takeaways

  1. 1. Crashes are inevitable – Plan for them with comprehensive crash reporting
  2. 2. Context is everything – Capture device state, user actions, and breadcrumbs
  3. 3. Symbolicate early, symbolicate often – Automate symbol uploads
  4. 4. Segment and prioritize – Not all crashes deserve equal attention
  5. 5. Close the loop – Track crashes from detection to fix to verification

Conclusion

Crash reporting is essential for maintaining app quality and user trust. By implementing comprehensive crash monitoring, automated symbolication, and a robust crash management workflow, you can quickly identify and resolve issues before they damage your app's reputation. The goal isn't to prevent all crashes—it's to find and fix them faster than users find and delete your app.

Ready to Implement Comprehensive Crash Reporting?

Logtrics provides AI-powered mobile app observability with intelligent crash analysis, automated session summaries, logging, analytics, and performance monitoring in one platform.

Why Logtrics?

  • AI Root Cause Analysis - Get automated crash analysis with actionable fixes
  • AI Session Summary - Understand user sessions with intelligent insights
  • ✓ Unified platform (logs + crashes + analytics + monitoring)
  • ✓ Automatic crash detection and symbolication
  • ✓ 365-day data retention
  • ✓ Real-time crash dashboards and alerts
  • ✓ iOS, Android, React Native, Flutter SDKs
  • ✓ Privacy-first with on-device processing

Get Started Free

✓ No Credit Card Required

  • • 10K events/month included
  • • 1 team member, 1 app
  • • 3-day retention
  • • Full feature access