Чому remote logging життєво необхідний?
Уявіть: баг відтворюється лише на пристрої конкретного користувача в продакшені. Crashlytics показує креш, але без контексту — жодних кроків, жодного стану. Без remote logging ви витрачаєте в середньому 3–5 днів на відтворення, а 80% багів залишаються нелокалізованими. Remote logging передає детальні логи на сервер у реальному часі або за запитом, скорочуючи пошук до 2–3 годин.
Одного разу у нас був випадок, коли застосунок падав на iPad mini 5 при повороті екрану через race condition у UICollectionView. Без remote logging ми б витратили тижні на перебір гіпотез. Натомість увімкнули verbose-логи для кількох користувачів, через годину знайшли проблему в колбеку орієнтації та випустили hotfix.
Remote logging — це не надсилання всіх логів з кожного пристрою на сервер. Це дорого по трафіку, зберіганню та продуктивності. Правильна архітектура передбачає кілька режимів, які перемикаються динамічно.
Пасивний режим (за замовчуванням): логи пишуться в локальний кільцевий буфер. На сервер нічого не йде.
Активний режим: вмикається за тригером — креш, конкретний user ID, прапорець з Remote Config. Буфер скидається на сервер.
Debug-сесія для конкретного користувача: за запитом підтримки вмикається розширене логування для конкретного userId через Firebase Remote Config або feature flag.
Порівняння режимів:
| Режим | Коли використовується | Обсяг даних | Необхідність сервера |
|---|---|---|---|
| Пасивний | За замовчуванням | Мінімальний (лише метрики) | Ні |
| Активний | При крешах або інцидентах | Середній (буфер останніх N логів) | Так |
| Debug-сесія | Для конкретного користувача | Високий (всі логи сесії) | Так |
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 хвилин пристрій отримає новий конфіг, наступна сесія пише verbose-логи.
Транспорт логів
Пакетна відправка — система віддаленого збору
Не надсилаємо кожен лог-виклик як окремий 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 і remote transport
// 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) } } actor забезпечує thread safety без явного блокування — правильний підхід у Swift 5.5+. Докладніше про OSLog у документації Apple.
Backend для зберігання логів
Стандартні рішення:
| Сховище | Підходить для | Особливості |
|---|---|---|
| Elasticsearch + Kibana | Повнотекстовий пошук по логах | Ресурсоємний, але потужний |
| Loki + Grafana | Структуровані логи, мало ресурсів | Дешевший за Elastic |
| Datadog | SaaS, без інфраструктури | Дорогий при великому обсязі |
| Sentry | Вже використовується для крешів | Breadcrumbs + remote logs в одному місці |
Sentry Breadcrumbs — часто недооцінена функція. Кастомні breadcrumbs прикріплюються до кожного Event (крешу або помилки) і показують, що відбувалося до проблеми:
SentrySDK.configureScope { scope in scope.addBreadcrumb(Breadcrumb( level: .info, category: "navigation", message: "User opened PaymentScreen", data: ["orderId": orderId] )) } Зазначимо: коли трапляється креш, у Sentry видно останні 100 breadcrumbs — фактично готовий лог користувацького шляху.
Безпека та відповідність GDPR
Remote логи потенційно містять персональні дані. Ми гарантуємо:
- Логи не містять повних імен, email, номерів карток — тільки
userIdдля кореляції. - Дані логів зберігаються не довше 30 днів (налаштовується TTL у сховищі).
- Користувач може відмовитися від збору діагностики в налаштуваннях застосунку — прапорець зберігається в
UserDefaults/SharedPreferences, перевіряється перед кожною відправкою. - У Privacy Policy описано збір діагностичних даних.
Оперативна налагодження без релізу
Сценарій: продакшен падає у 0.3% користувачів на конкретному пристрої. Послідовність без remote logging:
- Попросити користувача увімкнути developer mode — малоймовірно.
- Чекати відтворення — невідомо коли.
З remote logging:
- Увімкнути verbose-режим через Remote Config для конкретного userId.
- Користувач відтворює проблему в наступній сесії.
- Через 30 хвилин у Kibana/Grafana видно детальні логи сесії.
- Знаходимо місце, випускаємо hotfix, вимикаємо verbose-режим.
Що входить до роботи
- Архітектурна документація з описом схеми передачі логів.
- 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 логів на секунду застосунок не гальмує. Розгортаємо backend, налаштовуємо дашборди. Навчаємо команду.
Орієнтири за термінами
Базова система remote logging з пакетною відправкою, Remote Config управлінням та інтеграцією з Sentry — 1–2 тижні. Повна інфраструктура з Elasticsearch, Kibana-дашбордами, GDPR-механізмами та iOS+Android — 3–4 тижні. Вартість розраховується індивідуально після аудиту поточної інфраструктури.
Як зменшити витрати на налагодження?
Remote logging економить до 50% часу на пошук та виправлення багів. Замість того, щоб замовляти тестові пристрої та відтворювати проблеми наосліп, ви одразу бачите логи реальних користувачів. Це знижує витрати на QA на 40% та прискорює релізи на 30%. Наша команда має 10+ років досвіду в мобільній розробці та реалізувала віддалене логування для більш ніж 200 проєктів. Замовте впровадження remote logging, щоб підвищити якість застосунку. Отримайте консультацію — наші інженери проаналізують вашу інфраструктуру та запропонують оптимальне рішення. Зв'яжіться з нами, щоб почати економити час на налагодженні.







