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

介绍

有效的日志记录解决方案对于任何应用程序的成功至关重要。 Winston 是一款功能强大的日志记录库,也是一种流行的可用于 Node.js 应用程序的日志记录解决方案。Winston 的功能包括支持多种存储选项、日志级别、日志查询和内置的分析器。

在本教程中,您将使用 Winston 来记录一个您将创建的 Node/Express 应用程序的日志。您还将看到如何将 Winston 与另一种流行的用于 Node.js 的 HTTP 请求中间件记录器 Morgan 结合起来,以将 HTTP 请求数据日志与其他信息合并记录。完成本教程后,您的 Ubuntu 服务器将运行一个小型的 Node/Express 应用程序,并且 Winston 将被实现以将错误和消息记录到文件和控制台。

先决条件

要完成本教程,您将需要:

步骤1 — 创建一个基本的Node/Express应用

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

因为您在先决条件中安装了Node Package Manager,所以您可以使用npm命令来安装express-generator:

  1. sudo npm install express-generator -g

-g 标志会全局安装该软件包,这意味着它可以作为一个命令行工具在现有的 Node 项目/模块之外使用。

安装了 express-generator 后,您可以使用 express 命令创建您的应用,后面跟着您想要用于该项目的目录名称:

  1. express myApp

在本教程中,项目将被称为 myApp

注意:也可以直接运行 express-generator 工具,而不必先将其作为全局系统命令进行全局安装。要执行此操作,请运行以下命令:

  1. npx express-generator myApp

npx 命令是 Node 包管理器随附的命令运行器,它使得可以轻松地从 npm 注册表中运行命令行工具。

在第一次运行时,它会询问您是否同意下载该软件包:

Output
Need to install the following packages:
  express-generator
Ok to proceed? (y)

回答 y 并按下 ENTER。现在您可以使用 npx express-generator 代替 express

接下来,安装 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

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

  1. nodemon bin/www

此命令将在端口 3000 上启动应用程序。您可以通过将浏览器指向 http://your_server_ip:3000 来测试它是否正常工作。您应该会看到类似于这样的内容:

此时,您可以为本教程的剩余部分启动第二个 SSH 会话到您的服务器,让您刚刚启动的 Web 应用程序在原始会话中运行。本文的剩余部分中,当前正在运行应用程序的初始 SSH 会话将被称为 Session A。Session A 中的任何命令将显示为深海蓝色背景,就像这样:

  1. nodemon bin/www

您将使用新的 SSH 会话来运行命令和编辑文件。此会话将被称为 Session B。Session B 中的任何命令将显示为浅蓝色背景,就像这样:

  1. cd ~/myApp

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

在此步骤中,您创建了基本应用程序。接下来,您将对其进行自定义。

步骤 2 — 自定义日志变量

虽然由 express-generator 创建的默认应用程序是一个很好的起点,但您需要自定义应用程序,以便在需要时调用正确的日志记录器。

express-generator 包含了 Morgan HTTP 日志记录中间件,您将使用它来记录有关所有 HTTP 请求的数据。由于 Morgan 支持输出流,它与内置在 Winston 中的流支持很好地配合,使您能够 consol HTTP 请求数据日志与您选择使用 Winston 记录的任何其他内容。

express-generator 模板中,当引用 morgan 包时使用变量 logger。由于您将使用 morganwinston,它们都是日志记录包,将它们中的任何一个称为 logger 可能会引起混淆。要指定您想要的变量,您可以通过编辑 app.js 文件来更改变量声明。

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

  1. nano ~/myApp/app.js

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

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

将变量名称从 logger 更改为 morgan

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

此更新指定了声明的变量 morgan 将调用与 Morgan 请求记录器关联的 require() 方法。

你需要找到文件中其他地方引用的变量logger,并将其更改为morgan。您还需要更改morgan包使用的日志格式为combined,这是标准的Apache日志格式,将在日志中包含有用的信息,如远程IP地址和用户代理HTTP请求标头。

为此,请找到以下行:

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

将其更新为以下内容:

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

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

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

现在,您的应用已设置完成,可以开始使用Winston。

步骤3 — 安装和配置Winston

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

使用以下命令安装winston

  1. cd ~/myApp
  2. npm install winston

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

  1. mkdir ~/myApp/config

接下来,创建一个文件夹来存放日志文件:

  1. mkdir ~/myApp/logs

最后,安装app-root-path

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

当在 Node.js 中指定路径时,app-root-path 包非常有用。虽然这个包与 Winston 没有直接关联,但在确定 Node.js 中文件路径并避免丑陋的相对路径语法时非常有帮助。

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

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

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

接下来,添加以下代码来引入app-root-pathwinston包:

~/myApp/config/winston.js
const appRoot = require('app-root-path');
const winston = require('winston');

有了这些变量,你可以为你的传输定义配置设置了。传输是由 Winston 引入的一个概念,它指的是用于日志的存储/输出机制。Winston 自带四种核心传输: Console, File, HTTP, 和 Stream

本教程将专注于控制台传输和文件传输。控制台传输将日志记录到控制台,文件传输将日志记录到指定的文件中。每个传输定义可以包含配置设置,如文件大小、日志级别和日志格式。

以下是你将为每个传输使用的设置的快速摘要:

  • 级别: 记录消息的日志级别。
  • 文件名: 要用于写入日志数据的文件。
  • handleExceptions: 捕获并记录未处理的异常。
  • maxsize: 日志文件的最大大小,以字节为单位,在创建新文件之前。
  • maxFiles: 当日志文件大小超过限制时创建的文件数量的上限。
  • format: 日志输出的格式。

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

  • 0: 错误
  • 1: 警告
  • 2: 信息
  • 3: HTTP
  • 4: 冗余
  • 5: 调试
  • 6: 愚蠢

