Налаштування структурованого логування (JSON Logs) у застосунках

Текстові логи перетворюються на хаос: їх не розбирає машина, інциденти розслідуються годинами, а обсяг зберігання зростає без користі. Ми налаштовуємо структуроване логування під ключ — від аудиту подій до схеми полів і регламентів для команди. Розбір інцидентів прискорюється в рази, шум у логах падає, витрати на зберігання перестають зростати.
Часті запитання

Текстові логи нечитабельні: що вирішує структуроване логування

Інцидент виглядає так: частина запитів до сервісу відповідає помилкою, і потрібно зрозуміти, чим відрізняються успішні виклики від неуспішних.

Логи є, але кожна помилка записана вільним текстом, і та сама причина виглядає по-різному — «не вдалося обробити замовлення користувача 42, таймаут» та «order 42 failed: timeout».

Розбирати таке доводиться очима: інженер підіймає добовий архів, проганяє його регулярними виразами, отримує тисячі збігів і вручну відсіює сусідні події.

Ціна такої роботи зростає разом з обсягом. В архіві — гігабайти рядків, шаблон пошуку не покриває всі формулювання, а час відповіді сервісу та код помилки доводиться витягувати з тексту окремими виразами.

Розбір інциденту за логами займає години, відновлення затягується, і частина висновків залишається здогадами: згрупувати записи за кодом помилки або порахувати частку повільних запитів за таким файлом технічно можна, але на практиці ніхто цього не робить.

Структуроване логування змінює формат запису подій: замість рядка — набір полів з фіксованими іменами та типами. Мітка часу, рівень, ім'я сервісу, код помилки, ідентифікатор запиту й трасування лежать окремими значеннями, а довільний текст залишається в одному полі-повідомленні.

Пошук за логами застосунку перетворюється на запит із фільтром за полем: «усі записи цього ідентифікатора трасування», «усі відповіді з кодом 500 за інтервал», «частка запитів довших за поріг за кожним маршрутом». Збирач читає поля напряму, будує графіки та алерти, а не займається розбором рядків.

Нечитабельні логи перестають бути проблемою окремого інженера — вони стають даними, за якими рахується причина. Нижче розбираємо, як це закривається в застосунку та в контурі збору.

Ефект в експлуатації: розбір інцидентів, шум і зберігання логів

Структуроване логування переводить спостережуваність сервісу з режиму «читаємо очима» в режим запитів за полями. Нижче — що саме перевіряємо на прийманні, щоб перехід дав вимірюваний ефект, а не просто змінив формат файлів.

  • Час локалізації збою: за ідентифікатором запиту збираємо весь ланцюжок викликів між сервісами однією вибіркою — черговий знаходить причину за хвилини замість перебору файлів на кількох хостах.
  • Шум у логах: рівні та обов'язковий набір полів задані схемою, налагоджувальні повідомлення не потрапляють у бойовий потік, а кількість записів на один запит перестає зростати разом із навантаженням.
  • Обсяг зберігання логів: рівень, сервіс і строк життя запису описані політикою, тому зростання обсягу рахується заздалегідь, а не з'ясовується під час заповнення диска.
  • Розбір чергування: мітка часу, ім'я сервісу, ідентифікатор запиту та код відповіді вже всередині запису — контекст інциденту читається без доступу до сервера.
  • Минулий клас відмов: втрата логів під час перезапуску контейнера та «мовчазні» сервіси закриваються обов'язковим вивантаженням у спільний збирач Grafana Loki з перевіркою доступності приймача.

Кожен пункт підтверджуємо на навчальному інциденті: гасимо один сервіс і рахуємо, скільки кроків і часу займає шлях від сигналу до причини. Якщо причина знаходиться за полями, без читання сирого тексту, — логування працює.

Структуроване логування проти текстового: порівняння режимів

Режим запису подій визначає, хто буде споживачем логу — людина чи збирач. Вільний текст читається очима, але лише до першої правки формулювання: далі розбір на боці збору перестає збігатися з повідомленням, і частина подій мовчки випадає з вибірки.

