Как использовать Winston для ведения журнала приложений Node.js на Ubuntu 20.04

Введение

Эффективное решение для ведения журнала является ключевым элементом успеха любого приложения. Winston – это универсальная библиотека ведения журнала и популярное решение для ведения журнала, доступное для приложений на Node.js. Среди функций Winston можно выделить поддержку нескольких вариантов хранения, уровни журналирования, запросы журнала и встроенный профилировщик.

В этом учебнике вы будете использовать Winston для ведения журнала приложения Node/Express, которое вы создадите в процессе работы. Вы также увидите, как объединить Winston с Morgan, еще одним популярным промежуточным регистратором HTTP-запросов для Node.js, чтобы объединить журналы данных HTTP-запросов с другой информацией. После завершения этого учебника на вашем сервере Ubuntu будет запущено небольшое приложение Node/Express, и Winston будет использоваться для ведения журнала ошибок и сообщений в файле и на консоли.

Предварительные требования

Для выполнения этого учебника вам понадобится:

Шаг 1 — Создание простого веб-приложения на Node/Express

Winston часто используется для регистрации событий в веб-приложениях, созданных с использованием Node.js. На этом этапе вы создадите простое веб-приложение на Node.js с использованием фреймворка Express. Вы будете использовать express-generator, инструмент командной строки, чтобы быстро запустить веб-приложение Node/Express.

Поскольку вы установили Менеджер пакетов Node в ходе предварительных требований, вы можете использовать команду npm для установки express-generator:

  1. sudo npm install express-generator -g

Флаг -g устанавливает пакет глобально, что означает, что его можно использовать как инструмент командной строки вне существующего проекта/модуля Node.

После установки express-generator можно создать свое приложение, используя команду express, за которой следует имя каталога, который вы хотите использовать для проекта:

  1. express myApp

В этом руководстве проект будет называться myApp.

Примечание: Также возможно запустить инструмент express-generator напрямую, не устанавливая его глобально как системную команду сначала. Для этого выполните эту команду:

  1. npx express-generator myApp

Команда npx – это утилита запуска команд, поставляемая с менеджером пакетов Node, которая упрощает запуск инструментов командной строки из реестра npm.

При первом запуске он попросит вас согласиться на загрузку пакета:

Output
Need to install the following packages:
  express-generator
Ok to proceed? (y)

Ответьте y и нажмите ENTER. Теперь вы можете использовать npx express-generator вместо express.

Затем установите Nodemon, который автоматически перезагружает приложение при внесении изменений. Приложение Node.js должно быть перезапущено каждый раз, когда в исходный код вносятся изменения, чтобы эти изменения вступили в силу, поэтому Nodemon будет автоматически следить за изменениями и перезапускать приложение. Поскольку вы хотите использовать nodemon как инструмент командной строки, установите его с флагом -g:

  1. sudo npm install nodemon -g

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

  1. cd myApp
  2. npm install

По умолчанию приложения, созданные с помощью express-generator, работают на порту 3000, поэтому вам необходимо убедиться, что брандмауэр не блокирует этот порт.

Чтобы открыть порт 3000, выполните следующую команду:

  1. sudo ufw allow 3000

Теперь у вас есть все необходимое для запуска вашего веб-приложения. Для этого выполните следующую команду:

  1. nodemon bin/www

Эта команда запускает приложение на порту 3000. Вы можете проверить его работу, перейдя в браузере по адресу http://your_server_ip:3000. Вы должны увидеть что-то подобное:

На данном этапе вы можете открыть вторую SSH-сессию на ваш сервер для продолжения этого руководства, оставив запущенное вами веб-приложение в исходной сессии. Начиная с этого момента, первая SSH-сессия, на которой запущено приложение, будет называться Сессия A. Любые команды в Сессии A будут отображаться на темно-синем фоне, например, так:

  1. nodemon bin/www

Вы будете использовать новую SSH-сессию для выполнения команд и редактирования файлов. Эта сессия будет называться Сессия B. Любые команды в Сессии B будут отображаться на светло-голубом фоне, например, так:

  1. cd ~/myApp

За исключением случаев, когда указано иное, вы будете выполнять все оставшиеся команды в Сессии B.

На этом этапе вы создали базовое приложение. Далее вы его настроите.

Шаг 2 — Настройка переменных журналирования

Хотя стандартное приложение, созданное с помощью express-generator, является хорошим началом, вам нужно настроить приложение так, чтобы оно вызывало правильный регистратор при необходимости.

