日志记录本身是任何应用程序的关键方面。 日志记录帮助开发人员了解其代码在做什么。 它还有助于开发人员节省数小时的调试工作。 本教程介绍了如何在 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 应用程序。
完成该教程后,您应该可以使用包含create
、read
、update
和delete
路由的 Node.js 应用程序。
此外,此时,您可以下载nodemon
,以便每次保存代码库中的更改时,服务器都会自动重新启动,而您无需使用node server.js
手动重新启动它。
因此,在您的终端中编写以下命令
npm install -g --force nodemon
-g
标志表示依赖项已全局安装,为了全局执行某些操作,您在命令中添加了--force
标志。
步骤 2:安装 Pino
在此步骤中,您安装了日志记录所需的依赖项的最新版本。 这些包括Pino
、Express-Pino-logger
和Pino-pretty
。 您需要在项目根目录的命令行工具中执行以下命令。
npm install [email protected] [email protected] [email protected]
此时,您已准备好使用 Pino 创建日志记录服务。
步骤 3:创建日志记录服务
在此步骤中,您使用不同的日志级别(例如warning
、error
、info
等)创建 Pino-logger 服务。
之后,您使用 Node.js 中间件在应用程序中配置此日志记录器服务。 首先在根文件夹中创建一个名为services
的新目录
mkdir services
在此新目录中,创建一个名为loggerService.js
的新文件,并添加以下代码
const pino = require('pino')
module.exports = pino({})
此代码定义了使用 Pino-logger 可以创建的最基本的日志记录器服务。 导出的pino
函数接受两个可选参数options
和destination
,并返回一个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
,它可以接受true
或false
作为值。 它指定您是否希望在日志中包含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 附带的默认级别之一。 其他方法包括:fatal
、error
、warn
、debug
、trace
或silent
。
您可以通过将消息字符串作为参数传递给它来使用其中任何一个。
现在,在测试日志记录服务之前,以下是到目前为止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 后,您应该在终端中看到类似以下内容

这提供了很多信息
- 第一部分信息是日志的时间戳,它以默认格式显示,但我们可以在后面的步骤中将其更改为更易读的格式。
- 接下来是
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',
},
)
你应该在你的终端中看到类似这样的内容

而且,所有不必要的信息都应该消失了。
日志美化
现在你可以继续美化日志。换句话说,你是在终端输出中添加一些样式,使其更容易(或“更漂亮”)阅读。
从在导出的 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')
// ...
你应该在你的终端中看到类似这样的内容

此时,你已经对你的日志服务进行了足够的配置,可以用于生产级应用程序。
步骤 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 日志记录文档 来尝试各种配置选项。
以下是一些在创建新的日志服务时可以牢记的最佳实践
- 上下文:日志应该始终包含有关数据、应用程序、时间等的上下文信息。
- 目的:每个日志都应该有特定的目的。例如,如果给定的日志用于调试,那么你可以在提交之前确保将其删除。
- 格式:所有日志的格式应该始终易于阅读。
我遇到了以下错误:
“ReferenceError: FinalizationRegistry is not defined”
当我尝试在 Docker 容器中运行它时,作为单独的应用程序运行时没有问题。
当我从 index.js 文件中删除日志导入时,它也可以完美地作为 Docker 容器运行。有没有人遇到过这个问题?