【译】Node.js 日志打印指南
?
當(dāng)你開始使用JavaScript開發(fā)時(shí),可能要學(xué)習(xí)的第一個(gè)技能就是如何使用console.log將內(nèi)容打印到控制臺(tái)。如果你搜索如何調(diào)試JavaScript,將會(huì)發(fā)現(xiàn)數(shù)百篇博客和StackOverflow文章指向console.log。因?yàn)檫@是一種很常見的方法,我們甚至開始使用像no-console這樣的linter規(guī)則來確保我們不會(huì)在生產(chǎn)代碼中留下意外的日志語句。但是如果我們真的想通過打印一些東西來提供更多的信息呢?
?
在這篇文章中,我們將探討各種需要打印信息的場(chǎng)景;Node.js中console.log和console.error之間有什么區(qū)別;以及如何在不擾亂用戶控制臺(tái)的情況下在庫中記錄日志。
console.log(`Let's go!`); 復(fù)制代碼首要理論:Node.js的重要細(xì)節(jié)
假如我們能在瀏覽器或者Node.js中使用console.log或console.error,那么在使用Node.js時(shí),有一件重要的事情是要記住的。在一個(gè)名為index.js的文件中使用Node.js編寫以下代碼時(shí):
console.log('Hello there'); console.error('Bye bye'); 復(fù)制代碼并在終端使用node index.js執(zhí)行它,你會(huì)看到它們并排輸出了:
?
?
?
然而,盡管這兩者看起來相同,但實(shí)際上系統(tǒng)對(duì)它們的處理是不同的。查看Node.js文檔關(guān)于console的介紹,我們可以看到,console.log輸出到stdout,console.error輸出到stderr。
每個(gè)進(jìn)程默認(rèn)有三個(gè)可以使用的流:stdin, stdout和stderr。stdin流處理進(jìn)入程序的輸入。例如,按鈕按下或重定向輸出(我們稍后會(huì)講到)。stdout流用于應(yīng)用程序的輸出。最后,stderr用于錯(cuò)誤消息。如果你想了解為什么存在stderr以及何時(shí)使用它,請(qǐng)參閱這篇文章。
簡(jiǎn)而言之,我們可以使用重定向(>)和管道(|)操作符將應(yīng)用程序的錯(cuò)誤和診斷信息結(jié)果分離顯示。>操作符允許我們將stdout的輸出重定向到文件中,而2>允許我們將stderr的輸出重定向到文件中。例如,這個(gè)命令將“Hello there”導(dǎo)入一個(gè)名為Hello.log的文件,并將“Bye Bye”導(dǎo)入一個(gè)名為error.log的文件。
node index.js > hello.log 2> error.log 復(fù)制代碼?
?
?
什么情況下我們需要log?
既然我們已經(jīng)了解了一些關(guān)于日志的底層技術(shù)方面的知識(shí),那么接下來讓我們來討論一下可能需要記錄日志的場(chǎng)景。通常這些場(chǎng)景包含以下類別:
- 開發(fā)過程中快速調(diào)試異常
- 基于瀏覽器的日志記錄,用于分析或診斷
- 記錄服務(wù)應(yīng)用日志,以記錄傳入的請(qǐng)求以及可能發(fā)生的任何故障
- 庫的可選調(diào)試日志,以協(xié)助用戶排查問題
- 通過CLI打印進(jìn)度、確認(rèn)信息或錯(cuò)誤
下面的內(nèi)容我們將跳過的前兩個(gè)場(chǎng)景,重點(diǎn)介紹跟Node.js有關(guān)的后面三個(gè)場(chǎng)景。
服務(wù)應(yīng)用日志
在服務(wù)器上記錄日志的原因可能有很多。例如,通過記錄傳入的請(qǐng)求我們可以用來做信息統(tǒng)計(jì),比如用戶遇到有多少404請(qǐng)求,這些請(qǐng)求可能是什么,或者正在使用什么User-Agent。我們也想知道什么時(shí)候出了問題,原因是什么。
如果你想嘗試本文下面內(nèi)容,請(qǐng)創(chuàng)建一個(gè)新的項(xiàng)目目錄。在項(xiàng)目目錄中創(chuàng)建index.js用于編寫代碼的程序運(yùn)行入口。運(yùn)行以下代碼初始化項(xiàng)目并安裝express:
npm init -y npm install express 復(fù)制代碼讓我們?cè)O(shè)置一個(gè)帶有console.log的中間件的服務(wù)器。將以下內(nèi)容放入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); }); 復(fù)制代碼我們使用console.log('$0',req)用于記錄整個(gè)對(duì)象。console.log底層使用util.format方法支持%O占位符。詳細(xì)信息可以在Node.js官方文檔中了解。
當(dāng)執(zhí)行node index.js來執(zhí)行服務(wù)器并訪問http://localhost:3000時(shí),你會(huì)注意到它將打印出許多我們并不真正需要的信息。
?
?
?
即使我們將其更改為console.log('%s', req)不打印整個(gè)對(duì)象,也不會(huì)得到太多有用的信息。
?
?
?
我們可以寫我們自己的log函數(shù),只輸出我們關(guān)心的東西。但是在此之前,我們先討論下通常需要關(guān)心什么。雖然太多信息分散我們注意力的集中,但實(shí)際上我們也需要充分的信息。如:
- 時(shí)間戳——知道事情發(fā)生的時(shí)間
- 計(jì)算機(jī)/服務(wù)器名稱——如果你正在運(yùn)行一個(gè)分布式系統(tǒng)
- 進(jìn)程ID——如果你正在使用類似pm2的東西運(yùn)行多個(gè)節(jié)點(diǎn)進(jìn)程
- 消息——包含一些內(nèi)容的實(shí)際消息
- 堆棧跟蹤——用于記錄錯(cuò)誤的場(chǎng)景
- 其他一些額外的變量/信息
此外,既然我們已經(jīng)知道所有內(nèi)容都將進(jìn)入stdout和stderr流,那么我們可以借助它們實(shí)現(xiàn)不同級(jí)別的日志,以及根據(jù)它們配置和篩選日志的能力。
我們可以通過訪問進(jìn)程的各個(gè)部分并編寫一堆JavaScript來實(shí)現(xiàn)所有這些功能,但Node.js最棒的一點(diǎn)是,擁有npm生態(tài)系統(tǒng),而且已經(jīng)有各種庫可供我們使用。例如:
- pino
- winston
- roarr
- bunyan (注意:這個(gè)已經(jīng)兩年沒有更新了)
我個(gè)人喜歡pino,因?yàn)樗俣瓤?#xff0c;生態(tài)也很好。讓我們看看如何使用pino幫助我們進(jìn)行日志記錄。奇妙的是已經(jīng)有一個(gè)express-pino-logger包,我們可以使用它來記錄請(qǐng)求。
安裝pino和express-pino-logger:
npm install pino express-pino-logger 復(fù)制代碼然后更新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); }); 復(fù)制代碼在這個(gè)代碼片段中,我們創(chuàng)建了一個(gè)pino的日志程序?qū)嵗?#xff0c;并將其傳遞到express-pino-logger中來創(chuàng)建一個(gè)新的日志程序中間件以便app.use調(diào)用。此外,我們?cè)诜?wù)啟動(dòng)的時(shí)候用logger.info替換console.log,并向路由添加了一個(gè)額外的logger.debug,以顯示不同級(jí)別的日志。
如果通過再次運(yùn)行node index.js啟動(dòng)服務(wù)器。你會(huì)看到一個(gè)非常不同的輸出,它每一行輸出一個(gè)JSON。再次訪問http://localhost:3000,你將看到添加了一行新的JSON。
?
?
?
如果檢查這個(gè)JSON,你將看到它包含前面提到的所有信息,比如時(shí)間戳。你還可能注意到我們的logger.debug語句沒有打印出來。這是因?yàn)槲覀兪褂昧四J(rèn)的日志級(jí)別。創(chuàng)建logger實(shí)例時(shí),我們通過設(shè)置process.env.LOG_LEVEL的值改變?nèi)罩炯?jí)別,默認(rèn)值為info。通過運(yùn)行LOG_LEVEL=debug node index.js,我們可以調(diào)整日志級(jí)別顯示debug類型的日志。
在此之前,讓我們先討論這樣一個(gè)事實(shí):現(xiàn)在的輸出實(shí)際上可讀性很差。然而這是故意的。pino遵循一種原則,即更高的性能。我們也可以通過管道(使用|)將所有進(jìn)程的日志移動(dòng)到一個(gè)單獨(dú)的進(jìn)程中,用于提高其可讀性或?qū)?shù)據(jù)上載到云服務(wù)器。這個(gè)過程叫做transports。查看關(guān)于transports的文檔,還可以了解為什么pino中的錯(cuò)誤沒有被寫入stderr。
讓我們使用工具pino-pretty查看更具可讀性的日志版本。在終端執(zhí)行一下命令:
npm install --save-dev pino-pretty LOG_LEVEL=debug node index.js | ./node_modules/.bin/pino-pretty 復(fù)制代碼現(xiàn)在,使用|操作符,所有的日志將通過管道傳輸?shù)絧ino-pretty,你的輸出應(yīng)該變得清晰,包含了關(guān)鍵信息并且被著色。再次訪問http://localhost:3000,還應(yīng)該能看到debug級(jí)別的消息。
?
?
?
有許多現(xiàn)成的傳輸工具可以美化或轉(zhuǎn)換日志。你甚至可以用pino-colada工具使其支持表情符號(hào)的顯示。這些將對(duì)你本地的開發(fā)非常有用。在生產(chǎn)環(huán)境中運(yùn)行服務(wù)器之后,你可能希望將日志導(dǎo)入到另一個(gè)傳輸中,使用>將日志寫入磁盤,以便稍后處理它們,或者使用tee之類的命令進(jìn)行處理。
官方文檔還介紹關(guān)于日志文件歸檔、過濾和將日志寫入不同文件等內(nèi)容。
你的庫日志
既然我們已經(jīng)了解了如何為服務(wù)器應(yīng)用程序高效地編寫日志,為什么不為我們編寫的庫使用相同的技術(shù)呢?
問題是,我們希望打印出庫用于調(diào)試的內(nèi)容,但也不能混淆使用者的應(yīng)用程序。如果需要調(diào)試某些東西,使用者應(yīng)該能夠啟用日志。你的庫在默認(rèn)情況下應(yīng)該是靜默的,并將是否打印日志留給使用者決定。
express就是一個(gè)很好的例子。express的底層做了很多事情,在調(diào)試應(yīng)用程序時(shí),你可能想了解一下底層的情況。如果我們查閱express文檔,便會(huì)注意到啟動(dòng)相關(guān)日志只需要在命令前加上DEBUG=express:*:
DEBUG=express:* node index.js 復(fù)制代碼使用現(xiàn)有的應(yīng)用程序運(yùn)行該命令,你將看到許多額外的輸出,這些輸出將幫助你調(diào)試問題。
?
?
?
如果沒有啟用調(diào)試日志記錄,就不會(huì)看到這些。這是是通過一個(gè)名為debug的包來實(shí)現(xiàn)的。它允許我們?cè)谥付ā懊Q空間”下編寫消息,如果庫的使用者在調(diào)試環(huán)境變量中包含與之匹配的名稱空間或通配符,它將輸出這些消息。
要使用debug庫,首先安裝它:
npm install debug 復(fù)制代碼讓我們通過創(chuàng)建一個(gè)新文件來嘗試它,該文件將模擬我們的庫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 }; 復(fù)制代碼以上代碼創(chuàng)建一個(gè)名稱空間為mylib:randomid的新調(diào)試日志實(shí)例,其打印了兩條消息。讓我們?cè)谏弦徽碌膇ndex.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); }); 復(fù)制代碼重新運(yùn)行服務(wù)器,但是這次使用DEBUG=mylib:randomid node index.js,它將打印我們的“庫”的調(diào)試日志。
?
?
?
有趣的是,如果你的庫使用者希望將此調(diào)試信息放入他們的pino日志中,他們可以使用pino團(tuán)隊(duì)提供的pino-debug庫來正確格式化這些日志。
使用以下方法安裝庫:
npm install pino-debug 復(fù)制代碼在第一次使用debug之前,需要初始化pino-debug。最簡(jiǎn)單的方法是在啟動(dòng)腳本之前使用Node.js的-r或——require標(biāo)志來引入模塊。使用如下命令重新運(yùn)行服務(wù)器(假設(shè)已經(jīng)安裝了pino-colada):
DEBUG=mylib:randomid node -r pino-debug index.js | ./node_modules/.bin/pino-colada 復(fù)制代碼現(xiàn)在,你將看到庫的調(diào)試日志與應(yīng)用程序日志的格式相同。
?
?
?
CLI(命令行界面)輸出
在這篇文章中,我們將討論的最后一種情況是CLIs而不是庫的特殊日志記錄情況。我的原則是將邏輯日志與CLI輸出的“日志”分開。對(duì)于任何邏輯日志,都應(yīng)該使用debug之類的庫。這樣,你或其他人就可以重用邏輯,而不受CLI特定用例的限制。
當(dāng)你的Node.js應(yīng)用采用CLI構(gòu)建時(shí),你可能希望通過添加顏色、標(biāo)記或以一種特定的具有視覺吸引力的方式格式化內(nèi)容,使其看起來更漂亮。然而,在使用CLI構(gòu)建時(shí),你應(yīng)該記住以下幾個(gè)場(chǎng)景。
一種場(chǎng)景是,你的CLI可能在持續(xù)集成(CI)系統(tǒng)的上下文中使用,因此你可能希望刪除顏色或任何花哨的裝飾輸出。一些CI系統(tǒng)設(shè)置了一個(gè)稱為CI的環(huán)境標(biāo)志。如果你想更安全地檢查你是否在CI中,可以使用is-CI這樣的包,它已經(jīng)支持許多CI系統(tǒng)。
一些庫,如chalk,已經(jīng)為你檢測(cè)是否CI環(huán)境并為你刪除顏色。接下來讓我們一起看下使用它之后的樣子。
使用npm安裝chalk并創(chuàng)建一個(gè)名為clip .js的文件。放入以下代碼:
const chalk = require('chalk'); console.log('%s Hi there',chalk.cyan('INFO')); 復(fù)制代碼現(xiàn)在,如果你使用node clip.js運(yùn)行這個(gè)腳本,你將看到彩色的輸出。
?
?
?
但是如果你用CI=true node clip .js運(yùn)行它,你會(huì)看到顏色被抑制了:
?
?
?
你要記住的另一個(gè)場(chǎng)景是,如果你的stdout運(yùn)行在終端模式中,表示內(nèi)容寫入終端。如果是這種情況,我們可以使用boxen之類的東西來顯示所有漂亮的輸出。如果不是,很可能輸出被重定向到文件或管道的某個(gè)地方。
你可以通過檢查相應(yīng)流上的isTTY屬性來檢查stdin、stdout或stderr是否處于終端模式,例如:process.stdout.isTTY。TTY代表“teletypewriter(電傳打字機(jī))”,在本例中特指終端。
根據(jù)Node.js進(jìn)程的啟動(dòng)方式,這三個(gè)流的值可能有所不同。你可以在Node.js文檔的“process I/O”部分了解更多。
讓我們看看process.stdout的值。isTTY在不同的情況下是不同的。更新你的clil.js文件,以檢查它:
const chalk = require('chalk'); console.log (process.stdout.isTTY); console.log('%s Hi there',chalk.cyan('INFO')); 復(fù)制代碼現(xiàn)在在你的終端中運(yùn)行node clip.js,你會(huì)看到true后面跟著我們的彩色消息。
?
?
?
之后運(yùn)行相同的東西,但重定向輸出到一個(gè)文件,并檢查內(nèi)容后運(yùn)行:
node clip .js > output.log cat output.log 復(fù)制代碼你將看到,這一次它打印的是undefined,后面跟著一條純色的消息,因?yàn)閟tdout的重定向關(guān)閉了stdout的終端模式。chalk使用了support-color檢查相應(yīng)流上是否支持TTY。
?
?
?
類似chalk這樣的工具已經(jīng)為你處理了這種場(chǎng)景。但是,在開發(fā)CLI時(shí),你應(yīng)該始終了解CLI可能在CI模式下運(yùn)行或重定向輸出的情況。它還可以幫助您進(jìn)一步獲得CLI的體驗(yàn)。例如,你可以在終端中以漂亮的方式排列數(shù)據(jù),如果isTTY未定義,則切換到更容易解析的方式。
總結(jié)
開始使用JavaScript并使用console.log記錄第一行代碼非常快,但是當(dāng)你將代碼投入生產(chǎn)時(shí),你應(yīng)該考慮更多關(guān)于日志的內(nèi)容。這篇文章僅僅介紹了各種方法和可用的日志解決方案。但它不包含你需要知道的一切。我建議你查看一些你最感興趣的開源項(xiàng)目,了解它們?nèi)绾谓鉀Q日志記錄問題以及使用哪些工具。現(xiàn)在去記錄所有的信息,而不是僅僅打印日志吧😉。
原文:A Guide to Node.js Logging
?
總結(jié)
以上是生活随笔為你收集整理的【译】Node.js 日志打印指南的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue 使用axios
- 下一篇: FineReport数据执行官知识点