express-generator включает промежуточное ПО регистрации HTTP-запросов Morgan, которое вы будете использовать для регистрации данных о всех HTTP-запросах. Поскольку Morgan поддерживает потоковый вывод, он прекрасно сочетается с поддержкой потоков, встроенной в Winston, что позволяет вам объединить журналы данных HTTP-запросов с чем угодно еще, что вы выберете для регистрации с помощью Winston.

Шаблон express-generator использует переменную logger при ссылке на пакет morgan. Поскольку вы будете использовать как morgan, так и winston, которые являются пакетами регистрации, может быть запутано называть любой из них logger. Чтобы указать, какую переменную вы хотите использовать, вы можете изменить объявления переменных, отредактировав файл app.js.

Чтобы открыть файл app.js для редактирования, используйте nano или ваш любимый текстовый редактор:

  1. nano ~/myApp/app.js

Найдите следующую строку близко к верху файла:

~/myApp/app.js
...
var logger = require('morgan');
...

Измените имя переменной с logger на morgan:

~/myApp/app.js
...
var morgan = require('morgan');
...

Это обновление указывает, что объявленная переменная morgan будет вызывать метод require(), связанный с регистратором запросов Morgan.

Вам нужно найти, где еще в файле используется переменная logger и изменить ее на morgan. Вам также потребуется изменить формат журнала, используемый пакетом morgan, на combined, который является стандартным форматом журнала Apache и будет включать полезную информацию в журналах, такую как удаленный IP-адрес и заголовок запроса HTTP пользовательского агента.

Для этого найдите следующую строку:

~/myApp/app.js
...
app.use(logger('dev'));
...

Обновите ее следующим образом:

~/myApp/app.js
...
app.use(morgan('combined'));
...

Эти изменения помогут вам понять, на какой журнальный пакет ссылается в данный момент после интеграции конфигурации Winston.

По завершении сохраните и закройте файл.

Теперь, когда ваше приложение настроено, вы можете начать работу с Winston.

Шаг 3 — Установка и настройка Winston

На этом шаге вы установите и настроите Winston. Вы также изучите доступные параметры конфигурации в составе пакета winston и создадите регистратор для записи информации в файл и консоль.

Установите winston с помощью следующей команды:

  1. cd ~/myApp
  2. npm install winston

Полезно хранить любые файлы конфигурации поддержки или утилиты для ваших приложений в специальном каталоге. Создайте папку config, которая будет содержать конфигурацию winston:

  1. mkdir ~/myApp/config

Затем создайте папку, которая будет содержать ваши файлы журналов:

  1. mkdir ~/myApp/logs

Наконец, установите app-root-path:

  1. npm install app-root-path --save

Пакет app-root-path полезен при указании путей в Node.js. Хотя этот пакет не имеет прямого отношения к Winston, он полезен при определении путей к файлам в Node.js. Вы будете использовать его для указания расположения файлов журналов Winston от корня проекта и для избегания некрасивого синтаксиса относительных путей.

Теперь, когда настройка обработки журналирования готова, вы можете определить свои параметры. Создайте и откройте для редактирования ~/myApp/config/winston.js:

  1. nano ~/myApp/config/winston.js

Файл winston.js будет содержать вашу конфигурацию winston.

Затем добавьте следующий код для подключения пакетов app-root-path и winston:

~/myApp/config/winston.js
const appRoot = require('app-root-path');
const winston = require('winston');

После того как эти переменные настроены, вы можете определить настройки конфигурации для ваших транспортов. Транспорты – это концепция, введенная Winston, которая относится к механизмам хранения/вывода, используемым для журналов. Winston поставляется с четырьмя встроенными основными транспортами: Консоль, Файл, HTTP и Поток.

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

Вот краткое описание настроек, которые вы будете использовать для каждого транспорта:

  • уровень: уровень сообщений для ведения журнала.
  • имя_файла: файл, в который будут записываться данные журнала.
  • handleExceptions: перехват и запись необработанных исключений.
  • maxsize: максимальный размер файла журнала в байтах перед созданием нового файла.
  • maxFiles: ограничение количества файлов, создаваемых при превышении размера файла журнала.
  • формат: формат вывода журнальных данных.

Уровни журналирования указывают приоритет сообщения и обозначаются целым числом. Winston использует уровни журналирования npm, которые имеют приоритет от 0 до 6 (от самого высокого до самого низкого):

  • 0: ошибка
  • 1: предупреждение
  • 2: информация
  • 3: http
  • 4: подробно
  • 5: отладка
  • 6: глупость

