如何在 Node.js 应用程序中使用 Pino-logger 实现日志记录

Avatar of Sarthak Duggal
Sarthak Duggal

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

日志记录本身是任何应用程序的关键方面。 日志记录帮助开发人员了解其代码在做什么。 它还有助于开发人员节省数小时的调试工作。 本教程介绍了如何在 Node.js 应用程序中使用 Pino-logger 实现日志记录。

通过日志记录,您可以存储有关应用程序流程的每一点信息。 使用 Pino 作为 Node.js 应用程序的依赖项,实现日志记录甚至将这些日志存储在单独的日志文件中变得轻而易举。 它在 GitHub 上的 7.8K 颗星证明了这一点。

在本指南中

  • 您将学习如何使用不同的日志级别配置日志记录服务。
  • 您将学习如何在终端中美化日志以及是否将 JSON 响应包含在日志中。
  • 您将了解如何将这些日志保存到单独的日志文件中。

完成后,您将能够使用 Pino-logger 在您的 Node.js 应用程序中使用最佳编码实践来实现日志记录。

先决条件

在按照本教程操作之前,请确保您已具备以下条件

  • 熟悉使用 Express 创建服务器。
  • 熟悉在没有任何身份验证的情况下设置 REST API。
  • 了解命令行工具或代码编辑器中的集成终端。

建议下载并安装像 Postman 这样的工具来测试 API 端点。

步骤 1:设置项目

在此步骤中,您使用 Express 和 Mongoose 设置基本的 Node.js CRUD 应用程序。 这样做是因为**在模拟真实世界应用程序的代码库中实现日志记录功能会更好**。

由于本文介绍的是如何实现日志记录器,因此您可以按照 “如何使用 Mongoose 和 MongoDB Atlas 执行 CRUD 操作” 来创建您的基本 Node.js CRUD 应用程序。

完成该教程后,您应该可以使用包含createreadupdatedelete 路由的 Node.js 应用程序。

此外,此时,您可以下载nodemon,以便每次保存代码库中的更改时,服务器都会自动重新启动,而您无需使用node server.js 手动重新启动它。

因此,在您的终端中编写以下命令

npm install -g --force nodemon

-g 标志表示依赖项已全局安装,为了全局执行某些操作,您在命令中添加了--force 标志。

步骤 2:安装 Pino

在此步骤中,您安装了日志记录所需的依赖项的最新版本。 这些包括PinoExpress-Pino-loggerPino-pretty。 您需要在项目根目录的命令行工具中执行以下命令。

npm install [email protected] [email protected] [email protected]

此时,您已准备好使用 Pino 创建日志记录服务。

步骤 3:创建日志记录服务

在此步骤中,您使用不同的日志级别(例如warningerrorinfo 等)创建 Pino-logger 服务。

之后,您使用 Node.js 中间件在应用程序中配置此日志记录器服务。 首先在根文件夹中创建一个名为services 的新目录

mkdir services

在此新目录中,创建一个名为loggerService.js 的新文件,并添加以下代码

const pino = require('pino')
module.exports = pino({})

此代码定义了使用 Pino-logger 可以创建的最基本的日志记录器服务。 导出的pino 函数接受两个可选参数optionsdestination,并返回一个logger 实例

但是,您当前没有传递任何选项,因为您将在后面的步骤中配置此日志记录器服务。 但是,这可能会给此日志记录器服务带来一些问题:您将在稍后看到的 JSON 日志不可读。 因此,要将其更改为可读格式,请在导出的pino 函数中提及prettyPrint 选项,之后您的loggerService.js 文件应如下所示

const pino = require('pino')
module.exports = pino(
  {
    prettyPrint: true,
  },
)

配置您的loggerService 将在后面的步骤中介绍。

完成此日志记录器服务的下一步是在根目录中的server.js 文件中添加以下几行代码

const expressPinoLogger = require('express-pino-logger');
const logger = require('./services/loggerService');

在此代码中,您导入了刚刚创建的logger 服务以及之前安装的express-pino-logger npm 包。

最后一步是使用您创建的logger 服务 配置express-pino-logger。 将此代码段添加到同一文件中的const app = express(); 之后

// ...

const loggerMidlleware = expressPinoLogger({
  logger: logger,
  autoLogging: true,
});

app.use(loggerMidlleware);

// ...

