Structured Logging — це підхід до запису логів, де кожне повідомлення представлене в машиночитаному форматі з парами ключ-значення, а не у вигляді неструктурованого тексту. На відміну від пласких рядків, структуровані логи містять метадані: timestamp, рівень, модуль, ідентифікатор запиту — і можуть бути проіндексовані системами аналізу. За даними O'Reilly Effective Logging, перехід на структуровані формати скорочує час пошуку інциденту з годин до хвилин завдяки можливості фільтрації за полями. Це стандарт де-факто в сучасній мобільній та серверній розробці: JSON та logfmt дозволяють обробляти логи програмами, а не очима.
Головне
Structured Logging — це метод запису логів, при якому кожне повідомлення містить іменовані поля з типізованими значеннями. Замість рядка виду User 42 logged in from device ABC структурований лог виглядає як набір полів: user_id=42, event=login, device_id=ABC, timestamp=2026-07-04T10:30:00Z.
Основна перевага структурованих логів перед текстовими — можливість програмної обробки. Парсинг текстових логів вимагає регулярних виразів і припущень про формат рядка. Структуровані логи парсяться без втрат: кожне поле має відомий тип та ім’я, що дозволяє будувати запити рівня знайти всі помилки авторизації за останню годину для користувача 42 без додаткової обробки.
За даними Honeycomb.io (2023), команди, які використовують structured logging у production, виявляють інциденти в середньому в 4 рази швидше порівняно з командами, що покладаються на текстові логи та grep.
Structured Logging підтримує кілька форматів серіалізації. Вибір формату залежить від інфраструктури: JSON зручний для інтеграції з Elasticsearch та хмарними системами, logfmt — для консольного перегляду через tail та grep, Protocol Buffers — для високопродуктивних систем з обмеженням пропускної здатності.
| Формат | Приклад | Коли використовувати |
|---|---|---|
| JSON | {"event":"login","user_id":42} | ELK Stack, хмарні збирачі, мікросервіси |
| Logfmt | event=login user_id=42 duration_ms=150 | Консоль, tail, heroku logs |
| MessagePack | Бінарний еквівалент JSON | Високонавантажені системи, IoT |
JSON — найпоширеніший формат для структурованих логів. Він нативно підтримується всіма системами збору: Logstash, Fluentd, Amazon CloudWatch, Google Cloud Logging. JSON-логи легко читаються людиною та парсяться будь-якою мовою програмування без додаткових бібліотек. Основний недолік — надмірність: кожна пара ключ-значення потребує лапок і двокрапок, що збільшує об’єм збережених даних на 30–50% порівняно з logfmt.
Logfmt був розроблений у Heroku для консольної видимості. Він компактніший за JSON, зберігає читабельність для людини та легко обробляється через cut і awk. Приклад: ts=2026-07-04T10:30:00Z level=error module=api status=500. Logfmt не потребує екранування більшості символів і добре підходить для stdout-логування в контейнерах.
У мобільних застосунках Structured Logging вирішує три ключові завдання: пошук причин падінь без відтворення на пристрої, відстеження користувацьких сесій та аналіз продуктивності за версіями застосунку.
Текстові логи на мобільних пристроях майже марні — розробник не може grep-нути логи на пристрої користувача. Структуровані логи відправляються в хмарні системи (Firebase, Sentry, Datadog) і там індексуються. Можна побудувати запит: показати всі crash з iOS 17.4, версія застосунку 3.2, у модулі checkouts — і отримати точну вибірку за кілька секунд.
За даними Sentry (2024), застосунки, що використовують структуровані breadcrumbs, мають на 60% більше контексту в кожному креш-репорті порівняно із застосунками, що логують лише текст помилки. Це безпосередньо впливає на швидкість фіксу багу.
ELK Stack — Elasticsearch, Logstash, Kibana — залишається стандартною інфраструктурою для роботи зі структурованими логами. Logstash приймає логи в JSON, перетворює та відправляє в Elasticsearch для індексації, Kibana надає візуальний інтерфейс для запитів і дашбордів.
Для мобільних застосунків популярні хмарні рішення: Firebase Crashlytics з кастомними логами, Sentry з breadcrumbs, Datadog з APM-трекінгом. Вони приймають структуровані логи безпосередньо з мобільного SDK і не потребують розгортання власного бекенду. Firebase надає безкоштовний пакет для креш-репортів, Sentry додає distributed tracing, а Datadog інтегрується з APM для відстеження продуктивності запитів на клієнті та сервері одночасно.
Grafana Loki — альтернатива Elasticsearch, оптимізована для логів. Loki не індексує вміст повідомлень за замовчуванням, а використовує мітки (labels) для фільтрації. Це значно дешевше у зберіганні та швидше при запитах по фіксованому набору полів.
// Структуроване логування через Swift Logger у JSON
struct StructuredLog {
let event: String
let attributes: [String: Any]
let level: String
func serialize() -> String {
var base = "event=\(event) level=\(level)"
for (key, value) in attributes {
base += " \(key)=\(value)"
}
return base
}
}
Перше правило структурованого логування: кожне повідомлення повинно містити ідентифікатор запиту або сесії. Без контексту окремий лог марний — неможливо зрозуміти, до якого користувача або якого запиту він належить. Додавайте correlation ID на старті сесії та передавайте його через усі шари застосунку.
Друге правило: типізація полів. Числові поля (duration_ms, status_code, retry_count) мають передаватися як числа, а не як рядки. Elasticsearch та аналогічні системи індексують числа та рядки по-різному: до чисел можна застосовувати агрегації (середнє, медіана, перцентиль), до рядків — повнотекстовий пошук. Хибна типізація позбавляє можливості будувати аналітичні дашборди.
Третє правило: уникайте вкладених об’єктів. JSON-логи з глибиною вкладеності понад 2 рівні складно фільтрувати та візуалізувати. Замість {"user": {"name": "Alice", "role": "admin"}} використовуйте пласкі ключі: user_name=Alice user_role=admin.
// Структуроване логування на Android через Timber + logfmt
class StructuredTree : Timber.Tree() {
override fun log(priority: Int, tag: String?,
message: String?, t: Throwable?) {
val level = priorityToLevel(priority)
val logfmt = "level=$level tag=$tag message=$message"
sendToRemote(logfmt)
}
}
Мінімальний набір полів для кожного структурованого повідомлення: timestamp у ISO 8601, level (debug/info/warn/error/fatal), logger (ім’я модуля або класу), message (людиночитаний опис події). Додатково: correlation_id, user_id (якщо відомий), version (версія застосунку), platform (iOS/Android), environment (dev/staging/prod).
Без correlation_id структуровані логи перетворюються на набір розрізнених записів, які неможливо пов’язати в один користувацький сценарій. Генеруйте UUID при кожному запуску застосунку та додавайте його в усі логи сесії. На практиці correlation_id має передаватися через усі шари: від UI-подій до мережевих запитів і фонових завдань — інакше частина логів залишиться без контексту і не братиме участі в аналітиці. При наскрізному трекінгу один UUID дозволяє зібрати повну картину користувацького шляху.
На iOS структуроване логування можна реалізувати через обгортку над os_log, яка серіалізує поля у формат logfmt. На Android — через Timber з кастомним Tree, який перетворює повідомлення в JSON або logfmt перед відправкою на сервер.
import OSLog
struct StructuredLogger {
let subsystem: String
let category: String
func log(level: OSLogType,
event: String,
context: [String: Any]) {
let oslogger = Logger(
subsystem: subsystem,
category: category
)
let fields = context.map {
"\($0.key)=\($0.value)"
}.joined(separator: " ")
oslogger.log(level: level,
"\(event) \(fields)")
}
}
Вибір між структурованими та текстовими логами залежить від стадії проєкту. На ранніх етапах розробки текстові логи простіші та швидші — розробник пише message безпосередньо без додаткових обгорток. Але щойно проєкт виходить за межі однієї команди або одного сервера, структуровані логи стають обов’язковими.
| Критерій | Текстові логи | Структуровані логи |
|---|---|---|
| Читабельність | Висока в консолі | Середня (потребує pretty-print) |
| Пошук | grep за підрядком | Запити за полями та значеннями |
| Агрегація | Не підтримується | Середнє, медіана, перцентилі |
| Інтеграція | Потребує парсингу | Нативна в ELK/Loki/Datadog |
| Об’єм зберігання | Менший (без метаданих) | Більший (поля + значення) |
Часті запитання
Для відправки на сервер використовуйте JSON — він нативно підтримується Firebase Crashlytics, Sentry та Datadog. Для локального перегляду в логах Xcode або Android Studio використовуйте logfmt — він компактніший і читається без форматування.
Так, структуровані логи на клієнті дозволяють додати контекст до кожного креш-репорту: версія ОС, стан мережі, останні дії користувача. Без структурованих breadcrumbs креш-репорт містить лише стек викликів без користувацького сценарію.
Logfmt компактніший (на 30–50% менше об’єму) і легше читається в терміналі. JSON підтримує вкладені об’єкти та масиви, але потребує екранування лапок. Вибір залежить від інфраструктури: для ELK — JSON, для консольного перегляду — logfmt.
Створіть один екземпляр UUID при запуску застосунку, збережіть його в синглтоні або DI-контейнері та передавайте в усі логери через конструктор. Альтернатива — використовувати трединг-локал або Continuation Local Storage в Kotlin корутинах.
Можна, але не рекомендується — при змішуванні втрачається можливість автоматичної індексації. Якщо частина логів текстова, їх доводиться парсити регулярними виразами, що знижує продуктивність і надійність пошуку. Краще мігрувати всі логи на структурований формат.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також