Головна / Статті

Структуровані шаблони журналювання для ефективної налагодки API Node.js

Замініть розкидані виклики console.log у API Node.js на структуроване логування: контекст запиту, ідентифікатори запитів, рівні логів та метрики часу виконання для швидшого відлагоджування.

Видавець: REACTAPP.TOP8 хв читання1883 слів

Джерела: I Stopped Using Console.log() to Debug Node.js APIs

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

Типова початкова практика виглядає так:

console.log("HERE 1");

const user = await getUser(userId);

console.log("HERE 2", user);

const orders = await getOrders(userId);

console.log("HERE 3", orders);

console.log("HERE 4");

Потім ви запускаєте API та стежите за терміналом, сподіваючись знайти підказки.

Іноді це спрацьовує.

Іноді — ні.

А коли щось ламається, інстинктивно хочеться додати ще один console.log().

Потім ще один.

І ще один.

Зрештою термінал заповнюється чимось на кшталт:

HERE 1
HERE 2
HERE 2
HERE 3
HERE 1
HERE 4
HERE 3

І ви все одно не наблизилися до розуміння того, що насправді сталося.

Саме ця розчарування зазвичай спонукає людей переосмислити свій підхід до відладки бекенд-систем.

1. Проблема випадкових журналів

console.log() сам по собі не є проблемою.

Це справді корисний інструмент.

Справжня проблема полягає у використанні їх без будь-якої структури.

Візьмемо цей приклад:

console.log("user", user);
console.log("order", order);
console.log("data", data);
console.log("result", result);

Через кілька хвилин після запуску додатку ви бачите сотні рядків.

Тоді виникають справжні запитання:

Який запит створив цей конкретний рядок журналу?

Який користувач спричинив збій?

Скільки часу насправді зайняв виклик бази даних?

Який ендпоїнт у той момент працював?

Прості, неструктуровані журнали не можуть відповісти на жодне з цих запитань.

2. Початок запису контексту

Замість того, щоб писати:

console.log("Payment failed");

набагато корисніше писати:

console.error({
  level: "error",
  message: "Payment failed",
  userId,
  orderId,
  endpoint: req.originalUrl,
})

Тепер запис у журналі містить справжній контекст.

Наприклад, він може виглядати так:

{
  "level": "error",
  "message": "Payment failed",
  "userId": "user_123",
  "orderId": "order_456",
  "endpoint": "/api/orders/payment"
}

Цей додатковий контекст перетворює відлагоджування з процесу здогадок на щось кероване.

3. ID запитів змінили все

У вашому журналі подій може бути таке:

Database timeout
Payment failed
Redis error
User created
Database timeout

Але який саме запит спричинив кожну з цих помилок?

Це неможливо визначити.

Саме тут і стає важливим ID запиту.

Щось на кшталт:

req_8f92a1

Кожен рядок журналу подій, пов’язаний із цим запитом, може мати один і той самий ідентифікатор.

req_8f92a1 → Request started
req_8f92a1 → User authenticated
req_8f92a1 → Database query started
req_8f92a1 → Database query completed
req_8f92a1 → Payment API called
req_8f92a1 → Payment failed

Завдяки цьому ви можете відстежувати один запит від початку до кінця, незалежно від завантаженості системи.

4. Вимірюйте час виконання

Запис у журналі подій, який просто містить:

Database query completed

не розповідає всієї історії.

Насправді вам потрібно щось на кшталт:

Database query completed in 1,842ms

Простий спосіб це зафіксувати:

const start = Date.now();

const users = await getUsers();
console.log({
  operation: "getUsers",
  duration: Date.now() - start
});

Як тільки ви почнете вимірювати, можливо, побачите щось на кшталт:

getUsers → 38ms
getOrders → 1,923ms
getProducts → 42ms

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

"API здається повільним."

5. Не записуйте все

Це помилка, яку варто відразу вказати.

Раніше існувало припущення:

"Більше журналів полегшує виправлення помилок."

Це насправді не так.

Надмірне записування лише створює зайвий шум.

Наприклад:

console.log("Entering function");
console.log("User found");
console.log("Checking order");
console.log("Order found");
console.log("Checking product");
console.log("Product found");
console.log("Returning response");

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

Краще зосередитися лише на подіях, які справді мають значення:

Request started
Request completed
Authentication failed
Database error
External API failure
Important business event
Unexpected exceptio

Мета не в тому, щоб записувати кожну дрібницю, яка відбувається.

Мета — зробити важливі події легкодоступними.

6. Використовуйте рівні журналів

Системи виробництва повинні розділяти журнали за рівнем серйозності.

Зазвичай це означає:

DEBUG
INFO
WARN
ERROR

DEBUG

Деталі високого рівня дроблення, призначені для розробки, наприклад:

SQL query parameters
Cache lookup details
Internal state

INFO

Звичайні події, які відбуваються під час нормальної роботи:

Server started
User logged in
Order created
Payment completed

WARN

Сталося щось незвичайне, але додаток продовжує працювати:

Cache unavailable
Retrying external API
Slow database query

ERROR

Щось справді зламалося:

Database connection failed
Payment failed
Unhandled exception

Це розрізнення стає особливо корисним, коли журнали надходять у централізовану систему обліку журналів.

7. Уникайте потрапляння конфіденційної інформації до журналів

Цьому пункту варто приділити особливу увагу.

Ніколи не писайте бездумно щось на кшталт:

console.log(req.body);

адже тіло вхідного запиту може легко містити щось на кшталт:

{
  "email": "user@example.com",
  "password": "secret",
  "token": "..."
}

Якщо ви записуєте це без роздумів, ви просто записуєте конфіденційні дані безпосередньо у свої файли журналу.

