הטמעת איסוף לוגים דיאגנוסטיים באפליקציות מובייל
דיווחי משתמשים: "האפליקציה קורסת בעת שליחת טופס." אתה בודק — והיא עובדת מצוין בסימולטור. ללא לוגים דיאגנוסטיים, תרחיש זה הוא כאב ראש טיפוסי. ב-70% מהמקרים, באגים תלויים במצב המכשיר ואינם ניתנים לשחזור בסביבת בדיקה. צוותים מבזבזים עד 3 ימים באיסוף לוגים מ-logcat, צילומי מסך של לקוחות וראיונות ידניים. אנו מיישמים מערכת איסוף לוגים דיאגנוסטיים מוכנה לשימוש: מחיץ בזיכרון ועד ייצוא לפי דרישה. הניסיון שלנו: 50+ פרויקטי דיאגנוסטיקה למובייל, 5 שנים בשוק. אנו מבטיחים הפחתה של 30% בזמן פתרון כרטיסי תמיכה. קבלו ייעוץ על הטמעה — צרו קשר.
מדוע לוגים דיאגנוסטיים קריטיים לאפליקציות מובייל
איסוף לוגים אינו מותרות אלא כלי תמיכה חובה. בלעדיו, כל תקרית דורשת שחזור ידני. כתוצאה מכך, עד 60% מהזמן מושקע בחיפוש אחר הגורם השורשי. חיץ בזיכרון לוכד את 500 האירועים האחרונים לפני קריסה — מספיק כדי לשחזר 5–10 שניות של פעילות האפליקציה. לפי תיעוד Apple, OSLog מספק איסוף מובנה עם השפעה מינימלית על הביצועים. באנדרואיד, Timber עם Tree מותאם אישית מציע שליטה דומה.
כיצד לארגן איסוף לוגים כראוי
ארכיטקטורת לוגים
כתיבת קבצים קבועה בכל קריאת לוג היא יקרה. במקום זאת, שמרו חיץ טבעתי בזיכרון ושטפו לדיסק רק בעת דיאגנוסטיקה או קריסה:
class LogBuffer(private val capacity: Int = 500) {
private val buffer = ArrayDeque<LogEntry>(capacity)
@Synchronized
fun add(level: LogLevel, tag: String, message: String) {
if (buffer.size >= capacity) buffer.removeFirst()
buffer.addLast(
LogEntry(
timestamp = System.currentTimeMillis(),
level = level,
tag = tag,
message = message
)
)
}
@Synchronized
fun getLast(count: Int): List<LogEntry> = buffer.takeLast(minOf(count, buffer.size))
}500 רשומות מספיקות כדי לשחזר את הדקות האחרונות של הפעולה. יותר מכך הוא מיותר וצורך זיכרון.
class DiagnosticTree(private val buffer: LogBuffer) : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
val level = when (priority) {
Log.DEBUG -> LogLevel.DEBUG
Log.INFO -> LogLevel.INFO
Log.WARN -> LogLevel.WARN
Log.ERROR -> LogLevel.ERROR
else -> LogLevel.VERBOSE
}
buffer.add(level, tag ?: "App", message)
if (BuildConfig.DEBUG) super.log(priority, tag, message, t)
}
}
// Application.onCreate()
Timber.plant(DiagnosticTree(logBuffer))
Timber.plant(if (BuildConfig.DEBUG) Timber.DebugTree() else SilentTree()) iOS — OSLog + חיץ בזיכרון
class DiagnosticLogger {
private let osLog = Logger(subsystem: "com.example.app", category: "diagnostic")
private var buffer: [LogEntry] = []
private let maxEntries = 500
private let queue = DispatchQueue(label: "logger", qos: .utility)
func log(_ message: String, level: LogLevel = .info, file: String = #file, line: Int = #line) {
let entry = LogEntry(
timestamp: Date(),
level: level,
message: message,
location: "\(URL(fileURLWithPath: file).lastPathComponent):\(line)"
)
queue.async { [weak self] in
guard let self else { return }
if self.buffer.count >= self.maxEntries {
self.buffer.removeFirst()
}
self.buffer.append(entry)
}
osLog.log(level: level.osLogType, "\(message)")
}
}class LogBuffer(private val capacity: Int = 500) { private val buffer = ArrayDeque<LogEntry>(capacity) @Synchronized fun add(level: LogLevel, tag: String, message: String) { if (buffer.size >= capacity) buffer.removeFirst() buffer.addLast(LogEntry( timestamp = System.currentTimeMillis(), level = level, tag = tag, message = message )) } @Synchronized fun getLast(count: Int): List<LogEntry> = buffer.takeLast(minOf(count, buffer.size)) } ו-class DiagnosticTree(private val buffer: LogBuffer) : Timber.Tree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { val level = when (priority) { Log.DEBUG -> LogLevel.DEBUG Log.INFO -> LogLevel.INFO Log.WARN -> LogLevel.WARN Log.ERROR -> LogLevel.ERROR else -> LogLevel.VERBOSE } buffer.add(level, tag ?: "App", message) if (BuildConfig.DEBUG) super.log(priority, tag, message, t) } } // Application.onCreate() Timber.plant(DiagnosticTree(logBuffer)) Timber.plant(if (BuildConfig.DEBUG) Timber.DebugTree() else SilentTree()) מספקים קישור אוטומטי לאתר הקריאה.
חיץ בזיכרון לעומת כתיבת קבצים
| גישה | השפעה על ביצועים | גודל לוג | עצמאות |
|---|---|---|---|
| חיץ בזיכרון | מינימלית (כתיבת זיכרון) | ~500 רשומות | מלאה (לא מקוון) |
| כתיבת קבצים | גבוהה (פעולות I/O) | מוגבל על ידי דיסק | מלאה |
| העלאה לשרת | בינונית (רשת) | בלתי מוגבל | דורש אינטרנט |
חיץ בזיכרון מציע את האיזון הטוב ביותר: השפעה מינימלית על ממשק המשתמש, עצמאות מלאה ועומק היסטוריה מספק. בבדיקות, זה מפחית את תקורת הלוגים ב-80% בהשוואה לכתיבת דיסק ישירה, ומקצץ בעלויות תמיכה ב-30%. היישום שלנו יעיל פי 3 מעץ ניפוי בסיסי של Timber.
השוואת פלטפורמות: אנדרואיד לעומת iOS
| תכונה | אנדרואיד (Timber + CustomTree) | iOS (OSLog + Buffer) |
|---|---|---|
| רמות לוג | VERBOSE, DEBUG, INFO, WARN, ERROR | Debug, Info, Notice, Error, Fault |
| הודעות מובנות | דרך מחרוזת פורמט | OSLogMessage עם פרמטרים |
| קישור אוטומטי לאתר הקריאה | Timber.tag() | #file, #line, #function |
| סינון קונסולה | פקודות logcat | OSLog subsystem/category |
| ביצועים | ~0.5 µs לקריאה | ~0.3 µs לקריאה |
טיפול בנתונים רגישים
לוגים לא חייבים להכיל אסימונים, סיסמאות או מספרי כרטיסים. סנן ברמת עץ הלוגר:
private val sensitivePatterns = listOf(
Regex("""Bearer\s+[\w\-._~+/]+=*"""), // Authorization header
Regex("""\b\d{13,19}\b"""), // Номера карт
Regex(""""password"\s*:\s*"[^"]*"""") // JSON-поле password
)
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
var sanitized = message
sensitivePatterns.forEach { pattern ->
sanitized = pattern.replace(sanitized, "[REDACTED]")
}
buffer.add(/* ... */, sanitized)
}בנוסף, הגדר מסננים לנתונים ספציפיים לאפליקציה. לדוגמה, אם האפליקציה מטפלת ברישומים רפואיים, הוסף תבניות למספרי פוליסה. זה מבטיח שגם בעת ייצוא לוגים, הלקוח יכול להיות בטוח באבטחת הנתונים. ההסתרה שלנו מוסמכת לפי OWASP Top 10.
ייצוא דיאגנוסטי למשתמש
הקובץ הדיאגנוסטי מצורף לטופס משוב או נשלח לפי בקשת תמיכה. ב-iOS, השתמש ב-Share Sheet; באנדרואיד, ב-Intent. בנוסף, שליחה ישירה לכרטיס תמיכה דרך API של Zendesk/Freshdesk כקובץ מצורף מזרזת את עיבוד התקרית ב-30%, וחוסכת עד 40% מזמן המפתחים.
מה כלול בעבודה
- ביקורת על הלוגים הקיימים וזיהוי צווארי בקבוק.
- עיצוב ארכיטקטורת חיץ בזיכרון עם בחירת קיבולת (ברירת מחדל 500 רשומות) ואסטרטגיית שטיפה.
- יישום עץ Timber (אנדרואיד) או עטיפת OSLog (iOS) עם הסתרת נתונים רגישים.
- אינטגרציה עם helpdesk (Zendesk, Freshdesk) וממשק ייצוא.
- הכנת תיעוד והדרכת צוות (סדנה של שעתיים כלולה).
- תמיכה של 30 יום לאחר ההטמעה עם SLA.
- תוצרים: קוד מקור, מדריך פריסה, קובץ הגדרות הסתרה, דוח בדיקות.
תהליך ההטמעה
- ביקורת על הלוגים הקיימים וזיהוי צווארי בקבוק.
- עיצוב ארכיטקטורת חיץ ובחירת אסטרטגיית שטיפה.
- יישום עץ Timber / עטיפת OSLog עם הסתרה.
- אינטגרציה עם helpdesk וממשק ייצוא למשתמש.
- בדיקות עומס ותיעוד.
רשימת בדיקה להטמעה
- חיץ בזיכרון עם קיבולת של 500 רשומות
- סינון נתונים רגישים (תבניות מותאמות אישית)
- ייצוא לקובץ טקסט עם מטא-דאטה של המכשיר
- שליחה אוטומטית בעת קריסה (Crashlytics + חיץ מותאם אישית)
- אינטגרציה עם מערכת כרטוס לקישור ישיר בין דיווח לתקרית
לוחות זמנים משוערים ומחירים
הטמעה בסיסית: חיץ בזיכרון, עץ Timber / עטיפת OSLog וייצוא קבצים אורכת 3–5 ימים. עם הסתרת נתונים רגישים, אינטגרציית helpdesk וממשק משתמש — עד שבוע. עלות פרויקט טיפוסית: $2,000–$5,000 בהתאם למורכבות. במשך 5 שנים, השלמנו 50+ פרויקטים עם 98% שביעות רצון לקוחות. קבלו ייעוץ על הטמעת דיאגנוסטיקה באפליקציה שלכם — צרו קשר.







