Winstonで構造化ログ
出力をJSONでフォーマット
winston.format.combine をhookしてJSON.stringifyで出力
コンテキスト情報をバインドする仕組みを追加
logger.child(context) で子loggerを作成
やってないこと
logger.info("メッセージ", key=value) のようなキーバリュー渡しのAPI追加
方針
既存スクリプトにできるだけ手を入れたくない(本家のアップデート追従が大変になるので)という制約があったため、外部ライブラリの追加や大幅な改修は避けました。
code:ts
const winston = require('winston');
let logger = winston.createLogger({
level: 'info',
format: ((process.env.NODE_ENV !== 'production') ?
winston.format.simple():
winston.format.combine(
winston.format.printf(info => {
// VERSION_INFOを全てのログに含める
return JSON.stringify(Object.assign({}, info, VERSION_INFO), (key, value) => {
// 値がnullやundefinedの場合も出力する
return (value === null || value === undefined)? '': value;
});
})
)
),
transports: [
new winston.transports.Console()
],
exceptionHandlers: [
new winston.transports.Console()
]
});
const bind_logger_context = (context={}) => {
const child_logger = logger.child(context);
child_logger.parent = logger;
logger = child_logger; // グローバル変数を上書き!
};
const unbind_logger_context = () => {
if (logger.parent) {
logger = logger.parent;
}
};
const handler = async (event, context) => {
// Lambda呼び出し元のコンテキスト情報とLambdaのcontextをログにbind
bind_logger_context(Object.assign({RequestId: context?.awsRequestId}, event.context || {}));
logger.info("event.body", event.body)
// このへんでハンドラー処理
// 最後にコンテキスト情報を削除
unbind_logger_context();
};