Чому 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, щоб підвищити якість застосунку. Отримайте консультацію — наші інженери проаналізують вашу інфраструктуру та запропонують оптимальне рішення. Зв'яжіться з нами, щоб почати економити час на налагодженні.







