為Express.js編寫一個Logger
Express.js 是Node.js下最基礎最靈活的Web服務器。 Express的日志工具有很多,比如默認的訪問日志工具 morgan , 通用日志工具 winston 等等。
本文便來發掘一下這些日志工具的優秀特性,并一一給出實現: 對象輸出、日期前綴、訪問日志,模塊名前綴,以及彩色輸出等。
JSON Stringify
JavaScript服務器輸出JSON真是再平常不過了,輸出可讀的JSON在開發中非常有用。 為了輸出可讀的JSON,我們將所有對象類型的參數 stringify 即可。
function log(){
var args = Array.prototype.slice.call(arguments).map(stringify);
console.log.apply(console, args);
}
function stringify(arg) {
return typeof arg === 'object' ?
JSON.stringify(arg, null, 4) : arg;
}
log({foo: ["bar", "foo"]});
// 輸出:
// {
// foo: ["bar", "foo"]
// }
我們希望 log 函數像 console.log 一樣接受多個參數,因此我們需要對所有參數進行map。
日期前綴
當日志用于服務器環境時,我們希望知道日志的輸出時間, 這在性能調試和調試因果關系時非常重要。 尤其是在使用像pm2這樣的進程監視器時,錯誤和標準輸出是分開存儲的。 如果沒有時間戳很難得知順序關系。
function parse(str) {
// 為每行都添加時間戳
return str.split('\n')
.map(prefixify)
.join('\n');
}
function prefixify(str){
var now = new Date();
var prefix = `[${now.toLocaleString()}]`;
// 將時間戳放在行首
return prefix + ' ' + str;
}
時間日期的格式化可以使用 strftime 。
提供類console對象
我們的logger的使用方式最好與console相同以獲得最好的兼容與可用性。 于是需要提供 .log() , .warn() , .error() , .info() 等方法, 同時也需要支持多參數的情形。我們需要做的是封裝所有的 console 方法:
function parse(argvs) {
return Array.prototype.slice.call(argvs)
.map(stringify).join(' ')
.split('\n').map(prefixify).join('\n');
}
function prefixify(str){
var now = new Date();
var prefix = `[${now.toLocaleString()}]`;
return prefix + ' ' + str;
}
function stringify(arg) {
return typeof arg === 'object' ?
JSON.stringify(arg, null, 4) : arg;
}
module.exports = {
log: function(){ console.log(parse(arguments)) },
warn: function(){ console.warn(parse(arguments)) },
info: function(){ console.info(parse(arguments)) },
error: function(){ console.error(parse(arguments)) }
};
彩色的輸出
在開發環境中輸出彩色的日志可以讓我們更快地獲取信息,在終端中輸出彩色需要使用特殊字符。 自定義過 PS1 的童鞋一定會感受到手寫這些字符的費勁,在Node.js中我們可以使用 colors 庫來完成這件事情。
npm install colors
然后在輸出時便可以先調用 colors 提供的API做字符串轉換,現在來把日期前綴變成青色的:
const colors = require('colors/safe');
function prefixify(str){
var now = new Date();
var prefix = `[${now.toLocaleString()}]`;
// 將時間戳放在行首
return colors.cyan(prefix) + ' ' + str;
}
帶顏色的輸出只是具有特殊字符的字符串,可以像普通字符串一樣進行操作。
Express訪問日志
現在我們Express訪問日志與普通日志格式一致,這需要監聽Express請求和響應。 需要用到 on-headers 來監聽寫Response Header事件。
const onHeaders = require('on-headers');
// 訪問日志
app.use(function(req, res, next) {
req.receivedAt = Date.now();
onHeaders(res, function() {
var duration = Date.now() - req.receivedAt;
// 這里調用我們的logger
// 示例輸出: [2016-07-28 10:23:02] GET / 200 21ms
logger.log(req.method.toUpperCase(),
req.originalUrl,
res.statusCode,
duration + 'ms');
});
next();
});
// use 路由
可能你發現Express的默認訪問日志工具 morgan 會輸出訪問耗時。 這需要同時封裝 console.time和 console.timeEnd`,本文就不贅述了。
模塊名綁定
標準的Logger大多可以綁定一個模塊名(或者Trace ID), 模塊名會在該模塊的每條日志的前綴部分出現,以方便跟蹤Log是哪個模塊(或業務線)輸出的。 其用法大致如下:
var logger = Logger('account:factory');
logger.log('create account error');
// 輸出:
// [account:factory] create account error
如何實現呢?來一個簡單的閉包即可:
function Logger(traceId){
return {
log: function(str){
console.log(`[${traceId}] ${str}`);
}
}
}
你還可以將 [account:factory] 顯示為灰色,并支持多參數的輸出(見上文)。
來自:http://harttle.com/2016/09/22/express-logger.html