Пошук уроків, статей та іншого контенту
Організуйте рівні логів, формат повідомлень і виведення логів, придатне для діагностики продакшен-застосунків.
Логування — це запис подій, які відбуваються під час роботи застосунку. У production логи допомагають:
зрозуміти, що сталося під час помилки;
визначити час і місце виникнення проблеми;
відстежити важливі операції;
перевірити стан застосунку без підключення налагоджувача;
автоматично збирати та фільтрувати повідомлення.
Лог має бути корисним не лише розробнику, який його написав. Його можуть читати системи моніторингу, адміністратори та інші розробники.
Погане повідомлення:
Щось пішло не такКорисніше повідомлення:
Не вдалося створити замовлення: база даних недоступнаЩе краще — структурований запис із додатковими полями:
{
"timestamp": "2026-08-19T10:15:30.000Z",
"level": "error",
"message": "Не вдалося створити замовлення",
"orderId": "order-42",
"error": {
"name": "Error",
"message": "База даних недоступна",
"stack": "Error: База даних недоступна..."
}
}Рівень показує важливість повідомлення. Для production-застосунку достатньо почати з таких рівнів:
debugДетальна технічна інформація для налагодження.
Користувач знайдений у кешіТакі повідомлення зазвичай вимикають у production, щоб не створювати зайвий шум.
infoНормальні важливі події застосунку:
сервер запущено;
користувач увійшов у систему;
замовлення створено;
фонову операцію завершено.
warnПотенційна проблема, яка не зупинила виконання:
використано резервне значення;
зовнішній сервіс відповідає повільно;
скоро завершиться термін дії ключа.
errorПомилка, через яку операція не виконалася або потребує уваги:
не вдалося підключитися до бази даних;
платіжний сервіс повернув помилку;
сталася необроблена помилка під час запиту.
Зазвичай рівні мають такий порядок важливості:
debug < info < warn < errorЯкщо встановити мінімальний рівень warn, у виведенні залишаться лише warn та error.
Для production зручно використовувати JSON. Один запис логу — один JSON-об’єкт в одному рядку. Такий формат називають JSON Lines або NDJSON.
Переваги:
його легко обробляють системи збору логів;
поля можна фільтрувати окремо;
не потрібно розбирати довільний текст;
значення мають передбачувані назви.
Базовий запис може містити:
timestamp — час події;
level — рівень;
message — короткий опис;
додаткові поля контексту;
дані помилки, якщо вона виникла.
Час краще записувати в ISO 8601 у форматі UTC:
2026-08-19T10:15:30.000ZНе варто вбудовувати всі значення в текст повідомлення:
logger.info(`Користувач ${userId} створив замовлення ${orderId}`);Краще передавати значення окремими полями:
logger.info("Користувач створив замовлення", {
userId,
orderId
});Так поле userId можна буде окремо фільтрувати та аналізувати.
У production-застосунках Node.js зазвичай виводить логи у стандартні потоки:
stdout — звичайні повідомлення;
stderr — попередження та помилки.
Інфраструктура, у якій працює застосунок, може перехоплювати ці потоки та передавати їх у систему зберігання або аналізу.
Для локального запуску цього достатньо:
console.log("Повідомлення");
console.error("Помилка");Однак пряме використання console.log у великому застосунку має недоліки:
немає єдиного формату;
складно централізовано вимикати рівні;
легко випадково вивести секретні дані;
повідомлення важче тестувати та обробляти.
Тому зручно створити невеликий об’єкт логера, який відповідає за формат і фільтрацію.
Наведений приклад не потребує сторонніх бібліотек. Він:
підтримує чотири рівні;
читає мінімальний рівень із LOG_LEVEL;
виводить JSON;
додає час;
правильно серіалізує помилки;
використовує stdout для звичайних повідомлень і stderr для помилок.
// logger.js
const levels = {
debug: 10,
info: 20,
warn: 30,
error: 40
};
const configuredLevel = process.env.LOG_LEVEL || "info";
const minimumLevel = levels[configuredLevel] ?? levels.info;
function serializeError(error) {
if (!(error instanceof Error)) {
return error;
}
return {
name: error.name,
message: error.message,
stack: error.stack
};
}
function write(level, message, context = {}) {
if (levels[level] < minimumLevel) {
return;
}
const entry = {
timestamp: new Date().toISOString(),
level,
message,
...context
};
const output = JSON.stringify(entry);
if (level === "error") {
process.stderr.write(`${output}\n`);
} else {
process.stdout.write(`${output}\n`);
}
}
const logger = {
debug(message, context) {
write("debug", message, context);
},
info(message, context) {
write("info", message, context);
},
warn(message, context) {
write("warn", message, context);
},
error(message, context = {}) {
const normalizedContext = {
...context
};
if (normalizedContext.error instanceof Error) {
normalizedContext.error = serializeError(normalizedContext.error);
}
write("error", message, normalizedContext);
}
};
logger.debug("Починаємо завантаження конфігурації");
logger.info("Застосунок запущено", { port: 3000 });
logger.warn("Використовується резервний параметр", {
parameter: "timeout"
});
try {
throw new Error("Не вдалося підключитися до бази даних");
} catch (error) {
logger.error("Помилка під час запуску", { error });
}Збережіть код у файл logger.js і запустіть:
node logger.jsЗа замовчуванням мінімальним рівнем буде info, тому повідомлення debug не з’явиться.
Щоб побачити всі повідомлення:
LOG_LEVEL=debug node logger.jsЩоб залишити лише попередження та помилки:
LOG_LEVEL=warn node logger.jsПриклад запису:
{"timestamp":"2026-08-19T10:15:30.000Z","level":"info","message":"Застосунок запущено","port":3000}Сам текст логу має бути коротким і зрозумілим. Додаткову інформацію передавайте полями:
logger.info("Замовлення створено", {
orderId: "order-42",
userId: "user-7",
amount: 1250,
currency: "UAH"
});Контекст допомагає відповісти на запитання:
яке замовлення оброблялося;
який користувач виконав дію;
який зовнішній сервіс повернув помилку;
яке значення або операція спричинили проблему.
Назви полів мають бути стабільними. Не використовуйте для однієї й тієї самої інформації назви user, userId та idOfUser у різних місцях.
Для помилки важливо зберегти:
тип помилки;
повідомлення;
стек викликів;
контекст операції.
Погано:
try {
await saveOrder(order);
} catch (error) {
logger.error("Помилка");
}У такому випадку втрачається причина проблеми.
Краще:
try {
await saveOrder(order);
} catch (error) {
logger.error("Не вдалося зберегти замовлення", {
orderId: order.id,
error
});
}Властивість stack особливо корисна для пошуку місця, де виникла помилка.
Не записуйте одну й ту саму помилку багато разів на кожному рівні викликів. Виберіть місце, де є достатньо контексту, і додайте один змістовний запис.
Логи часто доступні багатьом системам і людям, тому вони не повинні містити секрети.
Не записуйте:
паролі;
токени доступу;
приватні ключі;
повні номери банківських карток;
cookie із сесією;
персональні дані без потреби.
Небезпечний приклад:
logger.info("Користувач увійшов", {
email,
password,
accessToken
});Безпечніше залишити лише необхідну інформацію:
logger.info("Користувач увійшов", {
userId,
method: "password"
});Також обережно передавайте у лог цілі об’єкти запитів або відповідей. Вони можуть містити заголовки, cookie та інші конфіденційні значення.
Для production-логів дотримуйтеся таких правил:
Використовуйте структурований формат, наприклад JSON.
Додавайте час у кожен запис.
Вибирайте правильний рівень важливості.
Пишіть коротке змістовне повідомлення.
Передавайте ідентифікатори та інший контекст окремими полями.
Додавайте стек для помилок.
Налаштовуйте рівень через змінну середовища.
Не записуйте секрети та зайві персональні дані.
Виводьте логи у стандартні потоки, якщо середовище запуску збирає їх автоматично.
Перевіряйте, що повідомлення не містять зайвих даних і не створюють надмірний обсяг.
errorlogger.error("Користувач успішно увійшов");Це ускладнює пошук справжніх помилок. Для нормальної події використовуйте info.
logger.info(`Замовлення ${orderId} створено користувачем ${userId}`);Системі складніше фільтрувати такі значення. Передавайте їх окремими полями.
logger.error(error.message);Без стека та контексту важче знайти причину. Передавайте об’єкт помилки й дані операції.
Рівень debug може створити великий потік повідомлень і підвищити ризик витоку даних. У production зазвичай починають із info або warn, а debug вмикають тимчасово за потреби.
Не передавайте в лог об’єкти конфігурації, заголовки запитів або дані автентифікації без перевірки їхнього вмісту.
Для придатного до діагностики production-логування:
використовуйте рівні debug, info, warn та error;
фільтруйте повідомлення за мінімальним рівнем;
записуйте події у структурованому JSON-форматі;
додавайте UTC-час, повідомлення та контекст;
для помилок зберігайте тип, повідомлення і стек;
виводьте логи у stdout та stderr;
ніколи не записуйте паролі, токени та інші секрети;
не покладайтеся на випадкові виклики console.log у різних частинах застосунку.