Node.js中日志信息的案例分析?這個問題可能是我們日常學習或工作經常見到的。希望通過這個問題能讓你收獲頗深。下面是小編給大家帶來的參考內容,讓我們一起來看看吧!
當你開始用 JavaScript 進行開發時,可能學到的第一件事就是如何用 console.log
將內容記錄到控制臺。如果你去搜索如何調試 JavaScript,會發現數百篇博文和 StackOverflow 文章都會簡單的告訴你用 console.log
。因為這是一種很常見的做法,我們甚至會在代碼中使用像 no-console
這樣的 linter 規則來確保不會留下意外的日志信息。但是如果我們真的想要去記錄某些內容呢?
在本文中,我們將梳理各種情況下要記錄的日志信息,Node.js 中 console.log
和console.error
之間的區別是什么,以及如何在不發生混亂的情況下把你庫中的日志記錄輸出到用戶控制臺。
console.log(`Let's go!`);
雖然你可以在瀏覽器和 Node.js 中使用 console.log
或 console.error
,但在使用 Node.js 時要記住一件重要的事。當你在 Node.js 中將以下代碼寫入名為 index.js
的文件中時:
console.log('Hello there'); console.error('Bye bye');
并用 node index.js
在終端中執行它,你會直接看到兩者的輸出:
雖然它們看起來可能一樣,但實際上系統對它們的處理方式是不同的。如果你查閱 Node.js 文檔的
console
部分,會看到 console.log
是輸出到 stdout
而 console .error
用的是 stderr
。
每個進程都有三個可用的默認 stream
。那些是 stdin
,stdout
和 stderr
。 stdin
流用來在處理進程的輸入。例如按下按鈕或重定向輸出。 stdout
流用于程序的輸出。最后 stderr
用于錯誤消息。如果你想了解為什么會有 stderr
存在,以及應該在什么時候使用它,可以查看這篇文章。
簡而言之,這允許我們在 shell 中使用重定向(>
)和管道(|
)來處理錯誤和診斷信息,它們是與程序的實際輸出結果是分開的。雖然 >
允許我們將命令的輸出重定向到文件中,但是 2>
允許我們將 stderr
的輸出重定向到文件中。例如,下面這個命令會將 “Hello there” 傳給一個名為 hello.log
的文件并把 “Bye bye” 傳到一個名為 error.log
的文件中。
node index.js > hello.log 2> error.log
現在我們已經了解了日志記錄的底層技術,接下來讓我們談談應該在什么情況下記錄日志內容。通常應該是以下情況之一:
我們將跳過前兩種情況,并重點介紹基于 Node.js 的后三點。
可能你在服務器上記錄日志的原因有多種。例如記錄傳入的請求并允許你從中提取諸如統計信息之類的內容,比如有多少用戶在點擊時發生了 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)
不打印整個對象,我們就不會獲得更多信息。
![終端中輸出的 "[object Object]" 信息](https://s3.amazonaws.com/com....
可以通過編寫自己的日志函數只輸出我們關心的東西,但是先等等,談談我們通常關心的東西。雖然這些信息經常成為我們關注的焦點,但實際上可能還需要其他信息:
pm2
來運行多個Node進程既然一切都會被轉到 stdout
和 stderr
,那么我們可能會想要不同的日志級別,還有配置和過濾日志的能力。
我們可以通過依賴 process
的各個部分并編寫一堆 JavaScript 來獲得所有這些,但關于 Node.js 的好消息是有 npm
這個生態系統,里面已經有了各種各樣的庫供我們使用。其中一些是:
pino
winston
roarr
bunyan
(請注意,這個已經 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); });
在這段代碼中,我們創建了一個 pino
的實例 logger
,并將其傳給 express-pino-logger
創建一個新的 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
遵循一種理念,為了提高性能,你應該通過管道(使用 |
)將輸出的任何處理移動到一個單獨的進程中。這包括使其可讀或將其上傳到云主機。這些被稱為 transports
??梢酝ㄟ^查看 transports
文檔了解為什么 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
消息。
有各種各樣的 transports 來美化或轉換你的日志。你甚至可以用 pino-colada
顯示 emoji。這些對你的本地開發很有用。在生產中運行服務器之后,你可能希望將日志傳輸到另一個 transports,再用 >
或者用像 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
在我們第一次使用debug
之前,需要初始化pino-debug
。最簡單的方法是在啟動 javascript 腳本的命令之前使用 Node.js 的 -r
或 --require
標志來 require 模塊。使用如下命令重新運行你的服務器(假設你安裝了pino-colada
):
DEBUG=mylib:randomid node -r pino-debug index.js | ./node_modules/.bin/pino-colada
你現在將用與程序日志相同的格式查看庫的調試日志。
本文介紹的最后一個案例是針對 CLI 進行日志記錄的特殊情況。我的理念是將“邏輯日志”與 CLI 的輸出 “日志” 分離。對于所有的邏輯日志,你應該用像 debug
這樣的庫。這樣你或其他人就可以重新使用該邏輯,而不受 CLI 的特定用例的約束。
當你用 Node.js 構建 CLI 時,可能希望添加一些看上去很漂亮顏色,或者用有視覺吸引力的方式格式化信息。但是,在構建 CLI 時,應該記住以下這幾種情況。
一種情況是你的 CLI 可能會在持續集成(CI)系統的上下文中使用,因此你可能希望刪除顏色和花哨的裝飾輸出。一些 CI 系統設置了一個名為 CI
的環境標志。如果你想更安全地檢查自己是否在 CI 中,那就是使用像 is-ci
這樣的包去支持一堆 CI 系統。
像 chalk
這樣的庫已經為你檢測了CI 并為你刪除了顏色。我們來看看它的樣子。
使用 npm install chalk
安裝 chalk
并創建一個名為 cli.js
的文件。將以下內容復制到其中:
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.
現在如果你用 node cli.js
運行這個腳本,將會看到彩色輸出。
但是如果你用 CI=true node cli.js
運行它,你會看到顏色被消除了:
你要記住的另一個場景是 stdout
是否以終端模式運行,也就是將內容寫入終端。如果是這種情況,我們可以使用 boxen
之類的東西顯示所有漂亮的輸出。如果不是,則可能會將輸出重定向到文件或用管道傳輸到某處。
你可以通過檢查相應流上的 isTTY
屬性來檢查 stdin
、stdout
或 stderr
是否處于終端模式。例如:process.stdout.isTTY
。 TTY
的意思是 “電傳打印機(teletypewriter)”,在這種情況下專門用于終端。
根據 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
被著色了。
之后運行相同的內容,但是將輸出重定向到一個文件,然后檢查內容:
node cli.js > output.log cat output.log
你會看到這次它打印了 undefined
后面跟著一個簡單的無色消息,因為 stdout
的重定向關閉了它的終端模式。因為 chalk
用了 supports-color
,它們會在相應的流上檢查 isTTY
。
像 chalk
這樣的工具已經為你處理了這種行為,但是在開發 CLI 時,你應該始終了解 CLI 可能在 CI 模式下運行或重定向輸出的情況。它還可以幫助你進一步獲得 CLI 的體驗。例如你可以在終端中以漂亮的方式排列數據,如果isTTY
是 undefined
,你可以切換到更容易解析的方式。
剛開始用 JavaScript 開發時用 console.log
記錄你的第一行日志確實很快,但是當你將代碼投入生產環境時,應該考慮更多關于日志記錄的內容。本文純粹是對各種方式和可用的日志記錄解決方案的介紹。我建議你去看一些自己喜歡的開源項目,看看它們是怎樣解決日志記錄問題的,還有它們所用到的工具。
感謝各位的閱讀!看完上述內容,你們對Node.js中日志信息的案例分析大概了解了嗎?希望文章內容對大家有所幫助。如果想了解更多相關文章內容,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。