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

Вступление

Эффективное решение для ведения журнала имеет решающее значение для успеха любого приложения. В этом руководстве мы сосредоточимся на пакете журналов под названиемWinston, чрезвычайно универсальной библиотеке журналов и самом популярном решении для журналов, доступном для приложенийNode.js, на основе статистики загрузок NPM. Функции Winston включают поддержку нескольких вариантов хранения и уровней журналов, запросов журналов и даже встроенного профилировщика. Из этого туториала вы узнаете, как использовать Winston для регистрации приложения Node / http: //expressjs.com/ [Express], которое мы создадим в рамках этого процесса. Мы также рассмотрим, как объединить Winston с другим популярным средством ведения журналов промежуточного программного обеспечения HTTP-запросов для Node.js, называемымMorgan, для объединения журналов данных HTTP-запросов с другой информацией.

После завершения этого урока у вас будет сервер Ubuntu, на котором будет запущено небольшое приложение Node / Express. У вас также будет реализован Winston для записи ошибок и сообщений в файл и консоль.

Предпосылки

Прежде чем начать это руководство, вам потребуется следующее:

Имея эти предварительные условия, мы можем построить наше приложение и установить Winston.

[[step-1 -—- Creating-a-basic-node-express-app]] == Шаг 1. Создание базового узла / экспресс-приложения

Обычно Winston регистрирует события из веб-приложений, созданных с помощью Node.js. Чтобы полностью продемонстрировать, как включить Winston, мы создадим простое веб-приложение Node.js с использованием среды Express. Чтобы помочь нам запустить базовое веб-приложение, мы будем использоватьexpress-generator, инструмент командной строки для быстрого запуска веб-приложения Node / Express. Поскольку мы установилиNode Package Manager как часть наших предварительных требований, мы сможем использовать командуnpm для установкиexpress-generator. Мы также будем использовать флаг-g, который устанавливает пакет глобально, чтобы его можно было использовать в качестве инструмента командной строки вне существующего проекта / модуля Node. Установите пакет с помощью следующей команды:

sudo npm install express-generator -g

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

express myApp

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

sudo npm install nodemon -g

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

cd myApp
npm install

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

sudo ufw allow 3000

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

nodemon bin/www

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

Default express-generator homepage

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

Шаг 2 - Настройка приложения Node.js

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

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

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

nano ~/myApp/app.js

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

~/myApp/app.js

...
var logger = require('morgan');
...

Измените это на следующее:

~/myApp/app.js

...
var morgan = require('morgan');
...

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

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

~/myApp/app.js

...
app.use(logger('dev'));
...

Измените это на следующее:

~/myApp/app.js

...
app.use(morgan('combined'));
...

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

Выйдите и сохраните файл, набравCTRL-X, затемY, а затемENTER.

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

[[step-3 -—- install-and-configuring-winston]] == Шаг 3 - Установка и настройка Winston

Теперь мы готовы установить и настроить Winston. На этом этапе мы исследуем некоторые параметры конфигурации, доступные как часть пакетаwinston, и создадим регистратор, который будет записывать информацию в файл и на консоль.

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

cd ~/myApp
npm install winston

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

mkdir ~/myApp/config

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

touch ~/myApp/config/winston.js

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

mkdir ~/myApp/logs

Наконец, давайте установимapp-root-path, пакет, который полезен при указании путей в Node.js. Этот пакет не имеет прямого отношения к Winston, но очень помогает при указании путей к файлам в коде Node.js. Мы будем использовать его, чтобы указать расположение файлов журнала Winston из корня проекта и избежать синтаксиса уродливого относительного пути:

npm install app-root-path --save

Все, что нам нужно для настройки того, как мы хотим обрабатывать нашу регистрацию, уже готово, поэтому мы можем перейти к определению наших параметров конфигурации. Начните с открытия~/myApp/config/winston.js для редактирования:

 nano ~/myApp/config/winston.js

Затем потребуйте пакетыapp-root-path иwinston:

~/myApp/config/winston.js

var appRoot = require('app-root-path');
var winston = require('winston');

Имея эти переменные на месте, мы можем определить параметры конфигурации для нашегоtransports. Транспорт - это концепция, представленная Уинстоном, которая относится к механизмам хранения / вывода, используемым для журналов. Winston имеет три основных транспорта -console,file иHTTP. В этом руководстве мы сосредоточимся на транспортировке консоли и файлов: транспорт консоли будет записывать информацию в консоль, а транспорт файлов будет записывать информацию в указанный файл. Каждое определение транспорта может содержать свои собственные параметры конфигурации, такие как размер файла, уровни журнала и формат журнала. Вот краткий обзор настроек, которые мы будем использовать для каждого транспорта:

  • level - Уровень сообщений для регистрации.

  • filename - файл, который будет использоваться для записи данных журнала.

  • handleExceptions - ловить и регистрировать необработанные исключения.

  • json - записывает данные журнала в формате JSON.

  • maxsize - Максимальный размер файла журнала в байтах до создания нового файла.

  • maxFiles - ограничивает количество файлов, создаваемых при превышении размера файла журнала.

  • colorize - Раскрасить вывод. Это может быть полезно при просмотре журналов консоли.

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

  • 0: ошибка

  • 1: предупреждать

  • 2: информация

  • 3: подробный

  • 4: отладка

  • 5: глупо