此代码使用expressPinoLogger 创建一个loggerMiddleware。 传递给函数的第一个选项是logger 本身,它表示您之前创建的loggerService。 第二个选项是autoLogging,它可以接受truefalse 作为值。 它指定您是否希望在日志中包含JSON 响应。 这将在稍后进行说明。

现在,最后,要测试loggerService,请重新访问您的foodRoutes.js 文件。 使用以下代码在顶部导入loggerService

const logger = require('../services/loggerService')

然后,在您之前创建的GET 路由控制器方法中,在回调函数的开头添加以下代码行

// ...

app.get("/food", async (request, response) => {
  logger.info('GET route is accessed')
  // ...
});

// ...

info 方法是 Pino-logger 附带的默认级别之一。 其他方法包括:fatalerrorwarndebugtracesilent

您可以通过将消息字符串作为参数传递给它来使用其中任何一个。

现在,在测试日志记录服务之前,以下是到目前为止server.js 文件的完整代码

const express = require("express");
const expressPinoLogger = require('express-pino-logger');
const logger = require('./services/loggerService');
const mongoose = require("mongoose");
const foodRouter = require("./routes/foodRoutes.js");
const app = express();
// ...
const loggerMidleware = expressPinoLogger({
  logger: logger,
  autoLogging: true,
});
app.use(loggerMidleware);
// ...
app.use(express.json());
mongoose.connect(
  "mongodb+srv://madmin:<password>@clustername.mongodb.net/<dbname>?retryWrites=true&w=majority",
  {
    useNewUrlParser: true,
    useFindAndModify: false,
    useUnifiedTopology: true
  }
);
app.use(foodRouter);

app.listen(3000, () => {
  console.log("Server is running...");
});

另外,不要忘记重启服务器

nodemon server.js

现在,您可以在终端中看到日志。 在 Postman 或类似工具中测试此 API 路由端点以查看它。 测试 API 后,您应该在终端中看到类似以下内容

Showing a black terminal window with output, including a first line in bright yellow, a second line in green and rest of the information in white. The information indicates the tool is watching files, starting the node server, when the GET route is accessed, and different API endpoints.

这提供了很多信息

  • 第一部分信息是日志的时间戳,它以默认格式显示,但我们可以在后面的步骤中将其更改为更易读的格式。
  • 接下来是info,它是 Pino-logger 附带的默认级别之一。
  • 接下来是一条简短的消息,说明请求已完成。
  • 最后,您可以在下一行看到该特定请求的完整JSON 响应。

步骤 4:配置日志

在此步骤中,您将学习如何配置 Logger 服务,以及如何使用pino-pretty 以及之前安装的pino 包中的内置选项来美化终端中的日志。

自定义级别

此时,您知道pino-logger 附带了默认的日志记录级别,您可以将其用作方法 来显示日志。

您在之前的步骤中使用了logger.info

但是,pino-logger 允许您使用自定义级别。 首先重新访问services 目录中的loggerService.js 文件。 在您在顶部导入pino 包之后,添加以下几行代码

// ...
const levels = {
  http: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};
// ...

此代码是一个简单的JavaScript 对象,用于定义其他日志记录级别。 此对象的键对应于日志级别的命名空间,而值应该是该级别的数值。

现在,要使用它,您必须在之前定义的导出的Pino 函数 中指定所有内容。 请记住,它接受的第一个参数是一个包含一些内置选项的对象

将该函数改写如下

module.exports = pino({
  prettyPrint: true,
  customLevels: levels, // our defined levels
  useOnlyCustomLevels: true,
  level: 'http',
})

在上面的代码中

  • 第一个选项customLevels: levels 指定应使用我们的自定义日志级别作为其他日志方法。
  • 第二个选项useOnlyCustomLevels: true 指定您只希望使用您的customLevels 并省略 Pino 的级别。

/explanation 要指定第二个选项useOnlyCustomLevels,Logger 的默认level 必须更改为customLevels 中的值。 这就是为什么您指定了第三个选项。

现在,您可以再次测试您的loggerService 并尝试使用您的customLevels 之一。 在您的foodRoutes.js 文件中尝试使用以下内容

// ...

app.get"/foods", async (request, response) => {
    logger.http('GET route is accessed')
});

// ...

/explanation 不要忘记在你的 server.js 文件中将 autoLogging: 设置为 false,因为实际上并不需要它带来的无关紧要的 JSON 响应。

