如何使用Winston记录Node.js应用程序

来自菜鸟教程
跳转至:导航、​搜索

介绍

有效的日志记录解决方案对于任何应用程序的成功都至关重要。 在本指南中,我们将重点介绍一个名为 Winston 的日志记录包,这是一个极其通用的日志记录库,也是 Node.js 应用程序可用的最流行的日志记录解决方案,基于 NPM 下载统计信息。 Winston 的功能包括支持多种存储选项和日志级别、日志查询,甚至是内置分析器。 本教程将向您展示如何使用 Winston 记录我们将在此过程中创建的 Node/Express 应用程序。 我们还将了解如何将 Winston 与另一个流行的用于 Node.js 的 HTTP 请求中间件记录器(称为 Morgan)结合起来,以将 HTTP 请求数据日志与其他信息合并。

完成本教程后,您将拥有一个运行小型 Node/Express 应用程序的 Ubuntu 服务器。 您还将实现 Winston 以将错误和消息记录到文件和控制台。

先决条件

在开始本指南之前,您需要以下内容:

有了这些先决条件,我们就可以构建我们的应用程序并安装 Winston。

第 1 步——创建一个基本的 Node/Express 应用程序

Winston 的一个常见用途是记录使用 Node.js 构建的 Web 应用程序的事件。 为了充分展示如何整合 Winston,我们将使用 Express 框架创建一个简单的 Node.js Web 应用程序。 为了帮助我们运行一个基本的 Web 应用程序,我们将使用 express-generator,一个让 Node/Express Web 应用程序快速运行的命令行工具。 因为我们安装了 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

我们现在拥有启动 Web 应用程序所需的一切。 为此,请运行以下命令:

nodemon bin/www

这将启动在端口 3000 上运行的应用程序。 我们可以通过在 Web 浏览器中转到 http://your_server_ip:3000 来测试它是否正常工作。 您应该看到如下内容:

此时最好为您的服务器启动第二个 SSH 会话以用于本教程的其余部分,让我们刚刚开始在原始会话中运行的 Web 应用程序。 对于本文的其余部分,我们将参考我们目前使用的 SSH 会话,它当前作为会话 A 运行应用程序。 我们将使用新的 SSH 会话来运行命令和编辑文件,我们将此会话称为会话 B。 除非另有说明,否则所有剩余的命令都应在会话 B 中运行。

第 2 步 - 自定义 Node.js 应用程序

express-generator 创建的默认应用程序在帮助我们入门方面做得很好,它甚至包括 Morgan HTTP 日志中间件,我们将使用它来记录有关所有 HTTP 请求的数据。 由于 Morgan 支持输出流,它与 Winston 中内置的流支持完美搭配,使我们能够将 HTTP 请求数据日志与我们选择使用 Winston 记录的任何其他内容合并。

默认情况下,express-generator 样板在引用 morgan 包时使用变量 logger。 由于我们将使用 morganwinston,它们都是日志记录包,因此调用其中任何一个 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 地址和用户代理 HTTP 请求标头。

为此,请找到以下行:

~/myApp/app.js

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

将其更改为以下内容:

~/myApp/app.js

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

在集成 Winston 配置后,这些更改将帮助我们更好地了解我们在任何给定时间引用的日志记录包。

通过键入 CTRL-XYENTER 退出并保存文件。

现在我们的应用程序已经设置好了,我们可以开始使用 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-pathwinston 包:

~/myApp/config/winston.js

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

有了这些变量,我们就可以为我们的 transports 定义配置设置。 传输是 Winston 引入的一个概念,它指的是用于日志的存储/输出机制。 Winston 带有三个核心传输 - consolefileHTTP。 我们将重点关注本教程的控制台和文件传输:控制台传输将信息记录到控制台,文件传输将信息记录到指定文件。 每个传输定义都可以包含自己的配置设置,例如文件大小、日志级别和日志格式。 以下是我们将为每个传输使用的设置的快速摘要:

  • level - 要记录的消息级别。
  • filename - 用于写入日志数据的文件。
  • handleExceptions - 捕获并记录未处理的异常。
  • json - 以 JSON 格式记录日志数据。
  • maxsize - 在创建新文件之前,日志文件的最大大小,以字节为单位。
  • maxFiles - 限制超过日志文件大小时创建的文件数。
  • colorize - 着色输出。 这在查看控制台日志时会很有帮助。

日志级别表示消息优先级,用整数表示。 Winston 使用 npm 日志级别,优先级从 0 到 5(从最高到最低):

  • 0:错误
  • 1:警告
  • 2:信息
  • 3:详细
  • 4:调试
  • 5:傻

为特定传输指定日志记录级别时,将记录该级别或更高级别的任何内容。 例如,通过指定级别 info,将记录级别为 errorwarninfo 的任何内容。 调用记录器时指定日志级别,这意味着我们可以执行以下操作来记录错误:logger.error('test error message')

我们可以在 winston 配置中定义 fileconsole 传输的配置设置,如下所示:

~/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,
  },
};

接下来,使用 options 变量中定义的属性实例化一个带有文件和控制台传输的新 winston 记录器:

~/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;

退出并保存文件。

我们现在配置了记录器,但我们的应用程序仍然不知道它或如何使用它。 我们现在将记录器与应用程序集成。

第 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 }));
...

退出并保存文件。

我们已经准备好查看一些日志数据了! 如果您在 Web 浏览器中重新加载页面,您应该会在 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 的更多信息。 要查看日志文件的内容,请运行以下命令:

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 创建的样板,应用程序已经设置为响应此类错误。 您的浏览器应显示如下所示的错误消息(您的错误消息可能比显示的更详细):

现在再看一下 SSH 会话 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 - Not Found)、请求的 URL (localhost/foo)、请求方法 (GET )、发出请求的 IP 地址以及发出请求的时间戳。

结论

在本教程中,您构建了一个简单的 Node.js Web 应用程序并集成了一个 Winston 日志记录解决方案,该解决方案将作为一种有效的工具来深入了解应用程序的性能。 您可以做更多工作来为您的应用程序构建强大的日志记录解决方案,尤其是当您的需求变得更加复杂时。 我们建议您花时间查看其中一些其他文档:

  • 要了解有关 Winston 传输的更多信息,请参阅 Winston 传输文档
  • 要了解有关创建自己的传输的更多信息,请参阅 添加自定义传输
  • 要创建用于 HTTP 核心传输的 HTTP 端点,请参阅 winstond
  • 要将 Winston 用作分析工具,请参阅 Profiling