原文地址:https://www.twilio.com/blog/g...原作者:DOMINIKKUNDEL翻译作者:icepy翻译来源:https://github.com/lightningm。..当你开始使用JavaScript进行开发时,你可能学到的第一件事就是如何使用console.log将内容打印到控制台。如果您搜索如何调试JavaScript,您会发现数百篇博客文章和StackOverflow文章都指向简单的console.log。由于这是一种常见的做法,我们甚至可以使用像no-console这样的规则来确保没有日志留在生产中。但是如果我们真的想记录这些信息怎么办?在这篇博文中,我们将介绍您想要记录信息的各种情况,Node.js中的console.log和console.error之间的区别是什么,以及如何在不弄乱用户控制台的情况下执行此操作库中的日志记录。console.log(`Let'sgo!`);TheoryFirst:Node.js的重要细节虽然你可以在浏览器和Node.js环境中使用console.log和console.error,但是在Node.js中你必须使用一个重要的要记住的事情。在index.js文件中写入如下代码,在Node.js环境下执行:console.log('Hellothere');console.error('再见');如图:虽然可以看到这两个输出听起来可能是一样的,但是系统实际处理起来是不一样的。如果您查看Node.js文档的控制台部分,您会发现console.log使用stdout打印,而console.error使用stderr。每个进程都有三个可以使用的默认流,它们是stdin、stdout和stderr。stdin可以处理进程的输入,例如按钮按下或重定向输出。stdout可用于处理进程输出。最后,stderr用于错误消息。如果你想知道stderr为什么存在以及什么时候使用它,你可以访问:WhentouseSTDERRinsteadofSTDOUT。简而言之,这允许我们使用重定向>和管道|运算符处理与应用程序的实际结果分开的错误和诊断信息。而>允许我们将命令的输出重定向到文件,而2>允许我们将stderr的输出重定向到文件。让我们看一个将Hellothere的输出重定向到hello.log并将Byebye的输出重定向到error.log的示例。:$nodeindex.js>hello.log2>error.log如图:WhenDoYouWanttoLog?既然我们已经了解了日志记录的基本技术,那么让我们来谈谈您可能想要记录的东西不同示例的不同示例,通常这些示例都属于以下类别之一:在开发阶段快速调试意外行为DebugLogCLI进度输出我们将跳过这篇博文的前两篇文章,重点关注三篇基于Node.js的文章。您的服务器应用程序日志您可能出于各种原因想要在您的服务器上记录一些东西,例如:记录传入请求、统计信息、有多少404用户正在访问,以及您想知道什么时候出错以及为什么出错。初始化项目:$npminit-y$npminstallexpress让我们用中间件设置一个服务器,只为你的请求打印console.log:constexpress=require("express");常量端口=进程。环境端口||3000;constapp=express();app.use((req,res,next)=>{console.log('%o',req);next();});app.get('/',(req,res)=>{res.send('helloworld');});app.listen(PORT,()=>{console.log('服务器运行在端口%d',PORT);});这里我们使用console.log('%o',req);记录整个对象。当您运行nodeindex.js并访问http://localhost:3000时,您会注意到打印的很多信息不是我们需要的。如果我们将其更改为console.log('%s',req)我们也不会获得太多信息。我们可以编写自己的日志记录函数来只打印我们关心的信息。但是让我们退后一步,谈谈我们通常关心的事情。虽然这些信息通常是我们关注的焦点,但实际上我们可能需要其他信息,例如:时间戳-知道什么时候发生了计算机/服务器名称-如果您正在运行分布式系统,则为进程ID-如果您正在使用pm2多节点.js处理正在运行的消息-实际的消息堆栈跟踪可能有一些额外的变量或信息此外,由于我们知道打印将在stdout和stderr上结束,我们可能需要不同的日志级别记录和过滤它的能力。我们可以通过访问流程的各个部分并编写一堆JavaScript代码来获取上述信息,但是npm生态系统已经为我们提供了各种库来使用,例如:pinowinstonroarrbunyan我个人喜欢pino,因为它速度快,生态完整。那么,让我们来看看pino是如何帮助我们进行日志记录的。$npminstallpinoexpress-pino-loggerconstexpress=require("express");constpino=require("pino");constexpressPino=require("express-pino-logger");constlogger=pino({level:process.env.LOG_LEVEL||'info'});constexpressLogger=expressPino({logger});constPORT=process.env.PORT||3000;constapp=express();app.use(expressLogger);app.get('/',(req,res)=>{logger.debug('调用res.send')res.send('helloworld');});app.listen(PORT,()=>{logger.info('服务器在端口%d上运行',PORT);});运行nodeindex.js并访问http://localhost:3000你可以看到一行一行的JSON输出:如果你检查这个JSON,你会看到前面提到的时间戳。您可能还会注意到我们的logger.debug语句没有被打印出来,那是因为我们必须更改默认日志级别以使其可见,尝试LOG_LEVEL=debugnodeindex.js来调整日志级别。在此之前,我们需要解决日志信息的可读性问题。pino遵循一个理念,就是为了性能,应该把输出处理通过pipeline移到一个单独的进程中。可以查看文档了解pino为什么错误没有写到stderr。让我们使用pino-pretty工具来查看更具可读性的日志:$npminstall--save-devpino-pretty$LOG_LEVEL=debugnodeindex.js|./node_modules/.bin/pino-prettyrunLOG_LEVEL=调试节点索引.js|./node_modules/.bin/pino-pretty并访问http://localhost:3000。如图:还有各种库可以美化你的日志,你甚至可以用pino-colada用emojis来展示。这些对于您的本地开发很有用,在运行到生产服务器后,您可能希望将日志通过管道传输到另一个管道,使用>将它们写入磁盘以便稍后处理。例如:$LOG_LEVEL=debugnodeindex.js|./node_modules/.bin/pino-漂亮|>success.log2>s_error.logYourLibraryLogs既然我们已经研究了如何有效地为服务器应用程序编写日志,为什么不在我们的一些库中使用它呢?问题是,您的库可能希望出于调试目的记录内容,但实际上不应该使消费者应用程序混乱。相反,如果消费者需要调试某些东西,他们应该能够开始记录日志。默认情况下,您的库不处理此问题,并将输入和输出操作留给用户。快递就是一个很好的例子。在express框架下发生了很多事情,当你调试你的应用程序时,你可能想看看这个框架做了什么。如果我们查阅文档,您会注意到您可以在命令行前加上DEBUG=express:*以启用它。$DEBUG=express:*nodeindex.js如图:如果不启用调试日志,则看不到任何此类日志输出。这是通过一个名为debug的包完成的。$npminstalldebug让我们创建一个新文件random-id.js来使用它:constdebug=require("debug");constlog=debug("mylib:randomid");日志(“图书馆加载”);functiongetRandomId(){log('计算随机ID');常量结果=Math.random().toString(36).substr(2);日志('随机ID是“%s”',结果);返回结果;}module.exports={getRandomId};这里将创建一个命名空间为mylib:randomid的调试记录器,然后记录这两种消息。我们可以在index.js文件中引用它:constexpress=require("express");constpino=require("pino");constexpressPino=require("express-pino-logger");constrandomId=require("./random-id");constlogger=pino({level:process.env.LOG_LEVEL||'info'});constexpressLogger=expressPino({logger});constPORT=process.环境端口||3000;constapp=express();app.use(expressLogger);app.get('/',(req,res)=>{logger.debug('调用res.send')constid=randomId.getRandomId();res.send(`helloworld[${id}]`);});app.listen(PORT,()=>{logger.info('服务器运行在端口%d',PORT);});然后使用DEBUG=mylib:randomidnodeindex.js重新运行你的index.js文件,如图:有趣的是,如果你的库用户想要将这些调试信息集成到他们的pino日志中,那么他们可以使用一个名为pino-debug的库来正确格式化这些日志。$npminstallpino-debugpino-debug在我们第一次使用之前需要初始化debug。最简单的方法是在启动前使用Node.js的-r或--require命令进行初始化。$DEBUG=mylib:randomidnode-rpino-debugindex.js|./node_modules/.bin/pino-colada如图:YourCLIOutput我将介绍本博文的最后一个案例,即为CLI进行日志记录。我的理念是将逻辑日志与CLI输出分开。对于任何逻辑日志记录,您应该使用像调试这样的包。这样您或其他人就可以重写逻辑而不受CLI的约束。一种情况是你的CLI在持续集成系统中使用,因此你可能想要删除各种花里胡哨的东西。一些CI系统设置了一个名为CI的环境标志。如果你想更安全地检查你是否在CI系统中,你可以使用is-ci库。一些像chalk这样的库已经为你检查了CI并为你删除了颜色。$npminstallchalkconstchalk=require("chalk");console.log('%s你好',chalk.cyan('INFO'));runnodecli.js,如图:当你运行CI=truenodecli.js时,如图:你要记住的是stdout是否可以在另一个场景下以终端模式运行。如果是这种情况,我们可以使用类似boxen的东西来显示所有漂移的输出。但如果不是,输出可能会被重定向到一个文件或输出到别处。您可以使用isTTY检查stdout、stdin、stderr是否处于终端模式。例如:process.stdout.isTTY根据Node.js的启动方式不同,这三个值可能不同。您可以在文档中找到有关它的更多信息。让我们看看process.stdout.isTTY在不同情况下是如何变化的:constchalk=require("chalk");console.log(process.stdout.isTTY);console.log('%s你好',chalk.cyan('INFO'));然后运行nodeindex.js,如图所示:运行相同的内容后,但是将它的输出重定向到一个文件,这次你会看到它会打印一个undefined紧跟着一个简单的Colorless消息。这是因为stdout在终端模式下关闭了stdout的重定向。chalk使用supports-color,它检查引擎中每个流的isTTY。像chalk这样的库已经为您处理了这些行为,但是在开发CLI、以CI模式运行或重定向输出时,您仍然必须小心。例如,您可以在终端中以漂亮的方式排列数据,如果isTTY未定义,则切换到更易于解析的方式。总结在JavaScript中使用console.log非常快,但是当您将代码部署到生产环境时,您应该更多地考虑日志记录。本文只是对可用的各种方法和日志记录解决方案的介绍,并未涵盖您需要了解的所有内容。所以我建议你看看你最喜欢的开源项目,看看他们是如何解决日志记录问题的,以及他们使用的工具。
