מערכת רישום מרחוק לניפוי באגים באפליקציות מובייל
למה רישום מרחוק הוא קריטי
תארו לעצמכם: באג שמופיע רק במכשיר של משתמש מסוים בסביבת הייצור. Crashlytics מראה קריסה, אבל ללא הקשר — ללא שלבים, ללא מצב. ללא רישום מרחוק, אתם מבלים בממוצע 3–5 ימים בניסיון לשחזר, ו-80% מהבאגים נשארים לא מזוהים. רישום מרחוק שולח לוגים מפורטים לשרת בזמן אמת או לפי דרישה, ומקצר את זמן החיפוש ל-2–3 שעות.
היה לנו פעם מקרה שבו אפליקציה קרסה ב-iPad mini 5 בעת סיבוב המסך, עקב תנאי מרוץ ב-UICollectionView. ללא רישום מרחוק, היינו מבלים שבועות בניחושים. במקום זאת, הפעלנו לוגים מפורטים לכמה משתמשים, מצאנו את הבעיה ב-callback של הכיוון תוך שעה, ושחררנו תיקון חם.
רישום מרחוק אינו אומר שליחת כל הלוגים מכל מכשיר לשרת. זה יקר בתעבורה, באחסון ובביצועים. הארכיטקטורה הנכונה כוללת מספר מצבים הניתנים להחלפה דינמית.
מצב פסיבי (ברירת מחדל): לוגים נכתבים למאגר טבעתי מקומי. שום דבר לא נשלח לשרת.
מצב פעיל: מופעל על ידי אירוע — קריסה, מזהה משתמש ספציפי, דגל מ-Remote Config. המאגר נשלח לשרת.
סשן ניפוי למשתמש ספציפי: עם בקשת תמיכה, רישום מפורט מופעל עבור userId מסוים דרך Firebase Remote Config או דגל תכונה.
השוואת מצבים:
| מצב | מתי משתמשים | נפח נתונים | נדרש שרת |
|---|---|---|---|
| פסיבי | ברירת מחדל | מינימלי (רק מדדים) | לא |
| פעיל | בקריסות או תקלות | בינוני (מאגר של N לוגים אחרונים) | כן |
| סשן ניפוי | למשתמש ספציפי | גבוה (לוגים מלאים של הסשן) | כן |
Firebase Remote Config לשליטה דינמית ברישום
// Android: проверка флагов логирования при старте
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener {
val logLevel = remoteConfig.getString("debug_log_level") // "OFF", "ERROR", "VERBOSE"
val targetUserId = remoteConfig.getString("debug_user_id") // пустая строка = все
RemoteLogger.configure(
level = LogLevel.fromString(logLevel),
targetUserId = targetUserId
)
}הפעלת רישום מפורט למשתמש ספציפי ללא שחרור: שנו את Remote Config — תוך 30 דקות המכשיר קולט את התצורה החדשה, ובסשן הבא יכתוב לוגים מפורטים.
הובלת לוגים
העלאה בקבוצות — מערכת איסוף לוגים מרחוק
אל תשלחו כל קריאת לוג כבקשת HTTP נפרדת: צברו בתור ושלחו בקבוצות של 100 רשומות.
class RemoteLogTransport(
private val apiService: LogApiService,
private val batchSize: Int = 100,
private val flushIntervalMs: Long = 30_000
) {
private val pendingLogs = ConcurrentLinkedQueue<LogEntry>()
fun enqueue(entry: LogEntry) {
pendingLogs.add(entry)
if (pendingLogs.size >= batchSize) {
flush()
}
}
private fun flush() {
val batch = mutableListOf<LogEntry>()
repeat(batchSize) {
pendingLogs.poll()?.let { batch.add(it) } ?: return@repeat
}
if (batch.isNotEmpty()) {
scope.launch {
runCatching {
apiService.sendLogs(
LogBatch(
sessionId = sessionId,
deviceInfo = deviceInfo,
logs = batch
)
)
}
}
}
}
}WorkManager להעברה מובטחת כשהרשת חוזרת:
val logUploadWork = OneTimeWorkRequestBuilder<LogUploadWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context).enqueue(logUploadWork) iOS — שילוב OSLog עם הובלה מרחוק
// OSLog для системного логирования + remote transport actor RemoteLogger {
private var buffer: [LogEntry] = []
private var isRemoteEnabled = false
private let transport: LogTransport
func log(_ message: String, level: LogLevel) async {
let entry = LogEntry(timestamp: Date(), level: level, message: message)
buffer.append(entry)
if buffer.count > 500 {
buffer.removeFirst()
}
if isRemoteEnabled {
await transport.enqueue(entry)
}
}
func enableRemote(for userId: String) async {
isRemoteEnabled = true
// Отправляем буфер накопленных логов
let bufferedLogs = buffer
await transport.sendBatch(bufferedLogs)
}
}// Android: проверка флагов логирования при старте val remoteConfig = Firebase.remoteConfig remoteConfig.fetchAndActivate().addOnCompleteListener { val logLevel = remoteConfig.getString("debug_log_level") // "OFF", "ERROR", "VERBOSE" val targetUserId = remoteConfig.getString("debug_user_id") // пустая строка = все RemoteLogger.configure( level = LogLevel.fromString(logLevel), targetUserId = targetUserId ) } מבטיח בטיחות תהליכונים ללא נעילה מפורשת — הגישה הנכונה ב-Swift 5.5+. לפרטים נוספים על OSLog, עיינו בתיעוד של Apple.
צד שרת לאחסון לוגים
פתרונות סטנדרטיים:
| אחסון | מתאים ל | הערות |
|---|---|---|
| Elasticsearch + Kibana | חיפוש טקסט מלא בלוגים | עתיר משאבים אך חזק |
| Loki + Grafana | לוגים מובנים, משאבים נמוכים | זול יותר מ-Elastic |
| Datadog | SaaS, ללא תשתית | יקר בקנה מידה גדול |
| Sentry | כבר בשימוש לקריסות | Breadcrumbs + לוגים מרחוק במקום אחד |
Sentry Breadcrumbs — תכונה שלעתים קרובות לא מעריכים. Breadcrumbs מותאמים אישית מחוברים לכל אירוע (קריסה או שגיאה) ומראים מה קרה לפני הבעיה:
SentrySDK.configureScope { scope in
scope.addBreadcrumb(Breadcrumb(
level: .info,
category: "navigation",
message: "User opened PaymentScreen",
data: ["orderId": orderId]
))
}הערה: כאשר מתרחשת קריסה, Sentry מציג את 100 ה-breadcrumbs האחרונים — למעשה לוג מוכן של נתיב המשתמש.
אבטחה ועמידה ב-GDPR
לוגים מרחוק עשויים להכיל מידע אישי. אנו מבטיחים:
- לוגים אינם מכילים שמות מלאים, אימיילים, מספרי כרטיסים — רק
class RemoteLogTransport( private val apiService: LogApiService, private val batchSize: Int = 100, private val flushIntervalMs: Long = 30_000 ) { private val pendingLogs = ConcurrentLinkedQueue<LogEntry>() fun enqueue(entry: LogEntry) { pendingLogs.add(entry) if (pendingLogs.size >= batchSize) { flush() } } private fun flush() { val batch = mutableListOf<LogEntry>() repeat(batchSize) { pendingLogs.poll()?.let { batch.add(it) } ?: return@repeat } if (batch.isNotEmpty()) { scope.launch { runCatching { apiService.sendLogs(LogBatch( sessionId = sessionId, deviceInfo = deviceInfo, logs = batch )) } } } } }לצורך קורלציה. - נתוני לוג נשמרים לא יותר מ-30 יום (TTL ניתן להגדרה באחסון).
- משתמשים יכולים לבטל איסוף אבחוני בהגדרות האפליקציה — הדגל נשמר ב-
val logUploadWork = OneTimeWorkRequestBuilder<LogUploadWorker>() .setConstraints(Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build()) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES) .build() WorkManager.getInstance(context).enqueue(logUploadWork)/// OSLog для системного логирования + remote transport actor RemoteLogger { private var buffer: [LogEntry] = [] private var isRemoteEnabled = false private let transport: LogTransport func log(_ message: String, level: LogLevel) async { let entry = LogEntry(timestamp: Date(), level: level, message: message) buffer.append(entry) if buffer.count > 500 { buffer.removeFirst() } if isRemoteEnabled { await transport.enqueue(entry) } } func enableRemote(for userId: String) async { isRemoteEnabled = true // Отправляем буфер накопленных логов let bufferedLogs = buffer await transport.sendBatch(bufferedLogs) } }ונבדק לפני כל העלאה. - מדיניות הפרטיות מתארת את איסוף הנתונים האבחוניים.
ניפוי תפעולי ללא שחרור
תרחיש: קריסות בייצור עבור 0.3% מהמשתמשים במכשיר ספציפי. רצף ללא רישום מרחוק:
- בקשו מהמשתמש להפעיל מצב מפתחים — לא סביר.
- חכו לשחזור — לא ידוע מתי.
עם רישום מרחוק:
- הפעילו מצב מפורט דרך Remote Config עבור userId ספציפי.
- המשתמש משחזר את הבעיה בסשן הבא.
- תוך 30 דקות, לוגים מפורטים של הסשן נראים ב-Kibana/Grafana.
- אתרו את הבעיה, שחררו תיקון חם, כבו מצב מפורט.
מה כלול
- תיעוד ארכיטקטוני עם סכמת העברת לוגים.
- SDK ל-iOS (Swift) ול-Android (Kotlin) עם תמיכה ב-OSLog ו-WorkManager.
- אינטגרציה עם Firebase Remote Config ו-Sentry breadcrumbs.
- אחסון צד שרת (Elasticsearch או Loki) עם דשבורדים מוגדרים.
- מדיניות אבטחה ו-TTL בהתאם ל-GDPR.
- הדרכת צוות על המערכת ותמיכה טכנית במהלך ההשקה.
תהליך
ניתוח ועיצוב
אנו לומדים את התשתית שלכם, מזהים נקודות רישום קריטיות. מעצבים את הארכיטקטורה: בוחרים אחסון, קובעים TTL, מגדירים רמות רישום.
יישום ב-iOS וב-Android
אנו כותבים SDK ל-iOS (Swift, עם OSLog) ול-Android (Kotlin, עם WorkManager). מוסיפים Remote Config לשליטה דינמית. משלבים Sentry breadcrumbs.
בדיקות והשקה
אנו מבצעים בדיקות עומס: מוודאים שהאפליקציה לא מאטה ב-1000 לוגים בשנייה. פורסים את צד השרת, מגדירים דשבורדים. מאמנים את הצוות.
הערכות זמנים
מערכת רישום מרחוק בסיסית עם העלאה בקבוצות, שליטה ב-Remote Config ואינטגרציה עם Sentry — 1–2 שבועות. תשתית מלאה עם Elasticsearch, דשבורדים של Kibana, מנגנוני GDPR ו-iOS+Android — 3–4 שבועות. העלות מחושבת באופן אישי לאחר ביקורת של התשתית הנוכחית.
איך להפחית עלויות ניפוי?
רישום מרחוק חוסך עד 50% מהזמן המושקע באיתור ותיקון באגים. במקום להזמין מכשירי בדיקה ולשחזר בעיות בעיוורון, אתם רואים מיד לוגים ממשתמשים אמיתיים. זה מפחית עלויות QA ב-40% ומאיץ שחרורים ב-30%. לצוות שלנו יש ניסיון של 10+ שנים בפיתוח מובייל והוא יישם רישום מרחוק בלמעלה מ-200 פרויקטים. הזמינו אינטגרציה של רישום מרחוק לשיפור איכות האפליקציה. קבלו ייעוץ — המהנדסים שלנו ינתחו את התשתית שלכם ויציעו פתרון אופטימלי. צרו קשר כדי להתחיל לחסוך זמן ניפוי.







