Галоўная / Артыкулы / Структураваныя логіі ў Node.js: ператварэнне хаосу падчас налагоджання ў прымэтных умовах у яснасць

Структураваныя логіі ў Node.js: ператварэнне хаосу падчас налагоджання ў прымэтных умовах у яснасць

Дазвольце дазнаць, чаму функцыя console.log не працюе ў продакшн-прыемліках Node.js, і як структураваная логгарыфікацыя, рэжымы логаў і ID-ы кореляцыі ператвараюць складныя багі на быстрыя рашэння.

1912 слоў

Адміністрацыя кліента скаржыцца, што процес адзначэння на выкарыстоўванне пакуль не ўдаліся. Панелі монітарынгу паказвае неконтрольваную адмоўку: TypeError: Cannot read properties of undefined (reading 'id').

Вы атрымліваеце файл, указаны ў логах адмоўкі, і проблемная строчка выглядае абсалютна звычайна:

const customerId = session.user.id;

Тады чаму session.user у той момент быў undefined?

Вы працуеце над павторным стварэнням адмоўкі на сваёй машыне. Вы заходзіце ў систему, праходзіце паэтапна праз процес адзначэння на выкарыстоўванне, і ніч не ламаецца. Вы пераглядаеце базу дадзенаў і бачыце, што кожны тэстовы запис мае цэлы об’ект пользователя. Без можлівасці адразу пазнакоміцца з тыми дадзеннямі, якія фактычна прайшлі да той строчкі коду ў рэальных умовах, вам застаёцца толькі здагадвацца.

Такія розследаванні можу зайняць цэлыя дні роботы інжынераў. Але якщо вы налагодзілі логаванне з адповедным падходам і структурой, той самы баг часта вирашваецца за калькі хвілін.

Чаму дыбагаванне Node.js у працоўнай сітцы ёсць складным

Калі вы розвіяеце програму локальна, у вас є інтэрактыўныя з’явальнікі бяга, мгновенная адпаведь з тэрміналу, і зазвычай працоўвае толькі аднаго корыстувальніка — вы самі. Якщо якая-небудзь стаза выклікае проблему, вы проста запускаеце ёё з іншымі данымі, пакуль прычына не стане ясной.

