- 原文地址:A Guide to Node.js Logging
- 原文做者:dkundel
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:fireairforce
当你开始使用 JavaScript 开始时,你应该学会的第一件事就是如何经过 console.log()
将事物记录到控制台。若是你搜索如何调试 JavaScript
,你会发现数百篇博客文章和 StackOverflow 上的文章会告诉你很“简单”的使用 console.log()
来完成调试。由于这是一种常见的作法,咱们甚至开始使用 linter
规则,好比 no-console
,以确保咱们不会在生产代码中留下意外的日志记录。可是若是咱们真的想记录一些东西来提供更多的信息呢?html
在这篇博文中,我将会介绍一些你想要记录信息的各类状况,以及在 Node.js 中 console.log
和 console.error
的区别,以及如何在不影响用户控制台的状况下往库里面发送日志记录。前端
console.log(`Let's go!`);
复制代码
虽然您能够在浏览器和 Node.js 中使用 console.log
或 console.error
,但在使用 Node.js 时须要记住一件重要的事情。在一个叫作 index.js
的文件中写下面的代码:node
console.log('Hello there');
console.error('Bye bye');
复制代码
而后在终端里面使用 node index.js
来运行它,你会看到这两个直接在下面输出:android
然而,虽然这两个看上去可能相同,但系统实际上对它们的处理方式并不相同。若是你去查看 Node.js 文档中 console
部分,你会看到 console.log
是使用 stdout
来打印而 console.error
使用 stderr
来打印。ios
每一个进程均可以使用三个默认的 streams
来工做。它们分别是 stdin
、stdout
和 stderr
。stdin
流来处理和你的进程相关的输出。例如按下按钮或重定向输出(咱们会在一秒钟以内完成)。stdout
流则用于你的应用程序的输出。最后 stderr
用于错误消息。若是你想了解 stderr
存在的缘由以及何时使用它,能够查看本文。git
简而言之,这容许咱们使用 redirect(>
)和 pipe(|
)运算符来处理和应用程序实际结果分开的错误和诊断信息。虽然 >
容许咱们将命令的输出重定向到文件中,2>
容许咱们将 stderr
的输出重定向到文件中。例如,下面这个命令会将 “Hello there” 传递到一个叫作 hello.log
的文件中和将 “Bye bye” 传递到一个叫作 error.log
的文件中。github
node index.js > hello.log 2> error.log
复制代码
既然咱们已经了解了日志记录的基础记录方面,让咱们先谈谈你可能想要记录某些内容的不一样用例。一般这些用例属于如下的类别之一:express
本篇博客将会跳过前面两个类别,而后重点介绍基于 Node.js 的后三个类别npm
你可能须要在服务器上进行日志记录的缘由有不少。例如,记录传入的请求从而容许你从里面提取信息,好比有多少用户正在访问 404,这些请求多是什么,或者正在使用什么 User-Agent
。你也想知道何时出了问题以及为何会出现问题。后端
若是你想在文章的这一部分中尝试下面的内容,首先要确保建立一个文件夹。在项目目录下建立一个叫作 index.js
的文件,而后使用下面的代码来初始化整个项目而且安装一下 express
:
npm init -y
npm install express
复制代码
而后设置一个带有中间件的服务器,只须要 console.log
为来提供每次的请求。将下面的内容放在 index.js
文件里面:
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
app.use((req, res, next) => {
console.log('%O', req);
next();
});
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(PORT, () => {
console.log('Server running on port %d', PORT);
});
复制代码
咱们用 console.log('%O', req)
来记录整个对象。console.log
在引擎盖下使用 util.format
,它还支持 %O
等其余占位符。你能够在 Node.js 文档中阅读它们。
当你运行 node index.js
执行服务器而且导航到 http://localhost:3000,你会注意到它将打印出许多咱们真正并不须要的信息。
若是将代码改为 console.log('%s', req)
为不打印整个对象,咱们也不会得到太多的信息。
咱们能够编写咱们本身的打印函数,它只输出咱们关心的东西,可是让咱们先回退一步,讨论一下咱们一般关心的事情。虽然这些信息常常成为咱们关注的焦点,但实际上咱们可能还须要其余信息。例如:
pm2
的工具来运行多个 Node 进程另外,既然咱们知道全部的东西都会转到 stdout
和 stderr
,那么咱们可能须要不一样的日志级别,而且根据它们来配置和过滤日志的能力。
咱们能够经过访问各部分的 process
而且写一大堆 JavaScript 代码来获取这些,可是关于 Node.js 最好的事情是咱们获得了 npm
生态系统,而且已经有各类各样的库供咱们使用。其中有一些是:
我我的很喜欢 pino
这个库,由于它运行很快,而且生态系统比较好,让咱们来看看如何使用 pino
来帮咱们记录日志。咱们同时也可使用 express-pino-logger
包来帮助咱们整洁的记录请求。
同时安装 pino
和 express-pino-logger
:
npm install pino express-pino-logger
复制代码
而后更新 index.js
文件来使用记录器和中间件:
const express = require('express');
const pino = require('pino');
const expressPino = require('express-pino-logger');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
const expressLogger = expressPino({ logger });
const PORT = process.env.PORT || 3000;
const app = express();
app.use(expressLogger);
app.get('/', (req, res) => {
logger.debug('Calling res.send');
res.send('Hello World');
});
app.listen(PORT, () => {
logger.info('Server running on port %d', PORT);
});
复制代码
在这个代码片断中,咱们经过 pino
建立了一个 logger
实例并将其传递给 express-pino-logger
来建立一个新的中间件,而且经过 app.use
来调用它。此外,咱们在服务器启动的位置用 logger.info
来替换 console.log
,并在咱们的路由中添加一行 logger.debug
来显示一个额外的日志级别。
若是经过 node index.js
再次运行从新启动服务器,你将会看到一个彻底不一样的输出,它会为每一行打印一个 JSON。再次导航到 http://localhost:3000,你将会看到添加了另外一行 JSON。
若是你检查这个 JSON,你将看到它包含全部先前提到的信息,例如时间戳。您可能还会注意到咱们的 logger.debug
声明没有打印出来。那是由于咱们必须更改默认日志级别才能使其可见。当咱们建立 logger
实例时,咱们将值设为 process.env.LOG_LEVEL
意味着咱们能够经过它更改值,或者接受默认值 info
。咱们能够经过运行 LOG_LEVEL=debug node index.js
来调整日志的级别。
在咱们这样作以前,让咱们先认清这样一个事实,即如今的输出并非真正可读的。这是故意的。pino
遵循一个理念,为了提升性能,你应该经过管道(使用|
)输出将日志任何处理移动到单独的过程当中去。这包括使其可读或将其上传到云主机上面去。咱们称这些为 传输
。查看关于传输
的文档 去了解 pino
中的错误为何没有写入 stderr
。
咱们将使用 pino-pretty
来查看更易读的日志版本。在终端运行:
npm install --save-dev pino-pretty
LOG_LEVEL=debug node index.js | ./node_modules/.bin/pino-pretty
复制代码
如今,你的全部日志信息都会使用 |
操做符输出到 pino-pretty
中去。若是你再次去请求 http://localhost:3000。你应该还能看到你的 debug
信息。
有许多现有的传输工具能够美化或转换你的日志。你甚至能够经过 pino-colada
来显示 emojis。这会对你的本地开发颇有用。在生产环境中运行服务器后,你可能但愿将日志输出到到另一个传输中,使用 >
将其写入磁盘以待稍后处理,或者使用相似于 tee
的命令来进行同时的处理。
该 文档 还将包含有关诸如轮换日志文件,过滤和将日志写入不一样文件等内容的信息。
既然咱们研究了如何有效地为服务器应用程序编写日志,为何不对咱们编写的库使用相同的技术呢?
问题是,你的库可能但愿记录用于调试的内容,但实际上不该该让使用者的应用程序变得混乱。相反,若是须要调试某些东西,使用者应该可以启用日志。你的库在默认状况下应该是不会处理这些的,并将写入输出的操做留给用户。
express
就是一个很好的例子。在 express
框架下有不少的事情要作,在调试应用程序时,你可能但愿了解一下框架的内容。若是咱们查询 express
文档,你会注意到你能够在你的命令前面加上 DEBUG=express:*
这样一行代码:
DEBUG=express:* node index.js
复制代码
若是你使用如今的应用程序运行这个命令,你将看到许多其余输出,可帮助你调试问题。
若是你没有启用调试日志记录,则不会看到任何这样的日志。这是经过调用一个叫作 debug
的包来完成的。它容许咱们在“命名空间”下编写消息,若是库的用户包含命名空间或者在其 DEBUG
环境变量 中匹配它的通配符,它将输出这些。使用 debug
库,首先要先安装它:
npm install debug
复制代码
让咱们经过建立一个模拟咱们的库调用的新文件 random-id.js
来尝试它,并在里面写上这样的代码:
const debug = require('debug');
const log = debug('mylib:randomid');
log('Library loaded');
function getRandomId() {
log('Computing random ID');
const outcome = Math.random()
.toString(36)
.substr(2);
log('Random ID is "%s"', outcome);
return outcome;
}
module.exports = { getRandomId };
复制代码
这里会建立一个带有命名空间 mylib:randomid
的 debug
记录器,而后会将两种消息记录上去。而后咱们在前一节的 index.js
文件中使用它:
const express = require('express');
const pino = require('pino');
const expressPino = require('express-pino-logger');
const randomId = require('./random-id');
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
const expressLogger = expressPino({ logger });
const PORT = process.env.PORT || 3000;
const app = express();
app.use(expressLogger);
app.get('/', (req, res) => {
logger.debug('Calling res.send');
const id = randomId.getRandomId();
res.send(`Hello World [${id}]`);
});
app.listen(PORT, () => {
logger.info('Server running on port %d', PORT);
});
复制代码
若是你此次使用 DEBUG=mylib:randomid node index.js
来从新启动服务器,它会打印咱们“库”的调式日志。
有意思的是,若是使用你的库的用户想把这些调试信息方法到本身的 pino
日志中去,他们可使用一个由 pino
团队出的一个叫作 pino-debug
库来正确的格式化这些日志。
使用下面的命令来安装这个库:
npm install pino-debug
复制代码
pino-debug
在咱们第一次使用以前须要初始化一次 debug
。最简单的方法是在启动脚本以前使用 Node.js 的 -r
或 --require
标识符 来初始化。使用下面的命令来重启你的服务器(假设你已经安装了 pino-colada
):
DEBUG=mylib:randomid node -r pino-debug index.js | ./node_modules/.bin/pino-colada
复制代码
你如今就能够用和应用程序日志相同的格式来查看库的的调试日志。
我将在这篇博文中介绍的最后一个案例是针对 CLI 而不是库去进行日志记录的特殊状况。个人理念是将逻辑日志和你的 CLI 输出 “logs” 分开。对于任何逻辑日志,你应该使用相似 debug
的库。这样你或其余人就能够从新使用逻辑,而不受 CLI 的特定用例约束。
当你使用 Node.js 构建 CLI 时,你可能但愿经过特定的视觉吸引力方式来添加颜色、旋转器或格式化内容来使事物看起来很漂亮。可是,在构建 CLI 时,应该记住几种状况。
一种状况是,你的 CLI 可能在持续继承(CI)系统的上下文中使用,所以你可能但愿删除颜色或任何花哨的装饰输出。一些 CI 系统设置了一个称为 “CI” 的环境标志。若是你想更安全的检查本身是否在 CI 中,可使用已经支持多个 CI 系统的包,例如is-ci
。
有些库例如 chalk
已经为你检测了 CI 并帮你删除颜色。让咱们来看看这是什么样子。
使用 npm install chalk
来安装 chalk
,并建立一个叫作 cli.js
的文件。将下面的内容放在里面:
const chalk = require('chalk');
console.log('%s Hi there', chalk.cyan('INFO'));
复制代码
如今,若是你使用 node cli.js
运行这个脚本,你将会看到对应的颜色输出。
可是你使用 CI=true node cli.js
来运行它,你会看到颜色被删除了:
你要记住另一个场景就是 stdout
可否在终端模式下运行。意思是将内容写入终端。若是是这种状况,咱们可使用相似 boxen
的东西来显示全部漂亮的输出。若是不是,则可能会将输出重定向到文件或传输到其余地方。
你能够检查 isTTY
相应的流属性来检查 stdin
、stdout
或 stderr
是否处于终端模式。例如:process.stdout.isTTY
. 在这种状况下特别用于终端,TTY
表明“电传打字机”。
根据 Node.js 进程的启动方式,三个流中的每一个流的值可能不一样。你能够在 Node.js 文档的“进程 I/O” 部分了解到更多关于它的信息。
让咱们看看 process.stdout.isTTY
在不一样状况下价值的变化状况。更新你的 cli.js
文件以检查它:
const chalk = require('chalk');
console.log(process.stdout.isTTY);
console.log('%s Hi there', chalk.cyan('INFO'));
复制代码
而后使用 node cli.js
在你的终端你面运行,你会看到 true
打印后会跟着咱们的彩色消息。
以后运行相同的东西,但将输出重定向到一个文件,而后经过运行检查内容:
node cli.js > output.log
cat output.log
复制代码
此次你会看到它会打印 undefined
后面跟着一个简单的无色消息。由于 stdout
关闭了终端模式下 stdout
的重定向。由于 chalk
使用了 supports-color
,因此在引擎盖下会检查各个流上的 isTTY
。
可是,像 chalk
这样的工具已经为你处理了这种行为,当你开发 CLI 时,你应该始终注意你的 CLI 可能在 CI 模式下运行或输出被重定向的状况。它也能够帮助你把你的 CLI 的经验更进一步。例如,你能够在终端以一种漂亮的方式排列数据,若是 isTTY
是 undefined
的话,则切换到更容易解析的方式。
开始使用 JavaScript 并使用 console.log
记录你的第一行是很快的,可是当你将代码带到生产环境时,你应该考虑更多关于记录的内容。本文仅介绍各类方法和可用的日志记录解决方案。它不包含你须要知道的一切。我建议你检查一些你最喜欢的开源项目,看看它们如何解决日志记录问题以及它们使用的工具。如今去记录全部的事情,不要打印你的日志😉
若是你知道或找到任何我应该明确说起的工具,或者若是你有任何问题,请随时联系我。我等不及想看看你作了什么。
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。