# iOS Crash Reporting ve Analytics: Production Kalite Rehberi
Uygulamanız App Store'da yayınlandıktan sonra asıl iş başlıyor. Kullanıcılarınız hangi hataları yaşıyor? Hangi ekranlarda takılıyor? Hangi özellikler en çok kullanılıyor? Bu soruların cevabı, sağlam bir crash reporting ve analytics altyapısında gizli. Bu rehberde, crash'lerden analytics'e, monitoring'den alerting'e production kalitesinin tüm boyutlarını ele alacağız.
İçindekiler
- Crash Reporting Temelleri
- Symbolication Süreci
- Custom Crash Handler
- Analytics Event Tasarımı
- Funnel ve Cohort Analizi
- Monitoring Dashboard
- Alerting Stratejisi
- Karşılaştırma Tablosu
- ALTIN İPUCU
- Sonuç ve Öneriler
1. Crash Reporting Temelleri
iOS'ta crash'ler iki ana kategoriye ayrılır:
Crash Türleri
Tür | Sebep | Yakalanabilir mi? | Örnek |
|---|---|---|---|
**Mach Exception** | Kernel seviye hata | Signal handler ile | EXC_BAD_ACCESS, EXC_CRASH |
**Unix Signal** | POSIX sinyal | Signal handler ile | SIGABRT, SIGSEGV, SIGBUS |
**NSException** | ObjC exception | try-catch ile | NSInvalidArgumentException |
**Swift Error** | Swift runtime | do-catch ile | fatalError, precondition |
**Watchdog** | Timeout (Ana thread) | Hayır | App launch > 20sn |
**OOM** | Bellek yetersiz | Dolaylı | Jetsam (sistem kill) |
**Hang** | Main thread blok | MetricKit ile | 250ms+ blok |
Crash Lifecycle
swift
1// MARK: - Crash Reporter Setup2final class CrashReporter {3 static let shared = CrashReporter()4 5 private var previousExceptionHandler: NSUncaughtExceptionHandler?6 private let queue = DispatchQueue(label: "com.app.crash-reporter", qos: .utility)7 private let storage: CrashStorage8 9 init(storage: CrashStorage = FileBasedCrashStorage()) {10 self.storage = storage11 }12 13 func setup() {14 // 1. Onceki handler'i sakla (zincirleme)15 previousExceptionHandler = NSGetUncaughtExceptionHandler()16 17 // 2. Kendi handler'imizi kur18 NSSetUncaughtExceptionHandler { exception in19 CrashReporter.shared.handleException(exception)20 }21 22 // 3. Signal handler'lar23 setupSignalHandlers()24 25 // 4. Onceki oturumdaki crash'leri gonder26 sendPendingReports()27 }28 29 private func handleException(_ exception: NSException) {30 let report = CrashReport(31 name: exception.name.rawValue,32 reason: exception.reason ?? "Unknown",33 stackTrace: exception.callStackSymbols,34 timestamp: Date(),35 appState: captureAppState(),36 deviceInfo: captureDeviceInfo()37 )38 39 // Senkron yaz (crash aninda async guvenilmez)40 storage.writeSynchronously(report)41 42 // Onceki handler'a ilet43 previousExceptionHandler?(exception)44 }45 46 private func setupSignalHandlers() {47 let signals: [Int32] = [SIGABRT, SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGTRAP]48 for sig in signals {49 signal(sig) { signalNumber in50 let report = CrashReport(51 name: "Signal \(signalNumber)",52 reason: CrashReporter.signalName(signalNumber),53 stackTrace: Thread.callStackSymbols,54 timestamp: Date(),55 appState: CrashReporter.shared.captureAppState(),56 deviceInfo: CrashReporter.shared.captureDeviceInfo()57 )58 CrashReporter.shared.storage.writeSynchronously(report)59 }60 }61 }62 63 private static func signalName(_ signal: Int32) -> String {64 switch signal {65 case SIGABRT: return "SIGABRT"66 case SIGSEGV: return "SIGSEGV"67 case SIGBUS: return "SIGBUS"68 case SIGFPE: return "SIGFPE"69 case SIGILL: return "SIGILL"70 case SIGTRAP: return "SIGTRAP"71 default: return "UNKNOWN"72 }73 }74 75 private func captureAppState() -> AppState {76 AppState(77 memoryUsage: ProcessInfo.processInfo.physicalMemory,78 diskFree: FileManager.default.freeDiskSpace,79 batteryLevel: UIDevice.current.batteryLevel,80 thermalState: ProcessInfo.processInfo.thermalState.rawValue,81 activeScreen: NavigationTracker.shared.currentScreen,82 sessionDuration: SessionManager.shared.duration83 )84 }85 86 private func captureDeviceInfo() -> DeviceInfo {87 DeviceInfo(88 model: UIDevice.current.model,89 osVersion: UIDevice.current.systemVersion,90 appVersion: Bundle.main.appVersion,91 buildNumber: Bundle.main.buildNumber,92 locale: Locale.current.identifier93 )94 }95 96 private func sendPendingReports() {97 queue.async { [weak self] in98 guard let reports = self?.storage.readPending() else { return }99 for report in reports {100 APIClient.shared.send(report) { success in101 if success {102 self?.storage.markSent(report.id)103 }104 }105 }106 }107 }108}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
2. Symbolication Süreci
Crash raporu ham haliyle sadece bellek adresleri içerir. Symbolication, bu adresleri okunabilir fonksiyon adlarına çevirir.
dSYM Yönetimi
swift
1// Build Settings kontrol2// DEBUG_INFORMATION_FORMAT = dwarf-with-dsym3// DWARF_DSYM_FOLDER_PATH = build/4 5// Symbolication komutu (Terminal)6// atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x1000123457 8// MARK: - Build Phase Script (dSYM upload)9// #!/bin/bash10// DSYM_PATH="BUILT_PRODUCTS_DIR/PRODUCT_NAME.app.dSYM"11// if [ -d "PATH_TO_DSYM" ]; then12// zip -r dsym.zip "PATH_TO_DSYM"13// curl -X POST https://api.crashreporter.com/dsym14// -F "[email protected]"15// -F "version=APP_VERSION"16// fi3. Analytics Event Tasarımı
Doğru event tasarımı, anlamlı veri toplamanın anahtarıdır.
Event Naming Convention
swift
1// MARK: - Analytics Event System2protocol AnalyticsEvent {3 var name: String { get }4 var properties: [String: Any] { get }5 var timestamp: Date { get }6}7 8enum AppEvent: AnalyticsEvent {9 // Screen Events10 case screenViewed(name: String)11 case screenExited(name: String, duration: TimeInterval)12 13 // User Actions14 case buttonTapped(name: String, screen: String)15 case searchPerformed(query: String, resultCount: Int)16 case itemSelected(id: String, category: String, position: Int)17 18 // Business Events19 case purchaseStarted(productId: String, price: Decimal)20 case purchaseCompleted(productId: String, price: Decimal, method: String)21 case purchaseFailed(productId: String, error: String)22 23 // Engagement24 case contentShared(type: String, platform: String)25 case notificationReceived(type: String)26 case notificationTapped(type: String, delay: TimeInterval)27 28 var name: String {29 switch self {30 case .screenViewed: return "screen_viewed"31 case .screenExited: return "screen_exited"32 case .buttonTapped: return "button_tapped"33 case .searchPerformed: return "search_performed"34 case .itemSelected: return "item_selected"35 case .purchaseStarted: return "purchase_started"36 case .purchaseCompleted: return "purchase_completed"37 case .purchaseFailed: return "purchase_failed"38 case .contentShared: return "content_shared"39 case .notificationReceived: return "notification_received"40 case .notificationTapped: return "notification_tapped"41 }42 }43 44 var properties: [String: Any] {45 switch self {46 case .screenViewed(let name):47 return ["screen_name": name]48 case .screenExited(let name, let duration):49 return ["screen_name": name, "duration_seconds": duration]50 case .buttonTapped(let name, let screen):51 return ["button_name": name, "screen_name": screen]52 case .purchaseCompleted(let id, let price, let method):53 return ["product_id": id, "price": price, "payment_method": method]54 default:55 return [:]56 }57 }58 59 var timestamp: Date { Date() }60}4. Funnel ve Cohort Analizi
Funnel analizi, kullanıcıların belirli bir akıştaki adımları ne ölçüde tamamladığını gösterir.
Funnel Tracker
swift
1// MARK: - Funnel Tracker2final class FunnelTracker {3 private let analytics: AnalyticsProviding4 private var activeSteps: [String: FunnelStep] = [:]5 6 struct FunnelStep {7 let funnelName: String8 let stepName: String9 let stepIndex: Int10 let startTime: Date11 }12 13 init(analytics: AnalyticsProviding) {14 self.analytics = analytics15 }16 17 func startFunnel(_ name: String, step: String) {18 let funnelStep = FunnelStep(19 funnelName: name,20 stepName: step,21 stepIndex: 0,22 startTime: Date()23 )24 activeSteps[name] = funnelStep25 analytics.track("funnel_step", properties: [26 "funnel": name,27 "step": step,28 "step_index": 029 ])30 }31 32 func advanceFunnel(_ name: String, to step: String) {33 guard let current = activeSteps[name] else { return }34 let next = FunnelStep(35 funnelName: name,36 stepName: step,37 stepIndex: current.stepIndex + 1,38 startTime: Date()39 )40 let stepDuration = Date().timeIntervalSince(current.startTime)41 activeSteps[name] = next42 analytics.track("funnel_step", properties: [43 "funnel": name,44 "step": step,45 "step_index": next.stepIndex,46 "previous_step_duration": stepDuration47 ])48 }49 50 func completeFunnel(_ name: String) {51 guard let current = activeSteps[name] else { return }52 let totalDuration = Date().timeIntervalSince(current.startTime)53 analytics.track("funnel_completed", properties: [54 "funnel": name,55 "total_steps": current.stepIndex + 1,56 "total_duration": totalDuration57 ])58 activeSteps.removeValue(forKey: name)59 }60 61 func dropFunnel(_ name: String, reason: String) {62 guard let current = activeSteps[name] else { return }63 analytics.track("funnel_dropped", properties: [64 "funnel": name,65 "dropped_at_step": current.stepName,66 "step_index": current.stepIndex,67 "reason": reason68 ])69 activeSteps.removeValue(forKey: name)70 }71}5. Monitoring Dashboard
Temel Metrikler
Metrik | Hedef | Alarm Eşiği | Ölçüm Yöntemi |
|---|---|---|---|
**Crash-Free Rate** | >99.5% | <99% | Crash count / DAU |
**ANR Rate** | <0.5% | >1% | Hang count / session |
**Launch Time** | <2sn | >4sn | Time to interactive |
**API Latency (p95)** | <500ms | >1000ms | Network monitoring |
**Memory Peak** | <200MB | >350MB | Memory gauge |
**Disk Usage** | <100MB | >500MB | FileManager |
6. Alerting Stratejisi
Doğru alerting, gece saat 3'te sizi uyandıracak kadar hassas, ama false positive'lerle bunaltmayacak kadar akıllı olmalıdır.
Severity Levels
Seviye | Tepki Süresi | Bildirim | Örnek |
|---|---|---|---|
**P0 - Critical** | 15 dakika | SMS + telefon | Crash rate >5% |
**P1 - High** | 1 saat | Push + email | API tamamen down |
**P2 - Medium** | 4 saat | Email | Crash rate >1% |
**P3 - Low** | Sonraki iş günü | Dashboard | Yeni crash türü |
swift
1import MetricKit2 3// MARK: - MetricKit ile Apple Diagnostik Verisi4final class AppleDiagnosticsSubscriber: NSObject, MXMetricManagerSubscriber {5 static let shared = AppleDiagnosticsSubscriber()6 7 func setup() {8 MXMetricManager.shared.add(self)9 }10 11 func didReceive(_ payloads: [MXMetricPayload]) {12 for payload in payloads {13 // Crash-free users oranini hesapla14 if let crashMetrics = payload.applicationExitMetrics {15 let foregroundCrashes = crashMetrics.foregroundExitData.cumulativeAbnormalExitCount16 let totalForeground = crashMetrics.foregroundExitData.cumulativeNormalAppExitCount + foregroundCrashes17 let crashFreeRate = 1.0 - (Double(foregroundCrashes) / Double(max(totalForeground, 1)))18 AnalyticsService.track("crash_free_rate", value: crashFreeRate)19 }20 }21 }22 23 func didReceive(_ payloads: [MXDiagnosticPayload]) {24 for payload in payloads {25 // Hang, crash ve disk write diagnostikleri26 payload.hangDiagnostics?.forEach { hang in27 CrashReporter.shared.reportHang(duration: hang.hangDuration)28 }29 }30 }31}ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:
Sonuç ve Öneriler
Crash reporting ve analytics, uygulamanızın sağlığını izlemenin iki temel ayağıdır. Crash-free rate hedefinizi belirleyin, doğru event taksonomisi oluşturun ve alerting mekanizmanızı kurun. Unutmayın: ölçemediğiniz şeyi iyileştiremezsiniz.

