1. Недоліки plain-text логів
На старті проєкту звичайні текстові логи здаються ідеальними: відкрили консоль — побачили «застосунок запустився», «курси завантажилися», «порт такий-то» — і краса. Проблема з’являється не тому, що текст поганий, а тому, що він занадто «людський»: очима його читати легко, а стабільно розбирати на поля — складно. Особливо коли ви хочете шукати, фільтрувати й порівнювати записи не навмання, а за фактами.
Уявіть типовий життєвий сценарій catalog-service. Ви отримали скаргу: «іноді не знаходиться курс за slug». Ви ввімкнули більше логів, з різних пакетів посипалися повідомлення, і вам треба відповісти на просте запитання: де саме виникла помилка та з якими параметрами. У plain-text форматі потрібні дані часто сховані всередині повідомлення, а формат записів може відрізнятися від класу до класу. У результаті ви або на око гортаєте консоль, або починаєте бавитися з регулярними виразами. Особливо «весело», коли повідомлення трохи змінилося, а пошук за шаблоном раптом перестав працювати.
Є й більш приземлена проблема: ви дуже швидко хочете фільтрувати за рівнем, класом-логером, іменем сервісу, потоком, конкретним кореляційним ідентифікатором операції. У plain-text це можливо, але крихко. Будь-яка дрібна зміна формату призводить до того, що машина перестає розуміти логи так само добре, як людина. А ми ж пишемо бекенд не лише для людей. Інколи його читають і машини — просто тому, що їх у продакшені зазвичай більше.
Щоб не звучало надто «enterprise», сформулюємо простіше: plain-text лог — це коли всі важливі поля склеєні в один рядок. Structured лог — це коли ці поля залишаються полями, і вам не потрібно «виколупувати» рівень або логер із тексту, наче родзинки з булочки.
Невелика таблиця, щоб зафіксувати сенс на пальцях:
| Завдання | Plain-text лог | Structured лог |
|---|---|---|
| Швидко зрозуміти очима, що відбувається | Зручно | Часто незручно, бо JSON виходить багатослівним |
| Знайти всі записи, де level=ERROR | Зазвичай через grep/шаблон, іноді крихко | За полем level — стабільно |
| Фільтрувати за логером/пакетом | За збігом рядка | За окремим полем |
| Автоматично аналізувати логи програмою | Складніше, потрібен парсинг тексту | Простіше, бо це вже JSON або набір полів |
2. Structured logging і машиночитаний рядок лога
Structured logging легко випадково зрозуміти неправильно, тож почнімо з чесної й простої моделі. У кожної log event є набір даних: час, рівень, імʼя логера, повідомлення, іноді виняток, іноді імʼя потоку та інші службові поля. У plain-text форматі ці дані перетворюються на один рядок «для людини». У structured форматі вони залишаються структурою — найчастіше у вигляді JSON-об’єкта — і друкуються як один рядок JSON, придатний для машинного парсингу.
Термін машиночитаний рядок лога означає: кожен рядок лога — це самостійний об’єкт фіксованого формату, який можна розібрати без вгадувань. Якщо це JSON-лог, то один рядок — один JSON-об’єкт. Так, це виглядає так, ніби «в консоль вилили словник», але саме в цьому й цінність: поля не загубилися й не розчинилися всередині тексту.
Корисно уявити собі шлях лога — дуже спрощено, без деталей Logback і без занурення в конфіги:
flowchart LR %% Спрощена схема: важливо зрозуміти, що structured — це про «як друкуємо», а не про «як пишемо log.info» A["Ваш код: log.info(...)"] --> B["Подія логування (поля: time, level, logger, message, ...)"] B --> C["Енкодер/форматувальник (plain або structured)"] C --> D["Вивід: консоль/файл"]
Зверніть увагу на важливу думку: структурованість — це про формат виводу, а не про те, що ми «по-іншому пишемо log.info(...)». Ви створюєте подію логування так само, а ось «принтер» друкує її або як рядок, зрозумілий людині, або як машиночитаний JSON.
Приклад надмінімального structured-лога, щоб побачити ідею:
{
"timestamp": "2026-03-19T10:15:00Z",
"level": "INFO",
"logger": "com.example.catalogservice.catalog.service.CourseCatalogService",
"message": "Завантажено рекомендовані курси: 4"
}
У реальності Spring Boot підтримує кілька стандартних структурованих форматів, наприклад ECS, GELF і Logstash, і вони відрізняються назвами полів та їхнім групуванням. У цій лекції нам важлива не таблиця всіх полів, а ключовий ефект: рівень, логер, повідомлення й час стають окремими полями, а не частиною «склеєного» рядка.
3. Формат виводу замість log.info(...)
Найприємніша новина: щоб перейти на structured logging, вам зазвичай не потрібно переписувати прикладний код. Якщо ви вже логуєте правильно — через SLF4J-логер, із плейсхолдерами {} — то структуризація змінює передусім те, як лог виглядатиме назовні. Тобто ми не переписуємо сервіси й контролери, а акуратно підкручуємо конфігурацію — як і годиться в Spring Boot, де більша частина поведінки керується конфігурацією.
Давайте візьмемо невеликий, зрозумілий фрагмент із контексту catalog-service. Наприклад, наш сервіс каталогу логує факт завантаження featured-курсів. Код при цьому максимально звичайний, без жодної structured-магії:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CourseCatalogService {
// SLF4J-логер: код не "знає" про JSON, він просто створює log-події
private static final Logger log =
LoggerFactory.getLogger(CourseCatalogService.class);
public void logFeaturedCount(int count) {
// Плейсхолдер {} + окремий аргумент: так параметр передається системі логування, а не склеюється в рядок
log.info("Завантажено рекомендовані курси: {}", count); // повідомлення залишиться тим самим
}
}
Тут важливо не прогледіти два моменти. По-перше, ми використовуємо плейсхолдер {} і передаємо параметр окремо, а не склеюємо рядок через +. Це і швидше, і чистіше, і краще дружить із системою логування. По-друге, у коді немає й не повинно бути жодного JSON. Це принципово: structured logging не має перетворювати ваш доменний код на код під лог-формат.
Якщо ви зараз увімкнете structured logging, цей самий запис log.info(...) піде назовні вже в структурованому вигляді. Тобто зміниться «фотографія», а не «модель». І це дуже в дусі Boot: поведінка змінюється через конфігурацію, а не через переписування десяти класів.
4. Увімкнення structured logging у Spring Boot 4
Тепер переходимо до найпрактичнішого: як увімкнути structured logging так, щоб він справді запрацював, а не залишився «ідеєю в голові». У Spring Boot 4 structured logging вмикається конфігурацією. Нас цікавлять дві властивості: одна відповідає за консоль, інша — за файл. Навіть якщо ви поки що пишете лише в консоль, важливо розуміти, що це два незалежні напрями: консоль зазвичай читає людина, а файли й потоки логів часто читає інструмент.
Ось компактна табличка за налаштуваннями, щоб не плутати їх:
| Куди пишемо | Властивість | Чим керує |
|---|---|---|
| У консоль (stdout) | logging.structured.format.console | Формат виводу в консоль |
| У файл | logging.structured.format.file | Формат виводу у файл |
Для старту нам достатньо увімкнути structured-формат для консолі. Наприклад, у src/main/resources/application.yaml можна додати:
spring:
application:
name: catalog-service # імʼя сервісу потрапить у structured-поля (наприклад, service.name)
logging:
structured:
format:
console: ecs # вмикаємо structured-формат для консолі (stdout)
Це мінімальне увімкнення structured logging у консоль. Для catalog-service у курсі вважаємо ecs робочою базовою точкою відліку для structured-консолі. Саме на ній фіксуємо подальші приклади та фінальну політику проєкту. Значення ecs — це назва одного з підтримуваних форматів. GELF і Logstash нам ще знадобляться, але вже як формати, які потрібно вміти впізнавати й читати, а не як плаваючий вибір усередині проєкту.
Зверніть увагу: ми не пишемо logback.xml, не підключаємо дивні енкодери вручну й не ліземо в нетрі конфігурації. Ми робимо рівно те, що Boot любить найбільше: описуємо бажану поведінку декларативно.
Якщо вам потрібно увімкнути structured-формат для файлу, логіка буде аналогічною — змінюється лише місце призначення:
logging:
structured:
format:
file: ecs # вмикаємо structured-формат для файлу (зазвичай його забирає збирач логів)
Ми свідомо не обговорюємо тут налаштування імені файлу, ротацію й архівування — це окремий всесвіт. Зараз фіксуємо головне: у Boot structured logging вмикається властивостями, і ваш Java-код при цьому не зобовʼязаний «знати», що логи стали JSON.
І нарешті, важлива практична думка: structured logging — це не «вічно увімкнена опція». Його потрібно вмикати там, де він дає цінність. Якщо ви увімкнули JSON-логи в консоль і раптом відчули, що дебажити стало незручно, це не означає, що ви помилилися. Просто ви ввімкнули інструмент не під ту задачу. Консоль зазвичай для очей, structured-формат — для стабільної обробки.
5. Порівняння: plain vs structured
Щоб не було відчуття «я щось увімкнув, але не розумію, що змінилося», давайте порівняємо два виводи для одного й того самого запису log.info("Завантажено рекомендовані курси: {}", count) з CourseCatalogService. З точки зору коду нічого не змінилося. Змінився лише спосіб друку події логування.
Приклад того, як подібний запис може виглядати в plain-text форматі:
2026-03-19T10:15:00.123 INFO 18432 --- [catalog-service] c.e.c.c.service.CourseCatalogService : Завантажено рекомендовані курси: 4
А тепер умовний structured-варіант:
{"@timestamp":"2026-03-19T10:15:00.123Z","log.level":"INFO","log.logger":"com.example.catalogservice.catalog.service.CourseCatalogService","service.name":"catalog-service","message":"Завантажено рекомендовані курси: 4"}
Якщо ви зараз думаєте: «Виглядає гірше, ніж раніше», — вітаю, ви правильно читаєте очима. Structured лог часто справді гірше читається очима, бо оптимізований не під людину, а під стабільні поля. Зате в нього з’являється суперсила: рівень — це поле log.level, логер — окреме поле, імʼя сервісу — окреме поле. Машина не повинна вгадувати, де саме в рядку сховане імʼя логера, і не повинна сподіватися, що ви не зміните порядок частин.
Іноді корисно показати JSON гарно, хоча в логах він зазвичай в один рядок. Це не новий формат, а просто візуалізація того, що всередині:
{
"@timestamp": "2026-03-19T10:15:00.123Z",
"log.level": "INFO",
"service.name": "catalog-service",
"message": "Завантажено рекомендовані курси: 4"
}
І тут добре видно, заради чого все це й затівалося: якщо вам потрібно швидко відфільтрувати всі INFO від нашого сервісу, ви не парсите рядок. Ви дивитеся на поля.
6. Де корисний structured logging
З structured logging легко потрапити в пастку «раз це сучасно — увімкну всюди». Але логування — це не показ мод. Воно або допомагає діагностувати систему, або заважає. Structured-формат особливо корисний там, де логи мають бути стабільними вхідними даними для обробки: для пошуку, агрегації, побудови статистики, розбору за полями. Це може бути і локально, якщо ви хочете швидко відбирати записи за полями, і в середовищах, де логи збираються та аналізуються.
Але є й зворотний бік. Коли ви запускаєте catalog-service в IDE і просто читаєте консоль, JSON може перетворити простий дебаг на читання бухгалтерського звіту. Вам хочеться побачити короткий рядок, а ви бачите об’єкт із полями. Це нормально. Structured logging не скасовує цінності plain-text виводу, він просто розв’язує інше завдання.
Дуже важливо зафіксувати ще одну думку, щоб не було магічних очікувань: structured logging не робить лог «розумним» автоматично. Якщо ви написали погане повідомлення, воно залишиться поганим — просто в JSON. Ось приклад поганого лога, який формально structured, але практично марний:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartupSummaryRunner {
private static final Logger log = LoggerFactory.getLogger(StartupSummaryRunner.class);
public void run() {
// Проблема не у форматі, а в сенсі: за таким логом незрозуміло, що саме «Запущено»
log.info("Запущено"); // занадто мало змісту
}
}
А ось більш корисний варіант — той самий рівень, той самий логер, але повідомлення вже несе конкретний сигнал:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartupSummaryRunner {
private static final Logger log = LoggerFactory.getLogger(StartupSummaryRunner.class);
public void run(int coursesCount) {
// Тут є конкретний факт + параметр передається окремо через {}, що коректно для логування
log.info("Каталог запущено. Завантажено курсів: {}", coursesCount); // зрозумілий сигнал
}
}
Structured-формат не рятує від дисципліни: не варто логувати величезні об’єкти цілком лише тому, що JSON нібито «для цього». Якщо ви почнете друкувати весь список курсів у лог, ви отримаєте гігантський шум, і жодна структурованість не поверне з нього корисний сигнал.
І так — structured-формат теж не скасовує базового правила безпеки. Якщо ви залогували токен, то ви залогували токен. Він буде не менш небезпечним, просто гарно упакованим у JSON.
7. Типові помилки під час увімкнення structured logging
Structured logging — штука корисна, але новачкам він часто «стріляє в ногу» не тому, що технологія погана, а тому, що очікування від неї не збігаються з реальністю. Якщо заздалегідь знати типові помилки, можна зекономити купу часу, а заодно не перетворити логи на дивний артефакт, який ніхто не хоче читати.
Помилка № 1: увімкнути structured logging і очікувати «красиву консоль».
Structured-логи часто виглядають у консолі гірше, ніж plain-text, і це нормально. Вони не зобовʼязані бути зручними для очей. Перед увімкненням варто чесно відповісти собі: ви зараз читаєте лог очима чи вам важливі поля для стабільного розбору. Якщо мета — швидко дебажити вручну, plain-text іноді виграє.
Помилка № 2: думати, що JSON-форма замінює хороший текст повідомлення.
Якщо повідомлення «ні про що» або надто розпливчасте, structured-формат лише запакує цю розпливчастість у JSON. Сенс усе одно залишається в повідомленні. Завантаження... і Запущено майже завжди гірші, ніж Каталог запущено. Завантажено курсів: 12 — незалежно від формату.
Помилка № 3: логувати великі об’єкти цілком, бо «раз уже JSON».
Друкувати цілий список курсів, цілу конфігурацію або всю відповідь сервера «для зручності» — дуже спокусливо. Але це народжує шум, збільшує обсяг логів і робить реальні проблеми менш помітними. Краще логувати агрегати й факти: кількість, slug, прапорці, важливі рішення, а не всі дані світу.
Помилка № 4: змішати різні стилі логів без чіткого правила.
Наприклад, частина середовищ пише plain-text, частина — structured, а частина — і те, і інше впереміш, бо конфігурація розʼїхалася. У результаті ви не впевнені, що саме побачите під час запуску. Логи мають бути передбачуваними: або тут plain, або тут structured, і це закріплено конфігом, а не пам’яттю розробника.
Помилка № 5: переплутати structured logging із налаштуванням рівнів.
Structured logging — це не «DEBUG замість INFO» і не «більше логів». Це форма виводу. Можна писати structured-логи на INFO, можна на DEBUG, можна на ERROR. Рівні вирішують, скільки подій ви пишете; structured-формат вирішує, як ці події виглядають і наскільки легко їх розбирати за полями.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