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

有效的日志记录解决方案对任何应用程序的成功至关重要。在本指南中,我们将重点介绍一种名为Winston的日志包,一种非常通用的日志记录库以及基于NPM下载统计信息的可用于Node.js应用程序的最流行的日志记录解决方案。本教程将向您展示如何使用Winston来记录我们将在此过程中创建的Node / Express应用程序。

介绍

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

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

先决条件

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

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

第1步 - 创建基本节点/快速应用程序

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来测试它是否正常工作。 你应该看到这样的东西:

默认Express生成器主页

在这一点上,启动第二个SSH会话到您的服务器以供本教程的剩余部分使用是个不错的主意,让我们刚开始运行的Web应用程序保留在原始会话中。 对于本文的其余部分,我们将引用到目前为止我们一直在使用的SSH会话,并且当前正在将该应用程序作为会话A运行。我们将使用新的SSH会话来运行命令和编辑文件,将此会话称为会话B.除非另有说明,否则所有剩余的命令应在会话B中运行。

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

express-generator创建的默认应用程序在启动我们时做得非常出色,甚至还包括我们将用于记录所有HTTP请求数据的Morgan HTTP日志记录中间件。 而且由于Morgan支持输出流,它与Winston内置的流支持结合得很好,使我们能够将HTTP请求数据日志与我们选择与Winston一起登录的任何其他内容整合在一起。

默认情况下, express-generator样板在引用morgan包时使用变量记录器 由于我们将使用morganwinston ,这两个都是日志软件包,调用其中一个日志记录器可能会引起混淆。 所以让我们通过编辑项目根目录中的app.js文件并进行一些更改来改变它。

要打开app.js进行编辑,请使用nano命令:

nano ~/myApp/app.js

在文件顶部附近找到以下行:

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

将其更改为以下内容:

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

我们还需要找到文件中引用的变量记录器的位置 ,并将其更改为morgan 在我们看来,让我们将morgan软件包使用的日志格式更改为combined ,这是标准的Apache日志格式,并将在日志中包含有用的信息,例如远程IP地址和用户代理HTTP请求标头。

为此,请找到以下行:

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

将其更改为以下内容:

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

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

键入CTRL-X ,然后按Y ,然后按ENTER退出并保存文件。

现在我们的应用程序已经建立,我们已经准备好开始与Winston合作。

第3步 - 安装和配置Winston

我们现在准备安装并配置Winston。 在这一步中,我们将探索一些可作为winston软件包一部分提供的配置选项,并创建一个将信息记录到文件和控制台的记录器。

要安装winston运行以下命令:

cd ~/myApp
npm install winston

将我们的应用程序的任何类型的支持或实用程序配置文件保存在一个特殊的目录中通常很有用,所以让我们创建一个包含winston配置的config文件夹:

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 /配置/ winston.js
var appRoot = require('app-root-path');
var winston = require('winston');

通过使用这些变量,我们可以为我们的传输定义配置设置。 传输是Winston引入的一个概念,指的是用于日志的存储/输出机制。 Winston带有三个核心传输 - 控制台文件HTTP 我们将重点介绍本教程的控制台和文件传输:控制台传输将记录信息到控制台,文件传输将记录信息到指定的文件。 每个传输定义可以包含自己的配置设置,例如文件大小,日志级别和日志格式。 以下是我们将用于每个运输工具的设置的简要总结:

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

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

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

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

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

〜/对myApp /配置/ 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 /配置/ 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 /配置/ winston.js
...
logger.stream = {
  write: function(message, encoding) {
    logger.info(message);
  },
};

最后,导出记录器,以便可以在应用程序的其他部分使用它:

〜/对myApp /配置/ winston.js
...
module.exports = logger;

完成的winston配置文件应该如下所示:

〜/对myApp /配置/ 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的控制台中看到与以下内容类似的内容:

[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错误。 回到您的Web浏览器,尝试加载以下URL: http:// your_server_ip :3000/foo 应用程序已经设置为响应这样的错误,这要感谢express-generator创建的样板。 您的浏览器应该显示一个如下所示的错误消息(您的错误消息可能比显示的内容更为详细):

浏览器错误消息

现在再看一下SSH会话A中的控制台。应该有一个错误日志条目,并且由于colorize设置,它应该很容易找到。

[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 Web应用程序,并集成了一个Winston日志记录解决方案,该解决方案将作为有效工具来提供对应用程序性能的深入了解。 您可以为应用程序构建强大的日志记录解决方案,特别是随着您的需求变得更加复杂,您可以做更多工作 我们建议您花时间查看其他一些文档:

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

分享按钮