Tüm Yazılar
KategoriDevOps
Okuma Süresi
21 dk okuma
Yayın Tarihi
...
Kelime Sayısı
1.639kelime

Kahveni hazırla - bu içerikli bir makale!

Crash reporting altyapısı, symbolication, analytics event tasarımı ve monitoring dashboard kurulumu ile production kalitesini zirveye taşıyın.

iOS Crash Reporting ve Analytics: Production Kalite Rehberi

# 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


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 Setup
2final 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: CrashStorage
8 
9 init(storage: CrashStorage = FileBasedCrashStorage()) {
10 self.storage = storage
11 }
12 
13 func setup() {
14 // 1. Onceki handler'i sakla (zincirleme)
15 previousExceptionHandler = NSGetUncaughtExceptionHandler()
16 
17 // 2. Kendi handler'imizi kur
18 NSSetUncaughtExceptionHandler { exception in
19 CrashReporter.shared.handleException(exception)
20 }
21 
22 // 3. Signal handler'lar
23 setupSignalHandlers()
24 
25 // 4. Onceki oturumdaki crash'leri gonder
26 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 ilet
43 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 in
50 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.duration
83 )
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.identifier
93 )
94 }
95 
96 private func sendPendingReports() {
97 queue.async { [weak self] in
98 guard let reports = self?.storage.readPending() else { return }
99 for report in reports {
100 APIClient.shared.send(report) { success in
101 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 kontrol
2// DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
3// DWARF_DSYM_FOLDER_PATH = build/
4 
5// Symbolication komutu (Terminal)
6// atos -arch arm64 -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp -l 0x100000000 0x100012345
7 
8// MARK: - Build Phase Script (dSYM upload)
9// #!/bin/bash
10// DSYM_PATH="BUILT_PRODUCTS_DIR/PRODUCT_NAME.app.dSYM"
11// if [ -d "PATH_TO_DSYM" ]; then
12// zip -r dsym.zip "PATH_TO_DSYM"
13// curl -X POST https://api.crashreporter.com/dsym
15// -F "version=APP_VERSION"
16// fi

3. Analytics Event Tasarımı

Doğru event tasarımı, anlamlı veri toplamanın anahtarıdır.

Event Naming Convention

swift
1// MARK: - Analytics Event System
2protocol AnalyticsEvent {
3 var name: String { get }
4 var properties: [String: Any] { get }
5 var timestamp: Date { get }
6}
7 
8enum AppEvent: AnalyticsEvent {
9 // Screen Events
10 case screenViewed(name: String)
11 case screenExited(name: String, duration: TimeInterval)
12 
13 // User Actions
14 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 Events
19 case purchaseStarted(productId: String, price: Decimal)
20 case purchaseCompleted(productId: String, price: Decimal, method: String)
21 case purchaseFailed(productId: String, error: String)
22 
23 // Engagement
24 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 Tracker
2final class FunnelTracker {
3 private let analytics: AnalyticsProviding
4 private var activeSteps: [String: FunnelStep] = [:]
5 
6 struct FunnelStep {
7 let funnelName: String
8 let stepName: String
9 let stepIndex: Int
10 let startTime: Date
11 }
12 
13 init(analytics: AnalyticsProviding) {
14 self.analytics = analytics
15 }
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] = funnelStep
25 analytics.track("funnel_step", properties: [
26 "funnel": name,
27 "step": step,
28 "step_index": 0
29 ])
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] = next
42 analytics.track("funnel_step", properties: [
43 "funnel": name,
44 "step": step,
45 "step_index": next.stepIndex,
46 "previous_step_duration": stepDuration
47 ])
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": totalDuration
57 ])
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": reason
68 ])
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 MetricKit
2 
3// MARK: - MetricKit ile Apple Diagnostik Verisi
4final 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 hesapla
14 if let crashMetrics = payload.applicationExitMetrics {
15 let foregroundCrashes = crashMetrics.foregroundExitData.cumulativeAbnormalExitCount
16 let totalForeground = crashMetrics.foregroundExitData.cumulativeNormalAppExitCount + foregroundCrashes
17 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 diagnostikleri
26 payload.hangDiagnostics?.forEach { hang in
27 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.

Etiketler

#Crash Reporting#Analytics#Monitoring#DevOps#iOS#Production
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz