如何在 Ubuntu 20.04 上使用 Winston 登录 Node.js 应用程序

介绍

有效的日志解决方案对于任何应用程序的成功至关重要。 Winston是一个多功能的日志库,也是可用于 Node.js应用程序的流行的日志解决方案。

在本教程中,您将使用 Winston 登录 Node/Express 应用程序,您将创建作为这个过程的一部分。您还将看到如何将 Winston 与 Morgan 结合起来,另一个流行的 HTTP 请求中间件日志器用于 Node.js,以将 HTTP 请求数据日志与其他信息合并。

前提条件

要遵循本教程,您将需要:

步骤 1 — 创建基本节点/Express 应用程序

Winston 通常用于从 Node.js 构建的 Web 应用程序中记录事件. 在此步骤中,您将使用 Express 框架创建一个简单的 Node.js Web 应用程序. 您将使用 express-generator,一个命令行工具,以便快速运行 Node/Express Web 应用程序。

由于您在前提条件中安装了 Node Package Manager,您可以使用npm命令来安装express-generator:

1sudo npm install express-generator -g

-g 旗帜在全球范围内安装该包,这意味着它可以作为现有 Node 项目/模块以外的命令行工具使用。

安装了快递生成器,您可以使用快递命令创建您的应用程序,然后是您想要用于项目的目录名称:

1express myApp

对于本教程,该项目将被称为myApp

<$>[注] 注: 也可以直接运行快递生成器工具,而不先在全球范围内安装它作为全系统的命令。

1npx express-generator myApp

npx命令是与 Node 包管理器一起发送的命令运行器,可以轻松地从npm注册表运行命令行工具。

在第一次运行中,它会问你是否同意下载该包:

1[label Output]
2Need to install the following packages:
3  express-generator
4Ok to proceed? (y)

回答「y」並按下「ENTER」。現在您可以使用「npx express-generator」而不是「express」。

接下来,安装 Nodemon,这将自动重新加载应用程序,每次你做出更改。 Node.js 应用程序需要重新启动任何时候对源代码进行更改,以便这些更改生效,因此Nodemon 会自动监控更改并重新启动应用程序。

1sudo npm install nodemon -g

要完成设置应用程序,转到应用程序目录并如下安装依赖:

1cd myApp
2npm install

默认情况下,使用快递生成器创建的应用程序在3000端口运行,因此您需要确保防火墙不会阻止端口。

打开3000端口,运行以下命令:

1sudo ufw allow 3000

您现在拥有启动 Web 应用程序所需的一切。 要做到这一点,请运行以下命令:

1nodemon bin/www

此命令将应用程序启动到端口 3000. 您可以通过将浏览器指向 http://your_server_ip:3000 来测试它是否工作。

Default express-generator homepage

在此时刻,您可以在本教程的剩余时间开始第二次 SSH 会话到您的服务器,留下您刚刚在原始会话中启动的 Web 应用程序。

1nodemon bin/www

您将使用新的 SSH 会话来运行命令和编辑文件. 该会话将被称为 Session B. 会话 B 中的任何命令都将出现在这样一个蓝色背景下:

1[environment second]
2cd ~/myApp

除非另有说明,否则您将在 B 会话中运行所有剩余的命令。

在此步骤中,您创建了基本应用程序,接下来,您将定制它。

步骤 2 – 定制存储变量

虽然由快递生成器创建的默认应用程序是一个很好的开始,但您需要定制应用程序,以便在需要时打电话给正确的日志。

快递生成器包括摩根HTTP日志中间软件,您将使用它来记录所有HTTP请求的数据. 由于摩根支持输出流,它与Winston内置的流支持相结合,允许您将HTTP请求数据日志与您选择与Winston登录的任何其他内容合并。

在引用morgan包时,express-generator锅炉板使用变量logger。由于您将使用morganwinston,这两个都是日志包,所以将其中一个称为logger可能令人困惑。

要打开app.js进行编辑,请使用nano或您最喜欢的文本编辑器:

1[environment second]
2nano ~/myApp/app.js

在文件的顶部找到以下行:

1[environment second]
2[label ~/myApp/app.js]
3...
4var logger = require('morgan');
5...

将变量名称从logger更改为morgan:

1[environment second]
2[label ~/myApp/app.js]
3...
4var morgan = require('morgan');
5...

此更新规定,声明变量morgan将调用与摩根请求日志器相关的require()方法。

您还需要将 morgan包使用的日志格式更改为组合,这是标准的Apache日志格式,并将日志中包含有用的信息,如远程IP地址和用户代理HTTP请求标题。

要做到这一点,找到下面的行:

1[environment second]
2[label ~/myApp/app.js]
3...
4app.use(logger('dev'));
5...

更新至以下内容:

1[environment second]
2[label ~/myApp/app.js]
3...
4app.use(morgan('combined'));
5...

这些更改将帮助您了解在整合 Winston 配置后,在任何特定时刻引用哪个日志包。

完成后,保存并关闭文件。

现在您的应用已经设置,您可以开始与Winston合作。

步骤 3 – 安装和配置 Winston

在此步骤中,您将安装和配置 Winston. 您还将探索作为Winston包的一部分可用的配置选项,并创建一个日志器来记录文件和控制台的信息。

使用以下命令安装Winston:

1[environment second]
2cd ~/myApp
3npm install winston

将您的应用程序的任何支持或实用程序配置文件保存在一个特殊的目录中是有帮助的. 创建一个包含winston配置的config文件夹:

1[environment second]
2mkdir ~/myApp/config

接下来,创建一个包含您的日志文件的文件夹:

1[environment second]
2mkdir ~/myApp/logs

最后,安装app-root-path:

1[environment second]
2npm install app-root-path --save

在 Node.js 中指定路径时,该app-root-path套件非常有用,虽然这个套件与 Winston 没有直接关系,但在确定 Node.js 中的文件路径时,它有助于指定 Winston 日志文件的位置,并避免丑陋的相对路径语法。

现在处理日志的配置已经到位,您可以定义您的设置. 创建并打开 ~/myApp/config/winston.js 进行编辑:

1[environment second]
2nano ~/myApp/config/winston.js

winston.js文件将包含您的winston配置。

接下来,添加以下代码以要求app-root-pathwinston包:

1[environment second]
2[label ~/myApp/config/winston.js]
3const appRoot = require('app-root-path');
4const winston = require('winston');

有了这些变量,您可以为您的 transports定义配置设置。 运输是温斯顿引入的概念,指用于日志的存储/输出机制。

您将专注于本教程的控制台和文件传输。控制台传输将记录信息到控制台,文件传输将记录信息到指定文件。

以下是您将在每个运输中使用的设置的快速总结:

  • 水平:要记录的消息水平.
  • filename:用于编写日志数据的文件.
  • handleExceptions: catch and log untreated exceptions.
  • maxsize:日志文件的最大尺寸,以字节,在创建新文件之前。
  • maxFiles:在日志文件大小超过时创建的文件数量限制。

Logging levels 表示消息优先级,用整数表示。 温斯顿使用从 0 到 6 (最高到最低) 的npm日志级别:

  • 0: error
  • 1: warn
  • 2: info
  • 3: http
  • 4: verbose
  • 5: debug
  • 6: silly

指定特定运输的日志级别时,会记录此级别或更高级别的任何内容,例如,设置信息级别时,会记录错误级别、警告级别或信息级别的任何内容。

当您调用日志仪时,会指定日志级别,这意味着您可以执行以下命令来记录错误: logger.error('test error message')

在配置文件中,添加以下代码来定义Winston配置中的文件控制台传输的配置设置:

 1[environment second]
 2[label ~/myApp/config/winston.js]
 3...
 4// define the custom settings for each transport (file, console)
 5const options = {
 6  file: {
 7    level: "info",
 8    filename: `${appRoot}/logs/app.log`,
 9    handleExceptions: true,
10    maxsize: 5242880, // 5MB
11    maxFiles: 5,
12    format: winston.format.combine(
13      winston.format.timestamp(),
14      winston.format.json()
15    ),
16  },
17  console: {
18    level: "debug",
19    handleExceptions: true,
20    format: winston.format.combine(
21      winston.format.colorize(),
22      winston.format.simple()
23    ),
24  },
25};

接下来,添加以下代码,以使用选项变量中定义的属性来实例化一个新的温斯顿日志器与文件和控制台传输:

 1[environment second]
 2[label ~/myApp/config/winston.js]
 3...
 4// instantiate a new Winston Logger with the settings defined above
 5const logger = winston.createLogger({
 6  transports: [
 7    new winston.transports.File(options.file),
 8    new winston.transports.Console(options.console),
 9  ],
10  exitOnError: false, // do not exit on handled exceptions
11});

默认情况下,morgan只输出到控制台,因此您将定义一个流函数,可以将morgan生成的输出输入到winston日志文件中。

 1[environment second]
 2[label ~/myApp/config/winston.js]
 3...
 4// create a stream object with a 'write' function that will be used by `morgan`
 5logger.stream = {
 6  write: function(message, encoding) {
 7    // use the 'info' log level so the output will be picked up by both
 8    // transports (file and console)
 9    logger.info(message);
10  },
11};

最后,添加下面的代码来导出日志器,以便在应用程序的其他部分中使用:

1[environment second]
2[label ~/myApp/config/winston.js]
3...
4module.exports = logger;

完成的Winston配置文件现在将看起来像这样:

 1[environment second]
 2[label ~/myApp/config/winston.js]
 3const appRoot = require("app-root-path");
 4const winston = require("winston");
 5
 6// define the custom settings for each transport (file, console)
 7const options = {
 8  file: {
 9    level: "info",
10    filename: `${appRoot}/logs/app.log`,
11    handleExceptions: true,
12    maxsize: 5242880, // 5MB
13    maxFiles: 5,
14    format: winston.format.combine(
15      winston.format.timestamp(),
16      winston.format.json()
17    ),
18  },
19  console: {
20    level: "debug",
21    handleExceptions: true,
22    format: winston.format.combine(
23      winston.format.colorize(),
24      winston.format.simple()
25    ),
26  },
27};
28
29// instantiate a new Winston Logger with the settings defined above
30const logger = winston.createLogger({
31  transports: [
32    new winston.transports.File(options.file),
33    new winston.transports.Console(options.console),
34  ],
35  exitOnError: false, // do not exit on handled exceptions
36});
37
38// create a stream object with a 'write' function that will be used by `morgan`
39logger.stream = {
40  write: function (message, encoding) {
41    // use the 'info' log level so the output will be picked up by both
42    // transports (file and console)
43    logger.info(message);
44  },
45};
46
47module.exports = logger;

保存并关闭文件。

你现在已经配置了日志仪,但你的应用程序仍然不知道它,或者如何使用它,所以你需要将日志仪与应用程序集成。

步骤 4 – 与应用程序集成 Winston

要让你的日志器与应用程序工作,你需要让express知道它. 你在步骤2中看到你的express配置位于app.js,所以你可以将你的日志器导入到这个文件中。

打开文件以编辑:

1[environment second]
2nano ~/myApp/app.js

添加一个Winston变量声明在文件的顶部与其他要求声明:

1[environment second]
2[label ~/myApp/app.js]
3...
4var winston = require('./config/winston');
5...

你将使用winston的第一个地方是morgan。在app.js中,找到下面的行:

1[environment second]
2[label ~/myApp/app.js]
3...
4app.use(morgan('combined'));
5...

更新它以包括选项:

1[environment second]
2[label ~/myApp/app.js]
3...
4app.use(morgan('combined', { stream: winston.stream }));
5...

在这里,您将选项设置为您作为温斯顿配置的一部分创建的流界面。

保存并关闭文件。

在此步骤中,您将您的 Express 应用程序配置为与 Winston 合作,接下来,您将查看日志数据。

步骤 5 – 访问日志数据并记录自定义日志消息

现在应用程序已配置,您已经准备好看到一些日志数据. 在此步骤中,您将检查日志条目并更新您的设置,使用示例自定义日志消息。

如果您在 Web 浏览器中重新加载页面,则应该在 SSH Session A 控制台中看到类似于以下输出的东西:

1[secondary_label Output]
2[nodemon] restarting due to changes...
3[nodemon] starting `node bin/www`
4info: ::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"
5
6info: ::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[environment second]
2tail ~/myApp/logs/app.log

尾巴将输出您的终端中的文件的最后一部分。

你应该看到类似于以下的东西:

1[environment second]
2{"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"}
3{"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"}

文件传输中的输出将被写成 JSON 对象,因为您在文件传输配置的格式选项中使用了winston.format.json()

到目前为止,您的日志器只记录 HTTP 请求和相关数据,这些信息必须在您的日志中。

在未来,您可能想要记录自定义日志消息,例如记录错误或配置数据库查询性能。例如,您将从错误处理路线调用日志器。

打开~/myApp/app.js文件:

1[environment second]
2nano ~/myApp/app.js

查找文件底部的代码块,它看起来像这样:

 1[environment second]
 2[label ~/myApp/app.js]
 3...
 4// error handler
 5app.use(function(err, req, res, next) {
 6  // set locals, only providing error in development
 7  res.locals.message = err.message;
 8  res.locals.error = req.app.get('env') === 'development' ? err : {};
 9
10  // render the error page
11  res.status(err.status || 500);
12  res.render('error');
13});
14...

此部分是最后的错误处理路径,最终会将错误响应发送回客户端. 由于所有服务器侧错误都会通过此路径运行,所以包括温斯顿日志器是一个很好的地方。

由于您现在正在处理错误,您想要使用错误日志级别. 两个传输器都配置为错误级别的日志消息,所以您应该在控制台和文件日志中看到输出。

您可以在日志中包含您想要的任何内容,包括如下信息:

  • err.status: HTTP 错误状态代码 如果没有,默认值为 500.
  • err.message: 错误的细节.
  • req.originalUrl: 被请求的 URL.
  • req.path: 请求 URL 的路径部分.
  • req.method: 请求的 HTTP 方法 (GET, POST, PUT 等)。

更新错误处理路径以包括Winston日志:

 1[environment second]
 2[label ~/myApp/app.js]
 3...
 4// error handler
 5app.use(function(err, req, res, next) {
 6  // set locals, only providing error in development
 7  res.locals.message = err.message;
 8  res.locals.error = req.app.get('env') === 'development' ? err : {};
 9
10  // include winston logging
11  winston.error(
12    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
13  );
14
15  // render the error page
16  res.status(err.status || 500);
17  res.render('error');
18});
19...

保存并关闭文件。

要测试此过程,请尝试在项目中访问一个不存在的页面。访问一个不存在的页面会引发 404 错误。在您的 Web 浏览器中,请尝试加载以下 URL: http://your_server_ip:3000/foo. 由于由 express-generator 创建的锅炉板,该应用程序已设置以响应此类错误。

您的浏览器会显示这样的错误消息:

Browser error message

当你在SSH Session A中查看控制台时,应该有一个错误的日志条目. 由于应用的色彩化格式,它应该很容易发现:

1[secondary_label Output]
2[nodemon] starting `node bin/www`
3error: 404 - Not Found - /foo - GET - ::1
4info: ::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"
5
6info: ::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"

至于文件日志器,运行尾巴命令再次应该向您显示新的日志记录:

1[environment second]
2tail ~/myApp/logs/app.log

您将看到如下这样的消息:

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

错误消息包括您专门指示Winston作为错误处理器的一部分登录的所有数据,这些信息将包括错误状态(404 - Not Found),请求的URL(localhost/foo),请求方法(GET),请求的IP地址,以及请求时刻印。

结论

在本教程中,您建立了一个简单的 Node.js Web 应用程序,并集成了一个 Winston 日志解决方案,该解决方案将作为一个有效的工具,以提供对应用程序的性能的见解。

您可以做更多的事情来为您的应用程序构建可靠的日志解决方案,特别是当您的需求变得更加复杂时。 有关 Winston 运输的更多信息,请参阅 Winston Transports Documentation. 若要创建自己的运输,请参阅 添加自定义运输 若要创建用于 HTTP 核心运输的 HTTP 终端,请参阅 winstond. 若要使用 Winston 作为配置工具,请参阅 Profiling

Published At
Categories with 技术
comments powered by Disqus