Текстові логи нечитабельні: що вирішує структуроване логування
Інцидент виглядає так: частина запитів до сервісу відповідає помилкою, і потрібно зрозуміти, чим відрізняються успішні виклики від неуспішних.
Логи є, але кожна помилка записана вільним текстом, і та сама причина виглядає по-різному — «не вдалося обробити замовлення користувача 42, таймаут» та «order 42 failed: timeout».
Розбирати таке доводиться очима: інженер підіймає добовий архів, проганяє його регулярними виразами, отримує тисячі збігів і вручну відсіює сусідні події.
Ціна такої роботи зростає разом з обсягом. В архіві — гігабайти рядків, шаблон пошуку не покриває всі формулювання, а час відповіді сервісу та код помилки доводиться витягувати з тексту окремими виразами.
Розбір інциденту за логами займає години, відновлення затягується, і частина висновків залишається здогадами: згрупувати записи за кодом помилки або порахувати частку повільних запитів за таким файлом технічно можна, але на практиці ніхто цього не робить.
Структуроване логування змінює формат запису подій: замість рядка — набір полів з фіксованими іменами та типами. Мітка часу, рівень, ім'я сервісу, код помилки, ідентифікатор запиту й трасування лежать окремими значеннями, а довільний текст залишається в одному полі-повідомленні.
Пошук за логами застосунку перетворюється на запит із фільтром за полем: «усі записи цього ідентифікатора трасування», «усі відповіді з кодом 500 за інтервал», «частка запитів довших за поріг за кожним маршрутом». Збирач читає поля напряму, будує графіки та алерти, а не займається розбором рядків.
Нечитабельні логи перестають бути проблемою окремого інженера — вони стають даними, за якими рахується причина. Нижче розбираємо, як це закривається в застосунку та в контурі збору.
Ефект в експлуатації: розбір інцидентів, шум і зберігання логів
Структуроване логування переводить спостережуваність сервісу з режиму «читаємо очима» в режим запитів за полями. Нижче — що саме перевіряємо на прийманні, щоб перехід дав вимірюваний ефект, а не просто змінив формат файлів.
- Час локалізації збою: за ідентифікатором запиту збираємо весь ланцюжок викликів між сервісами однією вибіркою — черговий знаходить причину за хвилини замість перебору файлів на кількох хостах.
- Шум у логах: рівні та обов'язковий набір полів задані схемою, налагоджувальні повідомлення не потрапляють у бойовий потік, а кількість записів на один запит перестає зростати разом із навантаженням.
- Обсяг зберігання логів: рівень, сервіс і строк життя запису описані політикою, тому зростання обсягу рахується заздалегідь, а не з'ясовується під час заповнення диска.
- Розбір чергування: мітка часу, ім'я сервісу, ідентифікатор запиту та код відповіді вже всередині запису — контекст інциденту читається без доступу до сервера.
- Минулий клас відмов: втрата логів під час перезапуску контейнера та «мовчазні» сервіси закриваються обов'язковим вивантаженням у спільний збирач Grafana Loki з перевіркою доступності приймача.
Кожен пункт підтверджуємо на навчальному інциденті: гасимо один сервіс і рахуємо, скільки кроків і часу займає шлях від сигналу до причини. Якщо причина знаходиться за полями, без читання сирого тексту, — логування працює.
Структуроване логування проти текстового: порівняння режимів
Режим запису подій визначає, хто буде споживачем логу — людина чи збирач. Вільний текст читається очима, але лише до першої правки формулювання: далі розбір на боці збору перестає збігатися з повідомленням, і частина подій мовчки випадає з вибірки.
Структурований запис переносить розбір у застосунок — споживач отримує готові поля й фільтрує за ними, не вгадуючи формат.
Передбачуваність дає схема події, єдина для всіх сервісів: мітка часу, рівень логування, ідентифікатор запиту, ім'я сервісу, код події та вкладений контекст.
Рівні тоді працюють як політика, а не як позначка в рядку: налагоджувальні записи йдуть в окремий контур, попередження та помилки йдуть у спільний потік і не тонуть у ньому.
| Варіант | Коли підходить | Обмеження |
|---|---|---|
| Вільний текст | Разові утиліти, де лог читає лише автор | Полів немає: вибірка будується за підрядком |
| Текст із шаблоном «ключ=значення» | Діючий сервіс, який не можна змінити повністю | Домовленість про ключі тримають вручну, вкладені структури втрачають форму |
| Фіксована схема полів (JSON-об'єкт на подію) | Події розбирає збирач: пошук, оповіщення, розбір інцидентів | Потрібен реєстр полів і правило їх зміни між командами |
| Потокова передача у збирач | Багато екземплярів сервісу, потрібна спільна картина одразу | Обрив каналу вимагає буфера на диску, інакше події губляться |
На практиці беруть гібрид: людиночитний рядок у консоль під час локального запуску та структурний запис у збирач для робочих контурів.
Перехід ведуть за сервісами — починаючи з тих, де розбір інциденту займає найбільше часу, і з реєстром полів, щоб нова подія не вимагала правок на боці приймання.
Впровадження за кроками: від аудиту подій до передачі регламентів
Послідовність вибудувана так, щоб схема подій з'явилася раніше, ніж перший лог піде у сховище: переробляти формат на працюючих сервісах дорожче, ніж домовитися про нього на старті. Нижче — порядок, який проходить кожен проєкт.
- Аудит джерел логів. Збираємо інвентаризацію: які сервіси пишуть самі, які віддають потік у stdout, у кого події розмазані по кількох потоках і файлах. На виході — таблиця з частотою подій і добовим обсягом за кожним джерелом.
- Проєктування схеми подій. Фіксуємо обов'язковий набір полів: час, рівень, ім'я сервісу, ідентифікатор запиту, код помилки, текст повідомлення. Окремо — словник значень рівня та правила іменування, щоб
errorіerrне жили в одному індексі. - Налаштування застосунку. Запис іде одним рядком на подію, без перенесень усередині повідомлення — інакше збирач розірве його на частини. Контекст запиту прокидається через спільну обгортку й потрапляє в кожен запис автоматично, включно з фоновими задачами.
- Налаштування збирача логів. Розбір полів не потрібен: маршрутизація йде за значеннями, додаються буферизація на диск і обмеження швидкості. Маскування токенів і персональних даних виконується на вході, до запису.
- Перевірка на реальному збої. Відтворюємо контрольовану помилку й проходимо ланцюжок повністю: запис у сервісі, збирач, сховище, пошук за ідентифікатором запиту. Окремо дивимося, чи збираються в один ланцюжок виклики між сервісами.
- Контроль якості та регламенти. Налаштовуємо регулярні перевірки: частка записів без обов'язкових полів, частка нерозпізнаних рівнів, обсяг шуму від перевірок доступності. Передаємо документ зі схемою полів, правилами додавання нових подій і строками зберігання.
Артефакти: схеми полів, конфігурації збирачів, регламенти
Після впровадження в команди залишається не набір налаштувань на сервері, а комплект файлів і документів, за якими структуроване логування можна розширювати без нас.
Усі артефакти версіонуються в тому самому репозиторії, що й код сервісів: правка проходить рев'ю і не живе в чужій голові.
- Словник подій — імена подій з обов'язковими та необов'язковими полями, типами й допустимими значеннями. Він же фіксує, що в лог не потрапляє: токени, тіла запитів повністю, вміст користувацьких файлів.
- Контракт полів і правила його зміни — як додавати й перейменовувати поля, не ламаючи розбір у сховищі та вже побудовані звіти.
- Правила знеособлення логів — що маскується, що хешується, де значення замінюється на ідентифікатор. Задаються в спільній конфігурації, а не в коді кожного сервісу, тому застосовуються однаково.
- Конфігурація збору логів — розбір потоку, буферизація на боці застосунку, поведінка за недоступності сховища, ротація файлів і пороги, за яких запис відкидається замість блокування сервісу.
- Політика зберігання логів — строки за типами подій, розділення оперативного й архівного зберігання, порядок видалення. Окремо зазначено, які події живуть довше через розбір інцидентів.
- Інструкція для чергового — як зібрати всі події одного запиту, які поля фільтрувати першими, готові запити під типові збої та порядок ескалації.
- Регламент змін схеми — хто погоджує правку словника, як перевіряється сумісність і що робити зі старими записами після зміни полів.
Панелі за частотою помилок і затримками збираються поверх тих самих полів — окремого джерела даних для них не потрібно.
Кейс: розбір інциденту скоротився з годин до хвилин
До: шість форматів, пошук навмання
Платформа замовника — шість застосунків: замовлення, білінг, сповіщення, інтеграції, адмінка та фонові задачі. Логи кожне писало вільним текстом у свій файл на своєму вузлі: час і рівень ішли всередину рядка повідомлення, ідентифікатор запиту між сервісами не передавався.
Розбір інциденту починався з обходу вузлів і пошуку за уривками фраз — до першої версії причини минало 3-4 години, а шість алертів із десяти виявлялися хибними: спрацьовували на текстових збігах.
Що змінили і що отримали
Ввели єдину схему події: рівень, мітка часу, сервіс, ідентифікатор запиту, код операції, тривалість, результат.
Ідентифікатор запиту генерується на вході й передається всім ланцюжком внутрішніх викликів — шлях «запит → сервіс → сховище → черга → відповідь» видно одним пошуком за полем, а не підбором виразів за рядками. Збір і пошук ідуть в OpenSearch.
Правила зберігання налаштували окремо: перевірки доступності та рутинні опитування пішли в налагоджувальний рівень і пишуться не за кожним викликом, гарячий контур тримає два тижні, далі дані переїжджають у холодне сховище.
Обсяг логів зріс приблизно втричі, але без цих правил зростання було б у рази більшим, а витрати на зберігання перестали зростати разом із трафіком.
Через три місяці: медіанний час пошуку причини — 8-10 хвилин замість 3-4 годин, хибні спрацювання — один випадок із десяти замість шести, удвічі більше інцидентів чергова зміна закриває без розробників.
На стику сервісів ефект найпомітніший: зв'язка логів за єдиним ідентифікатором одразу показує, який виклик повернув помилку і що йому передувало.
Чим лог відрізняється від метрики та трасування?
Три сигнали відповідають на різні питання, і плутанина починається, коли один використовують замість іншого. Метрика говорить «скільки і як часто»: це агрегат за числовим рядом — частка відмов, час відповіді за хвостовим процентилем.
Лог говорить «що сталося»: це подія з контекстом — ідентифікатор запиту, користувач, параметри виклику. Трасування — «де загубився час»: шлях запиту через сервіси з тривалістю кожного кроку.
Різниця між логом і метрикою видна під час розбору: за графіком зрозуміло, що відмови зросли після викочування, але не на якому вході. Метрика навмисно втрачає контекст заради дешевого зберігання та швидкої агрегації.
Тому в метрику виносять сигнали, на яких будуються оповіщення, а в структуроване логування — те, що розбирають за конкретним випадком.
Трасування запиту закриває третій шар: коли обробка йде через кілька сервісів і чергу, один запис у лозі не покаже, де накопичилися майже всі затримки. Ідентифікатор трасування в кожному записі логу дає перехід від графіка до конкретного виклику.
Головна помилка — дублювання телеметрії: один факт пишуть одразу в метрику, лог і трасування. Це роздуває зберігання, прискорює розходження систем і заплутує вибір інструмента діагностики: черговий відкриває три панелі й не розуміє, яка правильна. Робоче правило — у кожного факту один дім, інші сигнали посилаються на нього ідентифікатором.
Єдиний стандарт передачі телеметрії — OpenTelemetry — знімає прив'язку до вендора: бекенд спостережуваності застосунку змінюється без переписування коду інструментування.
Обмеження та часті питання щодо впровадження
У структурованого логування є ціна, і чесніше назвати її до впровадження. Запис «ключ — значення» займає у два-три рази більше місця, ніж текстовий рядок, а серіалізація на гарячому шляху віднімає час у обробки самого запиту.
Знімаємо це рівнями — налагодження на проді не пишемо, — буферизацією запису та винесенням його в окремий потік, щоб журнал не гальмував основний сценарій.
Друге обмеження — неоднорідний контур: частина сервісів пише у старому текстовому форматі, і спільна схема за ними не збирається, поки збирач не розбере їх правилом. Третє — чутливі дані: у полях запиту регулярно опиняються токени, адреси, ідентифікатори та персональні дані.
Їх маскуємо на прийманні, до запису у сховище, інакше спільний індекс відкриває їх кожному, хто має право на читання журналу.
Чи не втратимо записи під час сплеску навантаження?
Доставка — найтихіша точка відмови: буфер збирача переповнюється, і частина записів губиться без жодної помилки в застосунку.
Тому рахуємо не лише обсяг, а й пропускну здатність приймання, вмикаємо дисковий буфер і тримаємо частку відкинутих записів окремою метрикою з оповіщенням.
Старі сервіси доведеться переписувати?
Ні. Приведення до спільної схеми робиться на боці збирача правилами розбору, застосунок не чіпаємо. Переписуємо лише ті сервіси, які найближчий рік усе одно змінюються за бізнес-логікою — там новий формат ставимо попутно.
Як відокремити персональні дані від інших полів?
Проводимо розбір за схемою та списком полів: що вважається ідентифікатором, що — тілом операції. Далі маскування на прийманні, окремі потоки зберігання зі скороченим строком і розмежуванням доступу, щоб аналітика йшла за знеособленими зрізами.
Обговоримо задачу й повернемося з оцінкою та планом
Розмова починається з вихідних даних — без них оцінка перетворюється на припущення.
Нам достатньо поточної конфігурації логування застосунків, прикладів реальних записів із журналів, схеми розгортання та того, куди сьогодні йдуть події: файли на диску, stdout контейнера, зовнішній збирач.
Що ми уточнимо на дзвінку:
- які сервіси та середовища потрапляють в обсяг робіт, а які залишаються поза задачею;
- які події критичні для розбору збоїв, а які створюють шум і витісняють корисні записи;
- чи потрібен наскрізний ідентифікатор запиту, щоб зв'язати записи різних сервісів в один ланцюжок;
- як у компанії вирішується питання персональних даних у журналах і хто відповідає за політику маскування;
- строки зберігання, обсяг потоку подій і вимоги до швидкості пошуку за архівом;
- хто з боку замовника приймає результат і в якому вигляді очікує документацію.
Далі ми проводимо аудит поточного логування: дивимося, які поля пишуться зараз, де губиться контекст, як влаштовані рівні важливості та ротація.
За підсумками повертаємося з планом робіт із впровадження — порядок переведення сервісів, схема обов'язкових полів, налаштування збирача й зберігання, розбитий на етапи з зрозумілим результатом на кожному.
Окремим пунктом іде передача знань команді: опис формату записів, правила додавання нових полів, готові шаблони запитів до журналів і розбір на живих прикладах. Опишіть задачу логування — повернемося з оцінкою та планом.







