Crash Reporting and Debugging in Production
Whistl uses comprehensive crash reporting to quickly identify and fix issues. This technical guide explains crash symbolication, error grouping, release tracking, performance monitoring, and how Whistl maintains 99.9% crash-free sessions.
Why Crash Reporting Matters
Crashes directly impact user trust and safety:
- Protection gaps: Crashed app = no protection
- User trust: Frequent crashes erode confidence
- Data loss: Unsaved data lost on crash
- App store ratings: Crashes affect reviews
- Debugging: Production issues hard to reproduce locally
Whistl targets 99.9% crash-free sessions through proactive monitoring.
Crash Reporting Architecture
Whistl uses Sentry for comprehensive error tracking:
Integration Overview
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Client │ │ Sentry │ │ Team │
│ App │───▶│ Servers │───▶│ Dashboard │
│ │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
│ Crash Report │ Alert │
│ - Stack trace │ - Email │
│ - Breadcrumbs │ - Slack │
│ - Context │ - PagerDuty │
│ - User info │ │
│ │ │
▼ ▼ ▼
Capture Process Triage
Report Group Fix
iOS Crash Reporting
Sentry SDK captures crashes and errors on iOS:
Sentry Configuration
import Sentry
class CrashReporting {
static func start() {
SentrySDK.start { options in
options.dsn = "https://xxx@o123456.ingest.sentry.io/123456"
// Sample rate for transactions (performance)
options.tracesSampleRate = 0.1 // 10%
// Sample rate for profiling
options.profilesSampleRate = 0.1
// Enable automatic breadcrumbs
options.enableAutoSessionTracking = true
options.sessionTrackingIntervalMillis = 30000
// Attach stack trace to non-fatal errors
options.attachStacktrace = true
// Maximum breadcrumbs
options.maxBreadcrumbs = 100
// Before send hook for filtering
options.beforeSend = { event in
// Filter out sensitive data
event.user?.email = nil
event.user?.username = nil
return event
}
}
}
}
Manual Error Reporting
// Capture non-fatal errors
do {
try performRiskyOperation()
} catch {
SentrySDK.capture(error: error) { scope in
scope.setLevel(.warning)
scope.setTag(value: "network", key: "category")
scope.setExtra(value: endpoint, key: "api_endpoint")
}
}
// Capture messages
SentrySDK.capture(message: "Unexpected state") { scope in
scope.setLevel(.info)
scope.setContext(value: ["state": currentState], key: "debug")
}
// Capture NSException
SentrySDK.capture(exception: exception)
Breadcrumbs
// Add breadcrumbs for context
SentrySDK.addBreadcrumb(crumb: Breadcrumb(level: .info, category: "navigation")) {
$0.message = "Navigated to transaction detail"
$0.data = ["transaction_id": transaction.id]
}
SentrySDK.addBreadcrumb(crumb: Breadcrumb(level: .info, category: "network")) {
$0.message = "API Request"
$0.data = [
"method": "POST",
"url": "/api/transactions",
"status": 200,
"duration_ms": 234
]
}
// Breadcrumbs automatically included in crash reports
// Shows what happened before the crash
Android Crash Reporting
Sentry SDK for Android captures crashes and ANRs:
Sentry Configuration (Android)
import io.sentry.android.core.SentryAndroid
class Application : Application() {
override fun onCreate() {
super.onCreate()
SentryAndroid.init(this) { options ->
options.dsn = "https://xxx@o123456.ingest.sentry.io/123456"
// Enable ANR (Application Not Responding) tracking
options.isEnableANRTracking = true
// Sample rate for transactions
options.tracesSampleRate = 0.1
// Enable profiling
options.profilesSampleRate = 0.1
// Attach threads to events
options.isAttachThreads = true
// Before send callback
options.beforeSend = SentryOptions.BeforeSendCallback { event, hint ->
// Filter sensitive data
event.user?.email = null
event.user?.username = null
event
}
}
}
}
Manual Error Reporting (Android)
import io.sentry.Sentry
// Capture exception
try {
performRiskyOperation()
} catch (e: Exception) {
Sentry.captureException(e) { scope ->
scope.setLevel(SentryLevel.WARNING)
scope.setTag("category", "network")
scope.setExtra("api_endpoint", endpoint)
}
}
// Capture message
Sentry.captureMessage("Unexpected state") { scope ->
scope.setLevel(SentryLevel.INFO)
scope.setContexts("debug", mapOf("state" to currentState))
}
// Add breadcrumb
Sentry.addBreadcrumb { breadcrumb ->
breadcrumb.category = "navigation"
breadcrumb.message = "Navigated to transaction detail"
breadcrumb.setData("transaction_id", transaction.id)
}
Crash Symbolication
Raw crash reports contain memory addresses that need symbolication:
dSYM Files (iOS)
# Upload dSYM to Sentry
sentry-cli upload-dsym \
--org whistl \
--project ios \
./build/Products/Release-iphoneos/Whistl.app.dSYM
# Or configure in Xcode Build Phase
sentry-cli upload-dsym \
--org whistl \
--project ios
ProGuard Mapping (Android)
# Sentry automatically uploads mapping files
# Configure in build.gradle
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
# Sentry Gradle plugin handles upload automatically
sentry {
autoUploadProguardMapping = true
}
Symbolicated Crash Report
Fatal Exception: NSInvalidArgumentException
0 CoreFoundation -[NSDictionary objectForKey:]
1 Whistl TransactionViewModel.updateState()
(TransactionViewModel.swift:142)
2 Whistl TransactionView.onAppear()
(TransactionView.swift:67)
3 SwiftUI View.onAppear()
4 libdispatch _dispatch_call_block_and_release
Context:
- Device: iPhone 14 Pro
- iOS: 17.2.1
- App Version: 1.8.2 (234)
- User ID: usr-abc123 (anonymized)
- Free Storage: 2.3 GB
- Battery Level: 45%
Error Grouping
Similar crashes are grouped together:
Grouping Rules
- Same exception type: NullPointerException grouped together
- Same stack trace: Same location in code
- Custom fingerprints: Override default grouping
Custom Grouping
SentrySDK.configureScope { scope in
// Set fingerprint for custom grouping
scope.setFingerprint(["api_error", endpoint, statusCode])
}
// All API errors for same endpoint+status grouped together
// Even if error message differs
Release Tracking
Crashes are tracked by app version:
Release Configuration
// Set release information
SentrySDK.configureScope { scope in
scope.setDist(BuildNumber.current)
// e.g., "234"
}
// Sentry automatically detects release from:
// - iOS: CFBundleVersion in Info.plist
// - Android: versionCode in build.gradle
// View crashes by release in dashboard:
// - v1.8.2 (234): 0.1% crash rate
// - v1.8.1 (230): 0.3% crash rate
// - v1.8.0 (225): 0.2% crash rate
Performance Monitoring
Beyond crashes, Whistl tracks performance:
Transaction Tracing
// Start transaction
let transaction = SentrySDK.startTransaction(
name: "App Launch",
operation: "app.launch",
bindToScope: true
)
// Create child spans
let dbSpan = transaction.startChild(
operation: "db.query",
description: "Fetch transactions"
)
// ... perform query
dbSpan.finish()
let networkSpan = transaction.startChild(
operation: "http.request",
description: "GET /api/transactions"
)
// ... perform request
networkSpan.finish()
// Finish transaction
transaction.finish()
Performance Metrics
| Metric | P50 | P75 | P90 | P99 |
|---|---|---|---|---|
| App Launch (cold) | 1.2s | 1.5s | 2.1s | 3.4s |
| Transaction List Load | 230ms | 340ms | 520ms | 890ms |
| ML Inference | 5ms | 7ms | 10ms | 18ms |
| DNS Block Decision | <1ms | <1ms | 1ms | 2ms |
Alerting
Team is alerted to critical issues:
Alert Configuration
- New issue: Slack notification immediately
- Regression: Email when fixed issue reappears
- Volume spike: PagerDuty when crash rate spikes
- Release issues: Alert when new release has high crash rate
Alert Rules
# Sentry Alert Rules
{
"name": "High Crash Rate",
"conditions": [
{"id": "sentry.rules.conditions.event_frequency", "value": 100, "comparison": 100}
],
"filters": [
{"id": "sentry.rules.filters.level", "level": "fatal"}
],
"actions": [
{"id": "sentry.rules.actions.notify_event_service", "service": "slack"},
{"id": "sentry.rules.actions.notify_event_service", "service": "pagerduty"}
]
}
Privacy in Crash Reports
Crash reports are scrubbed of sensitive data:
Data Scrubbing
- User IDs: Anonymized before sending
- Email/Phone: Removed from all fields
- Financial data: Transaction amounts removed
- Location: Coarse location only (city level)
- Custom data: Reviewed before enabling
Crash-Free Metrics
Whistl maintains excellent crash-free rates:
Crash-Free Sessions
| Platform | Target | Actual |
|---|---|---|
| iOS | 99.9% | 99.94% |
| Android | 99.9% | 99.91% |
| Overall | 99.9% | 99.93% |
Conclusion
Comprehensive crash reporting enables Whistl to quickly identify and fix issues before they impact many users. Through Sentry integration, automatic symbolication, performance monitoring, and privacy-conscious reporting, Whistl maintains 99.9%+ crash-free sessions.
Every crash is an opportunity to improve—Whistl treats each one as a priority fix.
Experience Reliable Protection
Whistl maintains 99.9% crash-free sessions through comprehensive monitoring. Download free and experience reliable protection.
Download Whistl FreeRelated: API Rate Limiting | A/B Testing Infrastructure | Privacy-Compliant Analytics