Пошук уроків, статей та іншого контенту
Навчіться проєктувати структуровані логи, визначати рівні важливості та не допускати витоку чутливих даних.
Логування — це запис інформації про роботу програми. Логи допомагають:
знаходити причини помилок;
розуміти послідовність подій у системі;
контролювати продуктивність;
виявляти підозрілу активність;
перевіряти успішність операцій.
Лог не повинен бути просто довільним текстом на кшталт:
Something went wrongТакий запис складно шукати, фільтрувати й аналізувати автоматично. Краще записувати подію у структурованому форматі.
Структурований лог містить набір полів із передбачуваними назвами. Найпоширеніший формат — JSON.
Приклад:
{
"timestamp": "2026-09-02T10:15:30.000Z",
"level": "info",
"message": "Order created",
"service": "orders-api",
"requestId": "req-123",
"orderId": "order-456"
}Кожен запис містить дані, які можна обробити програмно:
timestamp — час події;
level — рівень важливості;
message — короткий опис;
service — назва сервісу;
requestId — ідентифікатор запиту;
orderId — ідентифікатор замовлення.
Завдяки структурі можна виконувати запити на кшталт:
показати всі помилки сервісу orders-api;
знайти всі записи для конкретного requestId;
порахувати кількість помилок за останню годину;
знайти всі події для певного замовлення.
Неструктурований запис:
2026-09-02 User 42 failed to create order order-456Структурований запис:
{
"timestamp": "2026-09-02T10:15:30.000Z",
"level": "error",
"message": "Order creation failed",
"userId": "42",
"orderId": "order-456"
}У другому варіанті userId і orderId є окремими полями. Їх не потрібно витягувати з тексту за допомогою складного пошуку.
Рівень показує важливість події. Набір рівнів може відрізнятися між системами, але зазвичай використовують такі значення.
debugДетальна технічна інформація для розробників.
Приклади:
параметри внутрішнього кроку обробки;
результат перевірки умови;
кількість елементів у проміжному списку.
Такі логи зазвичай вимкнені у production, оскільки їх може бути дуже багато.
infoНормальні важливі події під час роботи системи.
Приклади:
сервер запущено;
користувач успішно увійшов;
замовлення створено;
фонове завдання завершено.
warnНесподівана або потенційно проблемна ситуація, яка не зупинила операцію.
Приклади:
зовнішній сервіс відповів повільніше, ніж зазвичай;
використано запасний механізм;
запит повторено після тимчасової помилки;
користувач наближається до ліміту запитів.
errorОперація не виконалася або сталася помилка, яку потрібно дослідити.
Приклади:
не вдалося підключитися до бази даних;
платіж відхилено через помилку сервісу;
не вдалося обробити повідомлення з черги.
Для помилки важливо записати контекст: назву операції, ідентифікат запиту, ідентифікатор ресурсу та саму помилку.
fatalКритична помилка, через яку процес або сервіс більше не може нормально працювати.
Цей рівень потрібен не кожній програмі. У багатьох системах достатньо error, а критичність визначають за додатковими полями або правилами моніторингу.
Рівень потрібно визначати за наслідками події:
якщо подія є нормальною частиною роботи — info;
якщо вона не є помилкою, але потребує уваги — warn;
якщо конкретна операція не виконалася — error;
якщо потрібна детальна діагностика — debug.
Не варто записувати кожну подію як error. Якщо всі записи мають однаковий рівень, важливі проблеми губляться серед звичайних повідомлень.
Також не слід використовувати info для великих обсягів технічних даних. Це збільшує вартість зберігання логів і ускладнює пошук.
Мінімальний набір полів для структурованого логу:
час події;
рівень;
повідомлення;
назва сервісу або компонента;
ідентифікатор запиту;
контекст події.
Наприклад:
{
"timestamp": "2026-09-02T10:20:00.000Z",
"level": "error",
"message": "Payment request failed",
"service": "checkout-api",
"requestId": "req-789",
"orderId": "order-123",
"errorCode": "PAYMENT_PROVIDER_UNAVAILABLE"
}Один користувацький запит може пройти через кілька сервісів. requestId або correlationId допомагає знайти всі пов’язані записи.
Наприклад:
клієнт надсилає запит до API;
API звертається до сервісу замовлень;
сервіс замовлень звертається до платіжного сервісу;
усі компоненти записують однаковий ідентифікатор запиту.
Тоді помилку можна досліджувати як одну послідовність подій, а не як окремі записи з різних сервісів.
У лог краще записати:
{
"message": "Order created",
"orderId": "order-123",
"userId": "user-42"
}а не весь об’єкт замовлення або користувача. Великі об’єкти:
збільшують обсяг логів;
ускладнюють читання;
можуть містити секретні дані;
можуть змінювати формат логів після зміни моделі даних.
Логи часто доступні розробникам, операторам, системам моніторингу та підрядникам. Тому логування може стати джерелом витоку даних.
Не можна записувати без спеціальної потреби:
паролі;
токени доступу;
ключі API;
повні номери банківських карток;
секретні ключі;
значення cookie для автентифікації;
персональні дані, якщо вони не потрібні для діагностики.
Поганий приклад:
{
"message": "User login",
"email": "anna@example.com",
"password": "secret-password",
"token": "eyJhbGciOi..."
}Краще:
{
"message": "User login succeeded",
"userId": "user-42"
}Якщо значення потрібне для діагностики, його можна замаскувати.
{
"message": "Payment card selected",
"cardLast4": "4242"
}Для email можна записати частково приховане значення:
{
"email": "a***@example.com"
}Однак маскування не повинно створювати ілюзію безпеки. Навіть частково приховані дані можуть бути персональними. Найкраще правило — не записувати значення, яке не потрібне для вирішення технічної проблеми.
Нижче наведено runnable-приклад для Node.js без сторонніх бібліотек. Логер:
створює JSON-записи;
підтримує рівні debug, info, warn та error;
додає час і назву сервісу;
не записує пароль і токен;
дозволяє передавати контекст події.
const levels = {
debug: 10,
info: 20,
warn: 30,
error: 40,
};
const minimumLevel = process.env.LOG_LEVEL || "info";
const service = "orders-api";
function sanitize(context = {}) {
const safeContext = { ...context };
// Видаляємо поля, які не можна записувати до логів.
delete safeContext.password;
delete safeContext.token;
delete safeContext.accessToken;
delete safeContext.apiKey;
// Залишаємо лише останні чотири цифри номера картки.
if (typeof safeContext.cardNumber === "string") {
safeContext.cardLast4 = safeContext.cardNumber.slice(-4);
delete safeContext.cardNumber;
}
return safeContext;
}
function log(level, message, context = {}) {
if (levels[level] < levels[minimumLevel]) {
return;
}
const entry = {
timestamp: new Date().toISOString(),
level,
service,
message,
...sanitize(context),
};
console.log(JSON.stringify(entry));
}
log("debug", "Database query built", {
queryName: "findOrder",
});
log("info", "Order created", {
requestId: "req-123",
orderId: "order-456",
userId: "user-42",
});
log("warn", "Payment provider response is slow", {
requestId: "req-123",
durationMs: 1800,
});
log("error", "Order creation failed", {
requestId: "req-999",
orderId: "order-789",
password: "should-not-be-logged",
token: "secret-token",
cardNumber: "4242424242424242",
});Запустіть файл командою:
node logger.jsЗа замовчуванням будуть показані записи рівнів info, warn та error. Для увімкнення детальних логів:
LOG_LEVEL=debug node logger.jsУ результаті поле password не потрапить до логу, а номер картки буде перетворено на cardLast4.
Повідомлення має бути коротким і стабільним:
{
"message": "Order created"
}Не варто вбудовувати всі змінні в текст:
User Anna created order order-123 from IP 192.0.2.10Краще зберігати значення окремими полями:
{
"message": "Order created",
"userId": "user-42",
"orderId": "order-123",
"clientIp": "192.0.2.10"
}Так логи легше фільтрувати й групувати. Назви повідомлень також бажано робити стабільними: не змінювати формулювання залежно від значень змінних.
Для помилок корисно мати:
назву операції;
код помилки;
ідентифікатор запиту;
ідентифікатор ресурсу;
тривалість операції, якщо проблема пов’язана з часом;
стек помилки, якщо це дозволено політикою безпеки.
Логи займають місце і можуть мати вартість зберігання. Надмірне логування створює кілька проблем:
важливі записи губляться;
пошук стає повільнішим;
збільшується навантаження;
зростає ризик випадкового витоку даних.
Перед додаванням логу варто запитати:
Яку проблему допоможе діагностувати цей запис?
Чи достатньо ідентифікатора події замість повного об’єкта?
Чи не містить контекст секретних даних?
Який рівень підходить для цієї події?
Чи буде запис корисним у production?
Це одна з найнебезпечніших помилок. Секрети можуть залишатися в системах зберігання логів навіть після видалення з основної бази даних.
error для кожної подіїЗвичайний вхід користувача або пропущений необов’язковий параметр не є системною помилкою. Неправильний рівень ускладнює моніторинг.
Без requestId складно поєднати події, що виникли під час одного запиту, особливо у системі з кількома сервісами.
Повний об’єкт може містити зайві поля, персональні дані або секрети. Записуйте лише необхідний контекст.
Якщо в одному місці використовується userId, в іншому user_id, а в третьому user, пошук і агрегація стають складнішими. Назви полів потрібно узгодити.
Повідомлення Operation failed не пояснює, яка саме операція не вдалася. Краще використовувати Order creation failed і додати код помилки.
Довільний текст може бути зрозумілим людині, але його складно надійно обробляти автоматично. Для системних логів краще використовувати структурований формат.
Логи описують події, що відбуваються під час роботи програми.
Структурований формат, наприклад JSON, спрощує пошук і автоматичний аналіз.
debug призначений для детальної діагностики, info — для звичайних подій, warn — для потенційних проблем, error — для невдалих операцій.
До логів варто додавати час, рівень, сервіс, повідомлення, requestId та потрібний контекст.
Паролі, токени, ключі й інші секрети не можна записувати до логів.
Записуйте лише ті дані, які потрібні для діагностики.
Узгоджені назви полів і стабільні повідомлення роблять логи корисними для пошуку та моніторингу.