Поиск по сайту:

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


Введение

Эффективное решение для ведения журналов имеет решающее значение для успеха любого приложения. В этом руководстве мы сосредоточимся на пакете ведения журналов под названием Morgan для объединения журналов данных HTTP-запросов с другой информацией.

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

Предпосылки

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

  • Один сервер Ubuntu 16.04, настроенный в соответствии с руководством по начальной настройке сервера Ubuntu 16.04, включая пользователя без полномочий root и брандмауэр.
  • Node.js установлен с использованием официального PPA, как описано в разделе Как установить Node.js в Ubuntu 16.04.

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

Шаг 1 — Создание базовой ноды/экспресс-приложения

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

  1. sudo npm install express-generator -g

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

  1. express myApp

Затем установите 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. Мы будем использовать новый сеанс SSH для запуска команд и редактирования файлов, и мы будем называть этот сеанс сеансом B. Если не указано иное, все остальные команды должны выполняться в сеансе B.

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

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

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

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

  1. nano ~/myApp/app.js

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. cd ~/myApp
  2. npm install winston

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

  1. mkdir ~/myApp/config

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

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

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

  1. mkdir ~/myApp/logs

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

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

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

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

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

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

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

  • level — уровень сообщений для регистрации.
  • имя файла – файл, в который будут записываться данные журнала.
  • handleExceptions – перехват и регистрация необработанных исключений.
  • json — записывает данные журнала в формате JSON.
  • maxsize — максимальный размер файла журнала в байтах до создания нового файла.
  • maxFiles — ограничение количества файлов, создаваемых при превышении размера файла журнала.
  • colorize — раскрасить вывод. Это может быть полезно при просмотре журналов консоли.

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

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

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

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

...
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:

...
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, чтобы вывод был получен обоими транспортами (файлом и консолью):

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

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

...
module.exports = logger;

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

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;

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

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

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

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

  1. nano ~/myApp/app.js

Импортируйте winston в верхнюю часть файла с другими инструкциями require:

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

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

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

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

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

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

Мы готовы увидеть некоторые данные журнала! Если вы перезагрузите страницу в веб-браузере, вы должны увидеть что-то похожее на следующее в консоли сеанса SSH 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 в нашем вводном руководстве по JSON. Чтобы просмотреть содержимое файла журнала, выполните следующую команду:

  1. 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:

  1. nano ~/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-адрес запроса.

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

...
// 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. Ваш браузер должен отобразить сообщение об ошибке, которое выглядит следующим образом (ваше сообщение об ошибке может быть более подробным, чем показано):

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

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 должен показать нам новые записи журнала:

  1. 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 — Not Found), запрошенный URL-адрес (localhost/foo), метод запроса. (GET), IP-адрес, с которого поступил запрос, и отметка времени, когда был сделан запрос.

Заключение

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

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