當你開始用 JavaScript 進行開發(fā)時,可能學到的第一件事就是如何用 console.log
將內(nèi)容記錄到控制臺。如果你去搜索如何調(diào)試 JavaScript,會發(fā)現(xiàn)數(shù)百篇博文和 StackOverflow 文章都會簡單的告訴你用 console.log
。因為這是一種很常見的做法,我們甚至會在代碼中使用像 no-console
這樣的 linter 規(guī)則來確保不會留下意外的日志信息。但是如果我們真的想要去記錄某些內(nèi)容呢?
在本文中,我們將梳理各種情況下要記錄的日志信息,Node.js 中 console.log
和console.error
之間的區(qū)別是什么,以及如何在不發(fā)生混亂的情況下把你庫中的日志記錄輸出到用戶控制臺。
【視頻教程推薦:nodejs視頻教程 】
console.log(`Let's go!`);理論先行:Node.js 的重要細節(jié)
雖然你可以在瀏覽器和 Node.js 中使用 console.log
或 console.error
,但在使用 Node.js 時要記住一件重要的事。當你在 Node.js 中將以下代碼寫入名為 index.js
的文件中時:
console.log('Hello there'); console.error('Bye bye');
并用 node index.js
在終端中執(zhí)行它,你會直接看到兩者的輸出:
雖然它們看起來可能一樣,但實際上系統(tǒng)對它們的處理方式是不同的。如果你查閱 Node.js 文檔的
console
部分,會看到 console.log
是輸出到 stdout
而 console .error
用的是 stderr
。
每個進程都有三個可用的默認 stream
。那些是 stdin
,stdout
和 stderr
。 stdin
流用來在處理進程的輸入。例如按下按鈕或重定向輸出。 stdout
流用于程序的輸出。最后 stderr
用于錯誤消息。如果你想了解為什么會有 stderr
存在,以及應(yīng)該在什么時候使用它,可以查看這篇文章。
簡而言之,這允許我們在 shell 中使用重定向(>
)和管道(|
)來處理錯誤和診斷信息,它們是與程序的實際輸出結(jié)果是分開的。雖然 >
允許我們將命令的輸出重定向到文件中,但是 2>
允許我們將 stderr
的輸出重定向到文件中。例如,下面這個命令會將 “Hello there” 傳給一個名為 hello.log
的文件并把 “Bye bye” 傳到一個名為 error.log
的文件中。
node index.js > hello.log 2> error.log應(yīng)該在什么時候記錄日志?
現(xiàn)在我們已經(jīng)了解了日志記錄的底層技術(shù),接下來讓我們談?wù)剳?yīng)該在什么情況下記錄日志內(nèi)容。通常應(yīng)該是以下情況之一:
在開發(fā)過程中快速調(diào)試意外行為基于瀏覽器的分析或診斷日志記錄記錄你服務(wù)器上傳入的請求,以及所有可能發(fā)生的故障使用庫的日志調(diào)試選項來幫助用戶解決問題在 CLI 輸出進度、確認消息或錯誤信息我們將跳過前兩種情況,并重點介紹基于 Node.js 的后三點。
服務(wù)器程序日志可能你在服務(wù)器上記錄日志的原因有多種。例如記錄傳入的請求并允許你從中提取諸如統(tǒng)計信息之類的內(nèi)容,比如有多少用戶在點擊時發(fā)生了 404 錯誤,或者用戶瀏覽器的 User-Agent
。你也想知道在什么時候因為什么出錯了。
如果你想編碼嘗試下面的內(nèi)容,請先創(chuàng)建一個新的項目目錄。在目錄中創(chuàng)建一個 index.js
并運行以下命令來初始化項目并安裝 express
:
npm init -y npm install express
讓我們設(shè)置一個帶有中間件的服務(wù)器,每個請求只需用 console.log
進行輸出。將以下內(nèi)容復(fù)制到 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 文檔中查閱它們的細節(jié)。
當你運行 node index.js
來啟動你的服務(wù)器并導航到 http://localhost:3000 時,會發(fā)現(xiàn)它會打印出很多我們確實需要但不知道的信息。
如果將其更改為 console.log('%s', req)
不打印整個對象,我們就不會獲得更多信息。
![終端中輸出的 "[object Object]" 信息](https://s3.amazonaws.com/com....
可以通過編寫自己的日志函數(shù)只輸出我們關(guān)心的東西,但是先等等,談?wù)勎覀兺ǔjP(guān)心的東西。雖然這些信息經(jīng)常成為我們關(guān)注的焦點,但實際上可能還需要其他信息:
時間戳 - 知道事情何時發(fā)生計算機/服務(wù)器名稱 - 如果你運行的是分布式系統(tǒng)進程ID - 如果你用了pm2
來運行多個Node進程消息 - 包含某些內(nèi)容的實際消息可能會需要的其它變量或信息既然一切都會被轉(zhuǎn)到 stdout
和 stderr
,那么我們可能會想要不同的日志級別,還有配置和過濾日志的能力。
我們可以通過依賴 process
的各個部分并編寫一堆 JavaScript 來獲得所有這些,但關(guān)于 Node.js 的好消息是有 npm
這個生態(tài)系統(tǒng),里面已經(jīng)有了各種各樣的庫供我們使用。其中一些是:
pino
winston
roarr
bunyan
(請注意,這個已經(jīng) 2 年沒有更新了)我更喜歡pino
,因為它速度很快。接下來看看怎樣使用 pino
來幫助我們記錄日志。同時我們可以用 express-pino-logger
包來記錄請求。
安裝 pino
和 express-pino-logger
:
npm install pino express-pino-logger
用下面的代碼更新你的 index.js
文件以使用 logger 和中間件:
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); });
在這段代碼中,我們創(chuàng)建了一個 pino
的實例 logger
,并將其傳給 express-pino-logger
創(chuàng)建一個新的 logger中間件來調(diào)用 app.use
。另外,我們用 logger.info
替換了服務(wù)器啟動時的 console.log
,并在路由中添加了一個額外的 logger.debug
來顯示不同的日志級別。
再次運行 node index.js
重新啟動服務(wù)器,你會看到一個完全不同的輸出,它每一行打印一個 JSON。再次導航到 http://localhost:3000 ,你會看到添加了另一行JSON。
如果你檢查這些 JSON,將看到它包含所有前面所提到的信息,例如時間戳等。你可能還會注意到 logger.debug
語句沒有打印出來。那是因為我們必須修改默認日志級別才能看到。當我們創(chuàng)建 logger
實例時,將值設(shè)置為 process.env.LOG_LEVEL
,這意味著我們可以通過它修改值,或接受默認的 info
。通過執(zhí)行 LOG_LEVEL = debug node index.js
,就可以調(diào)整日志級別。
在這之前要先解決一個問題,即現(xiàn)在的輸出不適合人類閱讀。pino
遵循一種理念,為了提高性能,你應(yīng)該通過管道(使用 |
)將輸出的任何處理移動到一個單獨的進程中。這包括使其可讀或?qū)⑵渖蟼鞯皆浦鳈C。這些被稱為 transports
??梢酝ㄟ^查看 transports
文檔了解為什么 pino
中的錯誤不會寫入 stderr
。
讓我們用工具 pino-pretty
來查看更易閱讀的日志版本。在你的終端中運行:
npm install --save-dev pino-pretty LOG_LEVEL=debug node index.js | ./node_modules/.bin/pino-pretty
現(xiàn)在所有的日志都被用 |
運算符輸入給 pino-pretty
命令,你的輸出應(yīng)該會經(jīng)過美化,并且還會包含一些關(guān)鍵信息,而且應(yīng)該是彩色的。如果再次請求 http://localhost:3000 ,你還應(yīng)該看到debug
消息。
有各種各樣的 transports 來美化或轉(zhuǎn)換你的日志。你甚至可以用 pino-colada
顯示 emoji。這些對你的本地開發(fā)很有用。在生產(chǎn)中運行服務(wù)器之后,你可能希望將日志傳輸?shù)搅硪粋€ transports,再用 >
或者用像 tee
) 這樣的命令將它們寫入磁盤以便稍后處理。
這個文檔 中還將包含有關(guān)輪換日志文件、過濾和把日志寫入不同文件等內(nèi)容的信息。
庫的日志現(xiàn)在討論一下怎樣有效地為我們的服務(wù)器程序編寫日志,為什么不對我們的庫使用相同的技術(shù)呢?
問題是你的庫可能希望通過記錄日志來進行調(diào)試,但是不應(yīng)該與使用者的程序相混淆。如果需要調(diào)試某些內(nèi)容,使用者應(yīng)該能夠啟用日志。默認情況下,你的庫應(yīng)該是靜默的,并將是否輸出日志的決策權(quán)留給用戶。
一個很好的例子是 express
。 express
的底層有很多東西,你可能想在調(diào)試自己的程序時偷看它。如果我們查閱
express
文檔,就會注意到你可以在自己的命令之前添加 DEBUG=express:*
,如下所示:
DEBUG=express:* node index.js
如果你運行這個命令,將看到許多其他的輸出,這些可幫助你調(diào)試程序中的問題。
如果你沒有啟用調(diào)試日志記錄,則不會看到任何此類日志。這是通過一個稱為 debug
的包來完成的。它允許我們在“命名空間”下編寫日志消息,如果庫的用戶包含該命名空間或在 DEBUG
環(huán)境變量 中匹配了它的通配符,就會輸出這些。要使用 debug
庫,首先要安裝它:
npm install debug
讓我們通過創(chuàng)建一個名為 random-id.js
的新文件來模擬我們的庫,并將以下代碼復(fù)制到其中:
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 };
這將創(chuàng)建一個帶有命名空間 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
重新運行我們的服務(wù)器,它會打印前面“庫”的調(diào)試日志。
如果你的庫的用戶想要將這個調(diào)試信息放到他們的 pino
日志中,他們可以用 pino
團隊開發(fā)的名為 pino-debug
的庫來正確的格式化這些日志。
用以下命令安裝庫:
npm install pino-debug
在我們第一次使用debug
之前,需要初始化pino-debug
。最簡單的方法是在啟動 javascript 腳本的命令之前使用 Node.js 的 -r
或 --require
標志來 require 模塊。使用如下命令重新運行你的服務(wù)器(假設(shè)你安裝了pino-colada
):
DEBUG=mylib:randomid node -r pino-debug index.js | ./node_modules/.bin/pino-colada
你現(xiàn)在將用與程序日志相同的格式查看庫的調(diào)試日志。
CLI 輸出本文介紹的最后一個案例是針對 CLI 進行日志記錄的特殊情況。我的理念是將“邏輯日志”與 CLI 的輸出 “日志” 分離。對于所有的邏輯日志,你應(yīng)該用像 debug
這樣的庫。這樣你或其他人就可以重新使用該邏輯,而不受 CLI 的特定用例的約束。
當你用 Node.js 構(gòu)建 CLI 時,可能希望添加一些看上去很漂亮顏色,或者用有視覺吸引力的方式格式化信息。但是,在構(gòu)建 CLI 時,應(yīng)該記住以下這幾種情況。
一種情況是你的 CLI 可能會在持續(xù)集成(CI)系統(tǒng)的上下文中使用,因此你可能希望刪除顏色和花哨的裝飾輸出。一些 CI 系統(tǒng)設(shè)置了一個名為 CI
的環(huán)境標志。如果你想更安全地檢查自己是否在 CI 中,那就是使用像 is-ci
這樣的包去支持一堆 CI 系統(tǒng)。
像 chalk
這樣的庫已經(jīng)為你檢測了CI 并為你刪除了顏色。我們來看看它的樣子。
使用 npm install chalk
安裝 chalk
并創(chuàng)建一個名為 cli.js
的文件。將以下內(nèi)容復(fù)制到其中:
const chalk = require('chalk'); console.log('%s Hi there', chalk.cyan('INFO'));
Now if you would run this script using node cli.js
you'll see colored output.
現(xiàn)在如果你用 node cli.js
運行這個腳本,將會看到彩色輸出。
但是如果你用 CI=true node cli.js
運行它,你會看到顏色被消除了:
你要記住的另一個場景是 stdout
是否以終端模式運行,也就是將內(nèi)容寫入終端。如果是這種情況,我們可以使用 boxen
之類的東西顯示所有漂亮的輸出。如果不是,則可能會將輸出重定向到文件或用管道傳輸?shù)侥程帯?/p>
你可以通過檢查相應(yīng)流上的 isTTY
屬性來檢查 stdin
、stdout
或 stderr
是否處于終端模式。例如:process.stdout.isTTY
。 TTY
的意思是 “電傳打印機(teletypewriter)”,在這種情況下專門用于終端。
根據(jù) Node.js 進程的啟動方式,這三個流每個流的值可能不同。你可以在 Node.js 文檔的"process 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
被著色了。
之后運行相同的內(nèi)容,但是將輸出重定向到一個文件,然后檢查內(nèi)容:
node cli.js > output.log cat output.log
你會看到這次它打印了 undefined
后面跟著一個簡單的無色消息,因為 stdout
的重定向關(guān)閉了它的終端模式。因為 chalk
用了 supports-color
,它們會在相應(yīng)的流上檢查 isTTY
。
像 chalk
這樣的工具已經(jīng)為你處理了這種行為,但是在開發(fā) CLI 時,你應(yīng)該始終了解 CLI 可能在 CI 模式下運行或重定向輸出的情況。它還可以幫助你進一步獲得 CLI 的體驗。例如你可以在終端中以漂亮的方式排列數(shù)據(jù),如果isTTY
是 undefined
,你可以切換到更容易解析的方式。
剛開始用 JavaScript 開發(fā)時用 console.log
記錄你的第一行日志確實很快,但是當你將代碼投入生產(chǎn)環(huán)境時,應(yīng)該考慮更多關(guān)于日志記錄的內(nèi)容。本文純粹是對各種方式和可用的日志記錄解決方案的介紹。我建議你去看一些自己喜歡的開源項目,看看它們是怎樣解決日志記錄問題的,還有它們所用到的工具。
如果你知道或找到了我沒有提及的工具,或者有什么疑問,請留言。
原文地址: https://www.twilio.com/blog/guide-node-js-logging
相關(guān)推薦:node js教程
標題名稱:深入研究Node.js中的日志信息
鏈接分享:http://weahome.cn/article/cgepph.html