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), приложения, использующие structured 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) для фильтрации. Это значительно дешевле в хранении и быстрее при запросах по фиксированному набору полей.
// Structured logging через 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.
// Structured logging на 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-контейнере и передавайте во все логгеры через конструктор. Альтернатива — использовать threading-локал или Continuation Local Storage в Kotlin корутинах.
Можно, но не рекомендуется — при смешивании теряется возможность автоматической индексации. Если часть логов текстовые, их приходится парсить регулярными выражениями, что снижает производительность и надёжность поиска. Лучше мигрировать все логи на структурированный формат.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также