const pino = require('pino')
const levels = {
  http: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};
module.exports = pino(
  {
    prettyPrint: true,
    customLevels: levels, // our defined levels
    useOnlyCustomLevels: true,
    level: 'http',
  },
)

你应该在你的终端中看到类似这样的内容

A black terminal window that shows the node server starting in green, a note that the server is running, and a timestamp for when the GET route is accessed.

而且,所有不必要的信息都应该消失了。

日志美化

现在你可以继续美化日志。换句话说,你是在终端输出中添加一些样式,使其更容易(或“更漂亮”)阅读。

从在导出的 pino 函数中传递另一个选项开始。添加该选项后,你的 pino 函数应该看起来像这样

module.exports = pino({
  customLevels: levels, // our defined levels
  useOnlyCustomLevels: true,
  level: 'http',
  prettyPrint: {
    colorize: true, // colorizes the log
    levelFirst: true,
    translateTime: 'yyyy-dd-mm, h:MM:ss TT',
  },
})

你添加了另一个选项,prettyPrint,它是一个启用美化打印的 JavaScript 对象。现在,在这个对象内部还有其他属性

  • colorize:这会为终端日志添加颜色。不同级别的日志被分配了不同的颜色。
  • levelFirst:这会在记录的日期和时间之前显示日志级别名称。
  • translateTime:这会将时间戳转换为人类可读的日期和时间格式。

现在,再次尝试 API 端点,但在尝试之前,请确保添加多个日志语句,以便查看终端中不同类型的日志。

// ...

app.get("/foods", async (request, response) => {
  logger.info('GET route is accessed')
  logger.debug('GET route is accessed')
  logger.warn('GET route is accessed')
  logger.fatal('GET route is accessed')

// ...

你应该在你的终端中看到类似这样的内容

A black terminal window with the same information as before, but with colored labels for different lines of information, like a red label for a fatal message.

此时,你已经对你的日志服务进行了足够的配置,可以用于生产级应用程序。

步骤 5:将日志存储到文件中

在最后一步中,你将学习如何将这些日志存储到一个单独的日志文件中。将日志存储到单独的文件中非常容易。你只需要在导出的 pino-function 中使用 destination 选项。

你可以通过向它传递 destination 选项来编辑 pino-function,如下所示

module.exports = pino(
  {
    customLevels: levels, // the defined levels
    useOnlyCustomLevels: true,
    level: 'http',
    prettyPrint: {
      colorize: true, // colorizes the log
      levelFirst: true,
      translateTime: 'yyyy-dd-mm, h:MM:ss TT',
    },
  },
  pino.destination(`${__dirname}/logger.log`)
)

pino.destination 将日志文件的路径作为参数。__dirname 变量指向当前目录,对于此文件来说是 services 目录。

/explanation 你在路径中添加了 logger.log 文件,即使它还不存在。这是因为在保存此文件时会自动创建该文件。如果由于某种原因它没有创建文件,你可以手动创建一个并将其添加到文件夹中。

以下是完整的 loggerService.js 文件

const pino = require('pino')
const levels = {
  http: 10,
  debug: 20,
  info: 30,
  warn: 40,
  error: 50,
  fatal: 60,
};
module.exports = pino(
  {
    customLevels: levels, // our defined levels
    useOnlyCustomLevels: true,
    level: 'http',
    prettyPrint: {
      colorize: true, // colorizes the log
      levelFirst: true,
      translateTime: 'yyyy-dd-mm, h:MM:ss TT',
    },
  },
  pino.destination(`${__dirname}/logger.log`)
)

再次测试你的 API,你应该在日志文件中看到你的日志,而不是在你的终端中。

结论

在本文中,你学习了如何创建一个日志服务,可以在生产级应用程序中使用。你学习了如何配置日志以及如何将这些日志存储到一个单独的文件中,以便将来参考。

你仍然可以通过阅读官方的 Pino 日志记录文档 来尝试各种配置选项。

以下是一些在创建新的日志服务时可以牢记的最佳实践

  • 上下文:日志应该始终包含有关数据、应用程序、时间等的上下文信息。
  • 目的:每个日志都应该有特定的目的。例如,如果给定的日志用于调试,那么你可以在提交之前确保将其删除。
  • 格式:所有日志的格式应该始终易于阅读。