При указании уровня ведения журнала для определенного транспорта будет регистрироваться что-либо на этом уровне или выше. Например, при указании уровняinfo в журнал будет занесено все, что находится на уровнеerror,warn илиinfo. Уровни журнала указываются при вызове регистратора, что означает, что мы можем сделать следующее, чтобы записать ошибку:logger.error('test error message').

Мы можем определить параметры конфигурации для транспортовfile иconsole в конфигурацииwinston следующим образом:

~/myApp/config/winston.js

...
var options = {
  file: {
    level: 'info',
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    json: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    colorize: false,
  },
  console: {
    level: 'debug',
    handleExceptions: true,
    json: false,
    colorize: true,
  },
};

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

~/myApp/config/winston.js

...
var logger = new winston.Logger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console)
  ],
  exitOnError: false, // do not exit on handled exceptions
});

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

~/myApp/config/winston.js

...
logger.stream = {
  write: function(message, encoding) {
    logger.info(message);
  },
};

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

~/myApp/config/winston.js

...
module.exports = logger;

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

~/myApp/config/winston.js

var appRoot = require('app-root-path');
var winston = require('winston');

// define the custom settings for each transport (file, console)
var options = {
  file: {
    level: 'info',
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    json: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    colorize: false,
  },
  console: {
    level: 'debug',
    handleExceptions: true,
    json: false,
    colorize: true,
  },
};

// instantiate a new Winston Logger with the settings defined above
var logger = new winston.Logger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console)
  ],
  exitOnError: false, // do not exit on handled exceptions
});

// create a stream object with a 'write' function that will be used by `morgan`
logger.stream = {
  write: function(message, encoding) {
    // use the 'info' log level so the output will be picked up by both transports (file and console)
    logger.info(message);
  },
};

module.exports = logger;

Выйдите и сохраните файл.

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

[[step-4 -—- integration-winston-with-our-application]] == Шаг 4. Интеграция Winston с нашим приложением

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

nano ~/myApp/app.js

Импортируйтеwinston в начало файла с другими операторами require:

~/myApp/app.js

...
var winston = require('./config/winston');
...

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

~/myApp/app.js

...
app.use(morgan('combined'));
...

Измените это на это:

~/myApp/app.js

...
app.use(morgan('combined', { stream: winston.stream }));
...

Выйдите и сохраните файл.

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

Output[nodemon] restarting due to changes...
[nodemon] starting `node bin/www`
info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:29:36 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:29:37 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://167.99.4.120:3000/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

Здесь есть две записи журнала - первая для запроса к HTML-странице, вторая для сопровождающей таблицы стилей. Поскольку каждый транспорт настроен для обработки данных журнала уровняinfo, мы также должны увидеть аналогичную информацию в транспортном файле, расположенном в~/myApp/logs/app.log. Однако вывод в файловом транспорте должен быть записан как объект JSON, поскольку мы указалиjson: true в конфигурации файлового транспорта. Вы можете узнать больше о JSON в нашихintroduction to JSON tutorial. Чтобы просмотреть содержимое файла журнала, выполните следующую команду:

tail ~/myApp/logs/app.log

Вы должны увидеть что-то похожее на следующее:

{"level":"info","message":"::ffff:72.80.124.207 - - [07/Mar/2018:17:29:36 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\"\n","timestamp":"2018-03-07T17:29:36.962Z"}
{"level":"info","message":"::ffff:72.80.124.207 - - [07/Mar/2018:17:29:37 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://167.99.4.120:3000/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36\"\n","timestamp":"2018-03-07T17:29:37.067Z"}

Пока наш регистратор только записывает HTTP-запросы и связанные данные. Это очень важная информация в наших журналах, но как мы можем записывать пользовательские сообщения журнала? Конечно, будут времена, когда мы захотим эту возможность, например, для ошибок записи или профилирования производительности запросов к базе данных. Чтобы проиллюстрировать, как мы можем это сделать, давайте вызовем регистратор из маршрута обработчика ошибок.

Пакетexpress-generator по умолчанию включает маршрут обработчика ошибок 404 и 500, так что мы будем работать с этим. Откройте файл~/myApp/app.js:

nano ~/myApp/app.js

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

~/myApp/app.js

...
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  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-адрес запроса.

Обновите маршрут обработчика ошибок, чтобы он соответствовал следующему:

~/myApp/app.js

...
// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // add this line to include winston logging
  winston.error(`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`);

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});
...

Выйдите и сохраните файл.

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

Browser error message

Теперь еще раз взглянем на консоль в SSH Session A. Должна быть запись в журнале об ошибке, и благодаря настройке colorize ее легко найти.

Output[nodemon] starting `node bin/www`
error: 404 - Not Found - /foo - GET - ::ffff:72.80.124.207
info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:40:11 +0000] "GET /foo HTTP/1.1" 404 985 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

info: ::ffff:72.80.124.207 - - [07/Mar/2018:17:40:11 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://167.99.4.120:3000/foo" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36"

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

tail ~/myApp/logs/app.log

Вы увидите сообщение, подобное следующему:

{"level":"error","message":"404 - Not Found - /foo - GET - ::ffff:72.80.124.207","timestamp":"2018-03-07T17:40:10.622Z"}

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

Заключение

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

  • Чтобы узнать больше о транспорте Winston, см.Winston Transports Documentation.

  • Чтобы узнать больше о создании собственных транспортов, см.Adding Custom Transports

  • Чтобы создать конечную точку HTTP для использования с основным транспортом HTTP, см.winstond.

  • Чтобы использовать Winston в качестве инструмента профилирования, см.Profiling

Related