当为特定传输指定日志级别时,将记录该级别或更高级别的任何内容。例如,设置为信息级别时,将记录错误警告信息级别的内容。

调用记录器时会指定日志级别,这意味着您可以运行以下命令记录错误:logger.error('测试错误消息')

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

~/myApp/config/winston.js
...
// 为每个传输(文件、控制台)定义自定义设置
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

接下来,将以下代码添加到使用options变量中定义的属性实例化新的winston记录器的文件和控制台传输:

~/myApp/config/winston.js
...
// 使用上面定义的设置实例化一个新的Winston记录器
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // 不要在处理的异常上退出
});

默认情况下,morgan仅输出到控制台,因此您将定义一个流函数,该函数将能够将morgan生成的输出传送到winston日志文件中。您将使用info级别来接收两个传输(文件和控制台)的输出。将以下代码添加到配置文件中:

~/myApp/config/winston.js
...
// 创建一个带有“write”函数的流对象,该函数将被`morgan`使用
logger.stream = {
  write: function(message, encoding) {
    // 使用“info”日志级别,以便输出将被两个
    // 传输(文件和控制台)接收
    logger.info(message);
  },
};

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

~/myApp/config/winston.js
...
module.exports = logger;

完成的winston配置文件现在将如下所示:

~/myApp/config/winston.js
const appRoot = require("app-root-path");
const winston = require("winston");

// 为每个传输(文件、控制台)定义自定义设置
const options = {
  file: {
    level: "info",
    filename: `${appRoot}/logs/app.log`,
    handleExceptions: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
    format: winston.format.combine(
      winston.format.timestamp(),
      winston.format.json()
    ),
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  },
};

// 使用上述定义的设置实例化新的 Winston 记录器
const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console),
  ],
  exitOnError: false, // 不要在处理异常时退出
});

// 创建一个具有 'write' 函数的流对象,该函数将被 `morgan` 使用
logger.stream = {
  write: function (message, encoding) {
    // 使用 'info' 日志级别,以便输出将被两个传输(文件和控制台)捕获
    // 传输(文件和控制台)
    logger.info(message);
  },
};

module.exports = logger;

保存并关闭文件。

现在你已经配置了记录器,但你的应用程序仍然不知道它,也不知道如何使用它,所以你需要将记录器与应用程序集成。

步骤 4 — 将 Winston 与应用程序集成

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

打开文件进行编辑:

  1. nano ~/myApp/app.js

在文件顶部与其他 require 语句一起添加一个 winston 变量声明:

~/myApp/app.js
...
var winston = require('./config/winston');
...

你将首次使用 winston 是在 morgan 中。仍然在 app.js 文件中,找到以下行:

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

将其更新以包含 stream 选项:

~/myApp/app.js
...
app.use(morgan('combined', { stream: winston.stream }));
...

在这里,你将 stream 选项设置为你在 winston 配置中创建的流接口。

保存并关闭文件。

在这一步中,你配置了 Express 应用以与 Winston 协同工作。接下来,你将查看日志数据。

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

现在应用程序已配置完成,你可以查看一些日志数据了。在这一步中,你将审查日志条目并使用示例自定义日志消息更新你的设置。

如果在网页浏览器中重新加载页面,你应该会在 SSH 会话 A 的控制台中看到类似以下输出:

Output
[nodemon] restarting due to changes... [nodemon] starting `node bin/www` info: ::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" info: ::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. tail ~/myApp/logs/app.log

tail会在你的终端中输出文件的最后部分。

你应该会看到类似以下的输出:

{"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"}
{"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"}

由于你在文件传输配置的format选项中使用了winston.format.json(),文件传输中的输出将被写成一个JSON对象。你可以在JSON简介中了解更多关于JSON的信息。

到目前为止,你的记录器仅记录了HTTP请求和相关数据。这些信息在日志中是至关重要的。

将来,你可能想要记录自定义的日志消息,比如记录错误或分析数据库查询性能。例如,你将从错误处理路由调用记录器。默认情况下,express-generator包已经包含了一个404500错误处理路由,所以你将使用它。

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

  1. 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地址。

更新错误处理程序路由以包含winston日志记录:

~/myApp/app.js
...
// 错误处理程序
app.use(function(err, req, res, next) {
  // 设置本地变量,在开发环境中只提供错误信息
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // 包括winston日志记录
  winston.error(
    `${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
  );

  // 渲染错误页面
  res.status(err.status || 500);
  res.render('error');
});
...

保存并关闭文件。

为了测试这个过程,尝试访问项目中不存在的页面。访问一个不存在的页面会抛出404错误。在你的Web浏览器中,尝试加载以下URL:http://your_server_ip:3000/foo。感谢express-generator创建的样板,应用程序已经设置好了对这种错误的响应。

你的浏览器会显示如下错误消息:

当你在 SSH 会话 A 中查看控制台时,应该会有错误的日志记录。由于应用了colorize格式,很容易就能找到:

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

至于文件记录器,再次运行tail命令应该会显示新的日志记录:

  1. tail ~/myApp/logs/app.log

你会看到如下消息:

{"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 网络应用,并集成了一个 Winston 日志记录解决方案,作为一个有效的工具来提供应用程序性能的洞察力。

你可以为你的应用程序构建更健壮的日志解决方案,特别是当你的需求变得更加复杂时。要了解更多关于 Winston 传输的信息,请参阅 Winston 传输文档。要创建自己的传输,请参阅 添加自定义传输。要为与 HTTP 核心传输一起使用的 HTTP 端点,请参阅 winstond。要将 Winston 用作性能分析工具,请参阅 性能分析

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications-on-ubuntu-20-04