У працоўнай сітцы няма такога комфорту:

  • Асінхронная екзекуцыя: Node.js обрабоўвае многія операцыі адразу за дапамогою свайго цыклу змаганняў. Калі адна запита чакае на вызов базы дадзенаў, іншая вэсьлівае свою сябе логіку. Распісаны выхад з консолі ад адночасных запитаў плутаецца і становіцца неразбірамым.
  • Транзіентныя даны: Точны пакет дадзенаў, які выкарыстоўвае корыстувальнік, застаецца толькі ў памяці на короткі час. Калі неконтрольваная асключэнне збівае процес або сервер адпавядае кодам 500, гэтыя конкретныя даны стаюць незворачнымі.
  • Тонкіяя лінійкія вываду: Лінійкія вываду ў JavaScript паведамляе вам дзе абыў неякшы вывад, але майже ніколі чаму. Яна не паказвае, які корыстнік здзейсніў вызов, якія опцыі былі выбраныя, чым на самай працэ павернуўся вышэйшы сервіс.
  • Без цялесапраўедзенага логавання ваша працюючая аплікацыя паводзіцца як закрытыя чорныя скрынькі. Правільныя практыкі логавання выступаюць як панель керування, якая паказвае, што відбываецца ўнутры яе.

    Пастка консольных логаў

    Большасць разработчыкаў першыя, калі дэбагуюць аплікацыі на Node.js, вяртаюцца да console.log(). Яна ўключана ў сабе з рантаймам, не трэбуе налагоджэння і запісваецца безпосередна ў stdout.

    Але апошнечанне на яй у працэвыкладчым режыме стварае тры яскравыя проблемы.

    1. Яна стварае неструктураваны текст

    Возьмім такія рядок:

    console.log("Payment processed for user: " + userId + " amount: " + amount);
    

    Калі платформа для агрэгацыі логаў, такая як Datadog, CloudWatch чы Графана Loki, прыме гэты рядок, ён зберагаецца як звычны стрынг без індэксавання. Не існуе эфектыўнага спосабу для запытання «всі платежы вышэй 1000», адтуды платформе патрэбна дорогая парсація за дапамою регулярных выразаў, толькі каб выявіць значэнні.

    2. Яе не хапае контексту выканання

    Уявіце логі з адмовамі, які проста выглядае так:

    Database timeout on query
    

    Тут няма нічога, што паведамляла б вас пра тое, якае маршрута запусціла запыт, які корыстнік быў учаснікам чы калі прыйшаў запрос.

    3. Яна можа паслабіць працэсную спроможнасць цыклу змагання

    Пад заданыяя абстацэі — у першую чаргу пад запіс у файлы або пад працэю з выходным трафікам у дзеярых сістэмах — console.log() выкананы сінхронна. Калі навантажэння высока, частыя вызовы console.log() спрычынаюць блаканне ціклу запуску Node.js, што падвышае затрымкі ў всій аплікацыі.

    Тры основныя элементы логавання высокага стандарта

    Ёнколі логі маюць быць практычна корыстнымі для дэбагавання, неабходныя тры основныя практыкі: структураваны выход, аднаковыя рэвалюціі значэння серьознасці і ID для кореляцыі.

    1. Структуравана рэўізія логаў (JSON)

    У замест на запіс простых стрэнгаў, ваша аплікацыя должна выдаваць кожную ентрыту лога як структураваны об’ект JSON. Ключы JSON можна автаматычна індексаваць платформамі для логавання.

    {
      "timestamp": "2026-03-24T14:32:01.412Z",
      "level": "error",
      "message": "Payment processing failed",
      "userId": "usr_9912",
      "orderId": "ord_5521",
      "attempt": 3,
      "error": "Gateway timeout"
    }
    

    За дапамою такога формата не трэба здагадваліцца, калі ўтрапляе проблема — можна проста знайсці orderId: "ord_5521" і адразу побачыць усі лог-запісы, які стосуюцца гэтага замовлення.

    2. Значныя рэвалюціі лог-запісаў

    Не кожны запіс заслуговае на аднаковы рэвалюцій уваги. Іспользаванне аднаковых рэвалюцій лог-запісаў дапамагае кантролюваць выхідныя даны ў працэсе:

    • DEBUG: Дзейневыя дапаможныя даны, прызначаныя для разработкі, такія як повныя вывалы пакета дадзеных або внутршнія рашэнні ў развіце. Їх зазвычай не прадаюць у працэсе.
    • INFO: Звычайныя оператывныя паведамленні, якія паўтараюць, што прыстаночка работае нормальна, напрыклад Server started on port 3000 або User account created.
  • ПАВЕДЗІЛЯННЕ: Ситуацыі, з якіх система сама вярнулася да нормальнага стану, але які можу сведчыць пра падагачанне проблемы — напрыклад, немагчымасць знайсці даныя ў кэшы, што вымушвае абратыся да базы дадзеных, або спроба кліента з’явіцца да застарэлага канцэнтра.
  • ПАДЫЕКТ: Неудачы, якія не дазволілі завершыць операцыю і якія трэба расследаваць людзьму, такія як незавершаная оплата або неконтрольвана адхіленне запросу.
  • 3. ID кореляцыі (Адстэйканне запросаў)

    Паколькі Node.js выкананая асінхронна, вам патрабуецца спосаб для стежэння за адным запросам па ўсіх етапах — через кантролеры, службы, запыты да базы дадзеных і зовнішнія запросы API.

    ID карабатарства — які частая называюць requestId або traceId — ўнікальны ідэнтыфікатор, які ствараецца, калі спачатку прыходзіць HTTP-запрос. Шырокі ідэнтыфікатор прыўязуецца да кожной лініі журналу, якая ствараецца пад час обробкі таго запросу.

    Практычная рэалізацыя: структураваная логгірацыя ў Express

    Тепер давайце практычна застосуем гэта, стварыўшы сетап логгірацыі, гатовы да выкарыстоўвання, за дапамогай Pino — дужа шыранаг JSON-логгера для Node.js, які работае разам з вбудованым API AsyncLocalStorage для керування ID карабатарства.

    Крок 1: Наладжыце хранілнік контэксту і логгер

    Node.js выклікаеся з AsyncLocalStorage, які ўваходзіць у склад основнага модуля node:async_hooks. Можна супрацаваць з яным як з аналогам памяці, прызначанай для кожнага потока, яка існуе ў багатопоточных мовах: яны зберагаюць частку контексту праз асінхронныя вызовы, не вымагаючы ручнага перадавання параметраў потока ў кожны підпис функцыі.

    // logger.js
    import pino from 'pino';
    import { AsyncLocalStorage } from 'node:async_hooks';
    
    export const asyncLocalStorage = new AsyncLocalStorage();const baseLogger = pino({
      level: process.env.LOG_LEVEL || 'info',
      timestamp: pino.stdTimeFunctions.isoTime,
    });// A proxy that injects the requestId into every log entry automatically
    export const logger = new Proxy(baseLogger, {
      get(target, property) {
        const store = asyncLocalStorage.getStore();
        const childLogger = store?.requestId
          ? target.child({ requestId: store.requestId })
          : target;    return childLogger[property];
      }
    });
    

    Крок 2: Адкалікаванне медія-сервіса Express

    Далей напішыце медія-сервіс, які прызначае ідэнтыфікатор кожнаму запрошчанню та выконвае рэст жыццёвага циклу запрошчання ў контексте AsyncLocalStorage.

    // middleware.js
    import { randomUUID } from 'node:crypto';
    import { asyncLocalStorage, logger } from './logger.js';
    
    export function requestContextMiddleware(req, res, next) {
      // Use existing header if forwarded by a load balancer, or create a new UUID
      const requestId = req.headers['x-request-id'] || randomUUID();
    
      res.setHeader('x-request-id', requestId);  asyncLocalStorage.run({ requestId }, () => {
        const startTime = Date.now();    logger.info({
          method: req.method,
          url: req.url,
          ip: req.ip
        }, 'Incoming request');    res.on('finish', () => {
          const durationMs = Date.now() - startTime;
    
          const logData = {
            statusCode: res.statusCode,
            durationMs
          };      if (res.statusCode >= 500) {
            logger.error(logData, 'Request completed with server error');
          } else {
            logger.info(logData, 'Request completed');
          }
        });    next();
      });
    }
    

    Крок 3: Іспытанне логгера ў бізнес-логіцы

    У вашых кантролерах або функціях-сэрвісах проста імпортуйце спяльны логгер. Не трэба ручна перадаваць req або requestId як параметры функцыі.

    // orderService.js
    import { logger } from './logger.js';
    
    export async function processOrder(user, cart) {
      logger.info({ cartItemsCount: cart.items.length }, 'Validating cart inventory');  if (!user || !user.id) {
        logger.warn({ userState: user }, 'Attempted checkout without a valid user ID');
        throw new Error('User identification is missing');
      }  // Continue checkout logic...
    }
    

    Якщо ў функцыі processOrder выйдзе якаясь проблема, запис у журнале будзе выглядаць так:

    {
      "time": "2026-03-24T14:40:12.102Z",
      "level": 40,
      "requestId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
      "userState": null,
      "msg": "Attempted checkout without a valid user ID"
    }
    

    Заўважыце, як requestId апоштова праказуецца ў гэтым записе. Маючы гэтую значэнне, вы можете шукати ў своіх журналах і восстанавіць цэлыя ланкі падзей, зв’язаных з тым запитам, ад пачатку да канца.

    Структура 5-хвіліннага рашэння

    Павернімся да первачальной проблемы: session.user стае undefined пад час адбыцья пракупкі.

    Без структураванага контэксту (сценарый кошмару)

    • Трэйс каштоўкаў вядзе да const customerId = session.user.id.
    • Вы пераглядаеце базу дадзенаў і бачыце, што усі тэстовыя акаунты маюць цалкам правільныя сесіі.
  • Вы выдзейліваете 45 хвілін на перапрацоўку разных тэарыяў: аператыўнае выкарыстання абліков, заканчэнныя сесіі, спецыфічная працэздатнасьць для мобайльных прыстроёў.
  • Вы дадаеце тымчасовыя вызывы console.log() у працоўны сервіс, спадзяючыся, што баг з’явіцца знову, ўбачыць яго ў рэальным часе.
  • Проблема застаецца нерашанай колькі дзён.
  • З структураваным контэкстам (Сценарый на 5 хвілін)

    • Вы беразеце requestId з адчытнай картоцы бага кліента або з паведамлення пра адмышчанне 500.
    • Вы шукаеце ў сваём агрегаторы логаў requestId: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d".
    • Вы выкалічваеце тры лініі журналу, якія належаць да таго ID, і чытаете ўсія ў порядку. Першая паказвае, што запит прыбыў на маршрут праймання. Другая адкрывае, што сесія належыць анонімнаму, неавтентыфікованаму гасцю, а не кліянту, які ўвайшаў. Трэцяя падтверджвае, што логіка праймання адхіліла спробу самэўчынай з тых паўгод, што не было чыстаго ID пользователя.
    • Чытанне гэтых трох прызнакоў разам адразу ж робіць основную прычыну явной: гасцовыя пользователяў прабываюць прымусова на маршрут з автентыфікацыяй, адхіляючы запланаваную перэкідачку для гасцоў.
    • Вы вярнуле стан автентыфікацыі за апошнія пяць хвілін.

    Рашэнне прыйшло быстра не таму, што сам код стаў простым, а таму, што запускаемая програма вялікай часткай вже задокументавала свой сабе прынцып выконання.

    Тыповыя памылкі ў журналаванні, якіх трэба утрымляцца

    Нават апэктываныя команды можаю паслабіць якосьць своіх лог-файлаў за дапамой калькаваных спосабаў:

    • Запіс чутлівых дадзэнняў (PII): Ніколы не запішвайце пароляў, токенав аутанацыі, цэлых номераў картак чы іншых персональных дадзэнняў у лог-файлы. Апыявайцеся на вбудованых функціях маскавання — напрыклад, на опцыі redact у Pino — каб автаматычна адсёлкаваць такія полья, як password чы authorization, прычым перад тым, як данні будуць серыялізаваны.
    • Запіс усьго ў працоўным сераўеры: Заполненне жывога системы выходам на рэжыме DEBUG пад рэальным трафікам марнуе цыклы CPU і падвайнае суму за зберагачча лог-файлаў. Для стандартных працоўных сэрвісаў выберайце рэжым INFO чы WARN, і толькі временна падвайшыце рэжым деталізаціі, калі гэта справды неабходна.
  • Адзірванне дакладнаў пра аблыканнях: Штоносіцеся да ситуацый, калі ловіцца аблыканне і фіксуецца толькі звычайны текставы паведамленне, адтуды ўтрачаецца сам аб’ект аблыкання і яго стэк-трейс. У замен завжды перадавайце цэлы аб’ект аблыкання пад час вызову функціі логавання, напрыклад logger.error({ err }, "Не ўдалося зберагчы данні"), так калі-небудзь нічога не будзе загублена.
  • Уключэнне можлівасцяў адзірвання ў ваш код

    Застосоўка правільных прычын логавання перацварае ваш падход да систем, якія працуюць у рэальным часе. Уместо таго, каб спакульваць, што прыкладнэе будзе правільна працаваць, вы начынайце ставіцца да яго як да жывога процесу, які зобав’язаны дастаць вам адказ, калі ўтвараюцца проблемы.

    Ёжыдзець да гэтага не трэбуе вялікіх пачатковых зусиль: сформатавайце логі як JSON, пазначайце кожны прыходзячы выкалеку ідэнтыфікатаром супараганацыі, а таксама фіксуйце неабяжныя зменныкі кожны раз, калі вы пазначаеце адзіну з бягунковых ситуацый. Калі наступны раз у працоўнай среды вы паспяшыце на нечаканую бягунковую сітуацыю, гэтыя падготавкі дапаможу вам прыдзеўсці час на рэальнае вылечэнне проблемы, а не на спадзявання, што ж сталося.

    Спаднёе чытанне

  • Адказ пра аслівым бутэльнеку ў медленным канцэнтры Node.js — Узнаём систематычны метод для выявлення затрымкаў у бэкендзе па шляху запытку — ад коду Node.js да запыткаў да базы дадзеных — за дапамой метраграфіі і EXPLAIN ANALYZE.
  • Шаблоны структураванага логавання для эфектывнага дэбагавання API-ў Node.js — Узнаём, як заменіць розрозненыя вызывы console.log на структураванае логаванне, ID запыткаў, рэвэлі логаў і метрыкі часу, каб шырэйша дэбагаваць API-ў Node.js.
  • Стварэнне API для Node.js з можлівасцю аблявання: абляванне, метрыкі і трэйсінг — Дазвольце вам дазнацца, як структуравана абляванне, трэйсінг запыткаў, метрыкі і адлагоджаная сістэма паведамленняў спяваюць разам, чынячы адлагоджванне API для Node.js у працэсе вырабніцтва значна менш напружаным.