Структурований запис переносить розбір у застосунок — споживач отримує готові поля й фільтрує за ними, не вгадуючи формат.

Передбачуваність дає схема події, єдина для всіх сервісів: мітка часу, рівень логування, ідентифікатор запиту, ім'я сервісу, код події та вкладений контекст.

Рівні тоді працюють як політика, а не як позначка в рядку: налагоджувальні записи йдуть в окремий контур, попередження та помилки йдуть у спільний потік і не тонуть у ньому.

Варіант Коли підходить Обмеження
Вільний текст Разові утиліти, де лог читає лише автор Полів немає: вибірка будується за підрядком
Текст із шаблоном «ключ=значення» Діючий сервіс, який не можна змінити повністю Домовленість про ключі тримають вручну, вкладені структури втрачають форму
Фіксована схема полів (JSON-об'єкт на подію) Події розбирає збирач: пошук, оповіщення, розбір інцидентів Потрібен реєстр полів і правило їх зміни між командами
Потокова передача у збирач Багато екземплярів сервісу, потрібна спільна картина одразу Обрив каналу вимагає буфера на диску, інакше події губляться

На практиці беруть гібрид: людиночитний рядок у консоль під час локального запуску та структурний запис у збирач для робочих контурів.

Перехід ведуть за сервісами — починаючи з тих, де розбір інциденту займає найбільше часу, і з реєстром полів, щоб нова подія не вимагала правок на боці приймання.

Впровадження за кроками: від аудиту подій до передачі регламентів

Послідовність вибудувана так, щоб схема подій з'явилася раніше, ніж перший лог піде у сховище: переробляти формат на працюючих сервісах дорожче, ніж домовитися про нього на старті. Нижче — порядок, який проходить кожен проєкт.

  1. Аудит джерел логів. Збираємо інвентаризацію: які сервіси пишуть самі, які віддають потік у stdout, у кого події розмазані по кількох потоках і файлах. На виході — таблиця з частотою подій і добовим обсягом за кожним джерелом.
  2. Проєктування схеми подій. Фіксуємо обов'язковий набір полів: час, рівень, ім'я сервісу, ідентифікатор запиту, код помилки, текст повідомлення. Окремо — словник значень рівня та правила іменування, щоб error і err не жили в одному індексі.
  3. Налаштування застосунку. Запис іде одним рядком на подію, без перенесень усередині повідомлення — інакше збирач розірве його на частини. Контекст запиту прокидається через спільну обгортку й потрапляє в кожен запис автоматично, включно з фоновими задачами.
  4. Налаштування збирача логів. Розбір полів не потрібен: маршрутизація йде за значеннями, додаються буферизація на диск і обмеження швидкості. Маскування токенів і персональних даних виконується на вході, до запису.
  5. Перевірка на реальному збої. Відтворюємо контрольовану помилку й проходимо ланцюжок повністю: запис у сервісі, збирач, сховище, пошук за ідентифікатором запиту. Окремо дивимося, чи збираються в один ланцюжок виклики між сервісами.
  6. Контроль якості та регламенти. Налаштовуємо регулярні перевірки: частка записів без обов'язкових полів, частка нерозпізнаних рівнів, обсяг шуму від перевірок доступності. Передаємо документ зі схемою полів, правилами додавання нових подій і строками зберігання.

Артефакти: схеми полів, конфігурації збирачів, регламенти

Після впровадження в команди залишається не набір налаштувань на сервері, а комплект файлів і документів, за якими структуроване логування можна розширювати без нас.

Усі артефакти версіонуються в тому самому репозиторії, що й код сервісів: правка проходить рев'ю і не живе в чужій голові.

  • Словник подій — імена подій з обов'язковими та необов'язковими полями, типами й допустимими значеннями. Він же фіксує, що в лог не потрапляє: токени, тіла запитів повністю, вміст користувацьких файлів.
  • Контракт полів і правила його зміни — як додавати й перейменовувати поля, не ламаючи розбір у сховищі та вже побудовані звіти.
  • Правила знеособлення логів — що маскується, що хешується, де значення замінюється на ідентифікатор. Задаються в спільній конфігурації, а не в коді кожного сервісу, тому застосовуються однаково.
  • Конфігурація збору логів — розбір потоку, буферизація на боці застосунку, поведінка за недоступності сховища, ротація файлів і пороги, за яких запис відкидається замість блокування сервісу.
  • Політика зберігання логів — строки за типами подій, розділення оперативного й архівного зберігання, порядок видалення. Окремо зазначено, які події живуть довше через розбір інцидентів.
  • Інструкція для чергового — як зібрати всі події одного запиту, які поля фільтрувати першими, готові запити під типові збої та порядок ескалації.
  • Регламент змін схеми — хто погоджує правку словника, як перевіряється сумісність і що робити зі старими записами після зміни полів.

Панелі за частотою помилок і затримками збираються поверх тих самих полів — окремого джерела даних для них не потрібно.

Кейс: розбір інциденту скоротився з годин до хвилин

До: шість форматів, пошук навмання

Платформа замовника — шість застосунків: замовлення, білінг, сповіщення, інтеграції, адмінка та фонові задачі. Логи кожне писало вільним текстом у свій файл на своєму вузлі: час і рівень ішли всередину рядка повідомлення, ідентифікатор запиту між сервісами не передавався.

Розбір інциденту починався з обходу вузлів і пошуку за уривками фраз — до першої версії причини минало 3-4 години, а шість алертів із десяти виявлялися хибними: спрацьовували на текстових збігах.

Що змінили і що отримали

Ввели єдину схему події: рівень, мітка часу, сервіс, ідентифікатор запиту, код операції, тривалість, результат.

Ідентифікатор запиту генерується на вході й передається всім ланцюжком внутрішніх викликів — шлях «запит → сервіс → сховище → черга → відповідь» видно одним пошуком за полем, а не підбором виразів за рядками. Збір і пошук ідуть в OpenSearch.

Правила зберігання налаштували окремо: перевірки доступності та рутинні опитування пішли в налагоджувальний рівень і пишуться не за кожним викликом, гарячий контур тримає два тижні, далі дані переїжджають у холодне сховище.

Обсяг логів зріс приблизно втричі, але без цих правил зростання було б у рази більшим, а витрати на зберігання перестали зростати разом із трафіком.

Через три місяці: медіанний час пошуку причини — 8-10 хвилин замість 3-4 годин, хибні спрацювання — один випадок із десяти замість шести, удвічі більше інцидентів чергова зміна закриває без розробників.

На стику сервісів ефект найпомітніший: зв'язка логів за єдиним ідентифікатором одразу показує, який виклик повернув помилку і що йому передувало.

Чим лог відрізняється від метрики та трасування?

Три сигнали відповідають на різні питання, і плутанина починається, коли один використовують замість іншого. Метрика говорить «скільки і як часто»: це агрегат за числовим рядом — частка відмов, час відповіді за хвостовим процентилем.

Лог говорить «що сталося»: це подія з контекстом — ідентифікатор запиту, користувач, параметри виклику. Трасування — «де загубився час»: шлях запиту через сервіси з тривалістю кожного кроку.

Різниця між логом і метрикою видна під час розбору: за графіком зрозуміло, що відмови зросли після викочування, але не на якому вході. Метрика навмисно втрачає контекст заради дешевого зберігання та швидкої агрегації.

Тому в метрику виносять сигнали, на яких будуються оповіщення, а в структуроване логування — те, що розбирають за конкретним випадком.

Трасування запиту закриває третій шар: коли обробка йде через кілька сервісів і чергу, один запис у лозі не покаже, де накопичилися майже всі затримки. Ідентифікатор трасування в кожному записі логу дає перехід від графіка до конкретного виклику.

Головна помилка — дублювання телеметрії: один факт пишуть одразу в метрику, лог і трасування. Це роздуває зберігання, прискорює розходження систем і заплутує вибір інструмента діагностики: черговий відкриває три панелі й не розуміє, яка правильна. Робоче правило — у кожного факту один дім, інші сигнали посилаються на нього ідентифікатором.

Єдиний стандарт передачі телеметрії — OpenTelemetry — знімає прив'язку до вендора: бекенд спостережуваності застосунку змінюється без переписування коду інструментування.

Обмеження та часті питання щодо впровадження

У структурованого логування є ціна, і чесніше назвати її до впровадження. Запис «ключ — значення» займає у два-три рази більше місця, ніж текстовий рядок, а серіалізація на гарячому шляху віднімає час у обробки самого запиту.

Знімаємо це рівнями — налагодження на проді не пишемо, — буферизацією запису та винесенням його в окремий потік, щоб журнал не гальмував основний сценарій.

Друге обмеження — неоднорідний контур: частина сервісів пише у старому текстовому форматі, і спільна схема за ними не збирається, поки збирач не розбере їх правилом. Третє — чутливі дані: у полях запиту регулярно опиняються токени, адреси, ідентифікатори та персональні дані.

Їх маскуємо на прийманні, до запису у сховище, інакше спільний індекс відкриває їх кожному, хто має право на читання журналу.

Чи не втратимо записи під час сплеску навантаження?

Доставка — найтихіша точка відмови: буфер збирача переповнюється, і частина записів губиться без жодної помилки в застосунку.

Тому рахуємо не лише обсяг, а й пропускну здатність приймання, вмикаємо дисковий буфер і тримаємо частку відкинутих записів окремою метрикою з оповіщенням.

Старі сервіси доведеться переписувати?

Ні. Приведення до спільної схеми робиться на боці збирача правилами розбору, застосунок не чіпаємо. Переписуємо лише ті сервіси, які найближчий рік усе одно змінюються за бізнес-логікою — там новий формат ставимо попутно.

Як відокремити персональні дані від інших полів?

Проводимо розбір за схемою та списком полів: що вважається ідентифікатором, що — тілом операції. Далі маскування на прийманні, окремі потоки зберігання зі скороченим строком і розмежуванням доступу, щоб аналітика йшла за знеособленими зрізами.

Обговоримо задачу й повернемося з оцінкою та планом

Розмова починається з вихідних даних — без них оцінка перетворюється на припущення.

Нам достатньо поточної конфігурації логування застосунків, прикладів реальних записів із журналів, схеми розгортання та того, куди сьогодні йдуть події: файли на диску, stdout контейнера, зовнішній збирач.

Що ми уточнимо на дзвінку:

  • які сервіси та середовища потрапляють в обсяг робіт, а які залишаються поза задачею;
  • які події критичні для розбору збоїв, а які створюють шум і витісняють корисні записи;
  • чи потрібен наскрізний ідентифікатор запиту, щоб зв'язати записи різних сервісів в один ланцюжок;
  • як у компанії вирішується питання персональних даних у журналах і хто відповідає за політику маскування;
  • строки зберігання, обсяг потоку подій і вимоги до швидкості пошуку за архівом;
  • хто з боку замовника приймає результат і в якому вигляді очікує документацію.

Далі ми проводимо аудит поточного логування: дивимося, які поля пишуться зараз, де губиться контекст, як влаштовані рівні важливості та ротація.

За підсумками повертаємося з планом робіт із впровадження — порядок переведення сервісів, схема обов'язкових полів, налаштування збирача й зберігання, розбитий на етапи з зрозумілим результатом на кожному.

Окремим пунктом іде передача знань команді: опис формату записів, правила додавання нових полів, готові шаблони запитів до журналів і розбір на живих прикладах. Опишіть задачу логування — повернемося з оцінкою та планом.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    815
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139