Уважно стежте за такими полями:

  • Паролі
  • JWT-токени
  • Ключі API
  • Токени доступу
  • Деталі оплати
  • Особиста інформація

Замість того, щоб записувати весь об’єм даних, усвідомлено вирішуйте, що саме варто зберегти.

8. Правильний спосіб логування помилок

Якщо написати таке:

catch (error) {
  console.log(error);
}

цього буде занадто мало для роботи в умовах продакшену.

Вам потрібен контекст навколо помилки, щоб вона мала сенс.

Щось більш схоже на це є набагато кориснішим:

catch (error) {
  logger.error({
    message: "Failed to create order",
    error: error.message,
    stack: error.stack,
    userId,
    orderId
  });
throw error;
}

За такого підходу ви фіксуєте:

  • Що сталося
  • Повідомлення про помилку
  • Стек викликів
  • Будь-які відповідні ідентифікатори

Це поєднання перетворює запис у журналі на щось, з чим можна дійсно працювати.

9. Відлагодження рівня бази даних

Припустимо, ваш API став повільним.

Замість того, щоб записувати щось настільки нечітке, як:

API is slow

намагайтеся використовувати щось більш конкретне, наприклад:

GET /api/orders
Total: 2,431msAuthentication: 24ms
Database: 2,312ms
Serialization: 31ms

Цей опис точно показує, де потрібно шукати далі.

Звідти ви можете детальніше розглянути сам запит.

Ви можете виявити щось на кшталт:

Sequential Scan

Поширені причини включають:

  • Відсутній індекс
  • Шаблон запиту N+1
  • Погано оптимізоване об’єднання даних
  • Завантаження набагато більшої кількості даних, ніж потрібно

Основна ідея полягає у тому, що спостережуваність замінює здогадки на докази.

10. Відлагодження викликів зовнішніх сервісів

Payment API
Shipping API
Email API
WhatsApp API

Раптово виконання однієї запиту займає 8 секунд.

Яка з цих служб несе відповідальність?

Без даних про час виконання ви просто здогадуєтесь.

Натомість зафіксуйте щось на кшталт:

Request started
Payment API: 142ms
Shipping API: 6,821ms
Database: 81msRequest completed: 7,100ms

Тепер «вузьке місце» стало очевидним.

Сам сервер Node.js може працювати ідеально.

Уповільнення виникає через постачальника доставки.

Саме тому настільки важливо відстежувати час виконання зовнішніх залежностей.

11. Хороші журнали відповідають на конкретні запитання

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

Що сталося?

Payment request failed

Коли це сталося?

2026-09-16T10:15:23Z

Який запит?

req_8f92a1

Який користувач?

user_123

Який кінцевий пункт?

POST /api/orders/payment

Яка залежність вийшла з ладу?

payment-provider

Скільки часу це зайняло?

5,002ms

З урахуванням усього цього запис журналу нарешті виправдовує своє існування.

12. Журналування — це лише частина можливостей спостереження

Це була ще одна сфера, де початкова уява була хибною.

Можливості спостереження — це не просто:

console.log()

Продуктивна система зазвичай ґрунтується на спільній роботі трьох основних елементів:

Logs
+
Metrics
+
Traces

Журнали

Вони відповідають на запитання:

Що сталося?

Метрики

Вони відповідають на запитання:

Як часто це відбувається?

Наприклад:

Error rate: 2.4%
Average latency: 820ms
CPU: 75%

Трекування

Вони допомагають відповісти на запитання:

Куди насправді пішов час у різних залучених сервісах?

Наприклад:

Request
  ↓ 20ms
API
  ↓ 80ms
Database
  ↓ 1,200ms
Shipping Service
  ↓ 50ms
Response

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

13. Створення мінімального логгера

logger.info({
  event: "order_created",
  orderId,
  userId
});

console.log(
  "order created",
  orderId,
  userId
);

14. Процес відлагодження зараз

Something is broken
        ↓
Add console.log()
        ↓
Run again
        ↓
Add another console.log()
        ↓
Run again

Something is broken
        ↓
Identify the failing request
        ↓
Find the request ID
        ↓
Follow the logs
        ↓
Measure dependencies
        ↓
Find the bottleneck
        ↓
Fix it
        ↓
Verify with measurements

15. Найважливіше запитання

„Який код мені потрібно змінити?“

Краще запитати:

„Які докази у мене є щодо причини збою?“

Такий підхід ставить вас у набагато краще положення.

Якщо ви ще не знаєте, де саме виникає проблема, зміна коду — це лише припущення.

Чек-лист для журналізації на бекенді

Перед випуском сервісу на бекенді корисно підтвердити:

[ ] Structured logging
[ ] Request IDs
[ ] Error logs
[ ] Stack traces
[ ] Request duration
[ ] Database duration
[ ] External API duration
[ ] Useful business events
[ ] Log levels
[ ] Sensitive data excluded
[ ] Centralized logs
[ ] Error monitoring
[ ] Basic metrics
[ ] Health checks

Заключні міркування

console.log() все ще має своє місце.

Це не те, чого потрібно повністю уникати.

Справжня проблема — це використання його як єдиного інструменту для відлову помилок.

Продакшн-бекенд потребує більшого, ніж просто:

console.log("something happened");

Вам потрібен огляд:

What happened?
When?
Where?
Why?
For which request?
For which user?
How long did it take?
Which dependency was involved?

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

Найбільша зміна у підході зводиться до однієї ідеї:

Не додавайте більше журналів подій лише тому, що щось зламалося.

Натомість фіксуйте правильні деталі, щоб наступного разу, коли щось піде не так, ви могли справді зрозуміти, що сталось.

Адже в умовах робочої среди:

Якщо ви не бачите, що робить ваша система, ви виправляєте помилки у темряві.