При указании уровня журналирования для конкретного транспорта будут записаны все сообщения на этом уровне или выше. Например, при установке уровня информация, будут записаны все сообщения на уровне ошибка, предупреждение или информация.

Уровни журналирования указываются при вызове регистратора, что означает, что вы можете выполнить следующую команду для записи ошибки: logger.error('тестовое сообщение об ошибке').

Все еще в файле конфигурации добавьте следующий код для определения параметров конфигурации для транспортов файл и консоль в конфигурации winston:

~/myApp/config/winston.js
...
// определите настройки для каждого транспорта (файл, консоль)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

Затем добавьте следующий код для создания нового логгера winston с транспортами файлов и консоли, используя свойства, определенные в переменной options:

~/myApp/config/winston.js
...
// создание нового логгера Winston с заданными выше настройками
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // не завершать при обработке исключений
});

По умолчанию morgan выводит только на консоль, поэтому вы определите функцию потока, которая сможет получать вывод, сгенерированный morgan, в лог-файлы winston. Вы будете использовать уровень info для получения вывода обоими транспортами (файл и консоль). Добавьте следующий код в файл конфигурации:

~/myApp/config/winston.js
...
// создание объекта потока с функцией 'write', которая будет использоваться `morgan`
logger.stream = {
  write: function(message, encoding) {
    // использование уровня журнала 'info', чтобы вывод выбирался обоими
    // транспортами (файл и консоль)
    logger.info(message);
  },
};

Наконец, добавьте следующий код для экспорта логгера, чтобы его можно было использовать в других частях приложения:

~/myApp/config/winston.js
...
module.exports = logger;

Завершенный файл конфигурации winston теперь будет выглядеть так:

~/myApp/config/winston.js
const appRoot = require("app-root-path");
const winston = require("winston");

// Определите пользовательские настройки для каждого транспорта (файл, консоль)
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5 МБ
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

// Создайте новый логгер Winston с указанными выше настройками
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // Не завершайте выполнение при обработке исключений
});

// Создайте объект потока с функцией 'write', которая будет использоваться `morgan`
logger.stream = {
  write: function (message, encoding) {
    // Используйте уровень логирования 'info', чтобы вывод забирался обоими
    // транспортами (файл и консоль)
    logger.info(message);
  },
};

module.exports = logger;

Сохраните и закройте файл.

Теперь у вас настроен логгер, но ваше приложение все еще не знает о нем или как им пользоваться, поэтому вам нужно интегрировать логгер с приложением.

Шаг 4 — Интеграция Winston с приложением

Чтобы ваш логгер работал с приложением, вам нужно сделать express осведомленным об этом. Вы видели в Шаге 2, что ваша конфигурация express находится в app.js, поэтому вы можете импортировать свой логгер в этот файл.

Откройте файл для редактирования:

  1. nano ~/myApp/app.js

Добавьте объявление переменной winston возле верха файла с другими операторами require:

~/myApp/app.js
...
var winston = require('./config/winston');
...

Первое место, где вы используете winston, это с morgan. По-прежнему в app.js найдите следующую строку:

~/myApp/app.js
...
app.use(morgan('combined'));
...

Обновите её, чтобы добавить опцию stream:

~/myApp/app.js
...
app.use(morgan('combined', { stream: winston.stream }));
...

Здесь вы устанавливаете опцию stream на интерфейс потока, который вы создали в рамках конфигурации winston.

Сохраните и закройте файл.

На этом этапе вы настроили ваше приложение Express для работы с Winston. Далее вы рассмотрите данные журнала.

Шаг 5 — Доступ к данным журнала и запись пользовательских сообщений журнала

Теперь, когда приложение настроено, вы готовы увидеть некоторые данные журнала. На этом этапе вы рассмотрите записи журнала и обновите свои настройки с примером пользовательского сообщения журнала.

Если вы перезагрузите страницу в веб-браузере, вы должны увидеть что-то подобное следующему выводу в консоли сеанса SSH A:

Output
[nodemon] restarting due to changes... [nodemon] starting `node bin/www` info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET / HTTP/1.1" 200 170 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Здесь две записи в журнале: первая для запроса к HTML-странице; вторая для связанного стилевого файла. Поскольку каждый транспорт настроен на обработку данных журнала уровня info, вы также должны увидеть аналогичную информацию в файловом транспорте, расположенном в ~/myApp/logs/app.log.

Чтобы просмотреть содержимое файла журнала, выполните следующую команду:

  1. tail ~/myApp/logs/app.log

tail выведет последние части файла в вашем терминале.

Вы должны увидеть что-то подобное:

{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.573Z"}
{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://localhost:3000/\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.588Z"}

Вывод в файл transport будет записан в виде объекта JSON, так как вы использовали winston.format.json() в опции format для конфигурации транспорта файла. Вы можете узнать больше о JSON в Введение в JSON.

На данный момент ваш журналировщик записывает только HTTP-запросы и связанные с ними данные. Эта информация важна для ведения журналов.

В будущем вам может понадобиться записывать пользовательские сообщения журнала, например, для записи ошибок или профилирования производительности запросов к базе данных. В качестве примера вы вызовете журналировщик из маршрута обработчика ошибок. По умолчанию пакет express-generator уже включает обработчики ошибок 404 и 500, поэтому вы будете работать с ними.

Откройте файл ~/myApp/app.js:

  1. nano ~/myApp/app.js

Найдите блок кода в конце файла, который выглядит так:

~/myApp/app.js
...
// обработчик ошибок
app.use(function(err, req, res, next) {
  // устанавливаем локальные переменные, предоставляем только ошибку в режиме разработки
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // рендерим страницу ошибки
  res.status(err.status || 500);
  res.render('error');
});
...

Этот раздел – это окончательный маршрут обработки ошибок, который в конечном итоге отправит ответ с ошибкой обратно клиенту. Поскольку все серверные ошибки будут проходить через этот маршрут, это хорошее место для включения логгера winston.

Поскольку вы теперь имеете дело с ошибками, вы хотите использовать уровень журнала error. Оба транспорта настроены на регистрацию сообщений уровня error, поэтому вы должны видеть вывод в консоли и файловых журналах.

Вы можете включить в журнал любую информацию, включая данные, такие как:

  • err.status: Код статуса ошибки HTTP. Если он не указан, используйте значение по умолчанию 500.
  • err.message: Подробности об ошибке.
  • req.originalUrl: Запрошенный URL.
  • req.path: Часть пути в запрошенном URL.
  • req.method: Метод HTTP-запроса (GET, POST, PUT и т. д.).
  • req.ip: IP-адрес удаленного хоста.

Обновите обработчик ошибок маршрута для включения регистрации с помощью winston:

~/myApp/app.js
...
// обработчик ошибок
app.use(function(err, req, res, next) {
  // установить локальные переменные, предоставляя только ошибку в режиме разработки
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // включить регистрацию winston
  winston.error(
    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
  );

  // отображать страницу ошибки
  res.status(err.status || 500);
  res.render('error');
});
...

Сохраните и закройте файл.

Для проверки этого процесса попробуйте получить доступ к несуществующей странице в вашем проекте. Запрос к несуществующей странице вызовет ошибку 404. В своем веб-браузере попробуйте загрузить следующий URL: http://ваш_ip_сервера:3000/foo. Благодаря стартовому шаблону, созданному express-generator, приложение настроено на обработку такой ошибки.

Ваш браузер отобразит сообщение об ошибке вот так:

Когда вы посмотрите на консоль в сеансе SSH A, там должна быть запись журнала об ошибке. Благодаря примененному форматированию colorize, его будет легко заметить:

Output
[nodemon] starting `node bin/www` error: 404 - Not Found - /foo - GET - ::1 info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /foo HTTP/1.1" 404 982 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/foo" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"

Что касается регистратора файлов, повторное выполнение команды tail должно показать вам новые записи журнала:

  1. tail ~/myApp/logs/app.log

Вы увидите сообщение вроде следующего:

{"level":"error","message":"404 - Not Found - /foo - GET - ::1","timestamp":"2022-04-25T18:08:33.508Z"}

Сообщение об ошибке включает в себя все данные, которые вы явно указали winston для регистрации в качестве части обработчика ошибок. Эта информация будет включать статус ошибки (404 – Не найдено), запрошенный URL (localhost/foo), метод запроса (GET), IP-адрес, сделавший запрос, и временную метку, когда запрос был сделан.

Заключение

В этом руководстве вы создали простое веб-приложение Node.js и интегрировали решение по ведению журнала Winston, которое будет эффективным инструментом для получения представления о производительности приложения.

Вы можете сделать гораздо больше для создания надежных решений по ведению журналов для ваших приложений, особенно когда ваши потребности становятся более сложными. Чтобы узнать больше о транспортах Winston, см. Документация по транспортам Winston. Чтобы создать собственные транспорты, см. Добавление собственных транспортов. Чтобы создать HTTP-конечную точку для использования с транспортом HTTP, см. winstond. Чтобы использовать Winston в качестве инструмента профилирования, см. Профилирование.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications-on-ubuntu-20-04