Node.jsのロガー「pino」の使い方メモ
Node.jsのロガー「pino」について、忘備録を兼ねて簡単に使い方をメモします。
インストール
npm install pino
ログをファイルに書き込む設定
まず、ログをログファイルに書きこむことを試してみます。
設定ファイルとして次のlogger.js
ファイルを用意します(destination
にログファイルのパスを指定します)。
let pino = require('pino')
module.exports.logger = pino({
level: 'trace',
transport: {
target: 'pino/file',
options: {
destination: 'logs/out.log',
mkdir: true
}
}
})
Common JSで書いていますがESMでもOKです。
pino/file
トランスポートがログをファイルに書き込みます。トランスポートとはログが発生したときにログをどこに送るかの転送先になります。
公式の使用例に比べて少し設定が複雑に見えるかもしれません。pino.destination
を使うともっとシンプルに書けますが、後々の拡張を考えるとトランスポートを使う方法が良いと思いますのでこうしました。
pino.destination
とpino/file
トランスポートはインターフェースが異なるだけで、中身は同じもの(sonic-boom
後述)です。
ロガーのインポート
設定したロガーは、logger.js
からインポートすれば利用できるようになります。
let { logger } = require('./logger.js')
ログレベル
先のlogger.js
ファイルのlevel
プロパティには出力したい最低のログレベルを設定します。デフォルトは本番環境向けにinfo
になっていますので、trace
とdebug
は出力されません。開発環境ではtrace
に設定しておいたほうが良いです。
ログレベルの一覧と対応付けられた数値はlogger.levels
で確認できます。
let { logger } = require('./logger.js')
console.log(logger.levels)
// {
// labels: {
// '10': 'trace',
// '20': 'debug',
// '30': 'info',
// '40': 'warn',
// '50': 'error',
// '60': 'fatal'
// },
// values: { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 }
// }
ログの出力
ログを出力するには、ログレベルごとにメソッドが用意されています。
let { logger } = require('./logger.js')
logger.trace('trace log')
logger.debug('debug log')
logger.info('info log')
logger.warn('warn log')
logger.error('error log')
logger.fatal('fatal log')
実行すると、ログファイルに1行ごとにJSON形式で出力されます。
{"level":10,"time":1658630562576,"pid":200036,"hostname":"...","msg":"trace log"}
{"level":20,"time":1658630562578,"pid":200036,"hostname":"...","msg":"debug log"}
{"level":30,"time":1658630562578,"pid":200036,"hostname":"...","msg":"info log"}
{"level":40,"time":1658630562578,"pid":200036,"hostname":"...","msg":"warn log"}
{"level":50,"time":1658630562578,"pid":200036,"hostname":"...","msg":"error log"}
{"level":60,"time":1658630562578,"pid":200036,"hostname":"...","msg":"fatal log"}
プロパティは以下です。
プロパティ | 説明 |
---|---|
level | ログレベルの数値 |
time | UNIXタイムスタンプ(ミリ秒) |
pid | プロセスID |
hostname | OSのホスト名 |
msg | 出力メッセージ |
独自のプロパティを出力する
ログを出力する各メソッドの第一引数にオブジェクトを渡せば独自のプロパティを出力することができます。第二引数にはメッセージを指定します(省略可)。
let { logger } = require('./logger.js')
logger.trace({ domain: 'main' }, 'trace log')
{"level":10,"time":1658633672743,"pid":174296,"hostname":"...","domain":"main","msg":"trace log"}
グループ化する
logger.child()
で、独自のプロパティをデフォルトに持つロガーを作ることができます。
let { logger } = require('./logger.js')
let child = logger.child({ domain: 'main' })
child.trace('trace log')
child.info('info log')
{"level":10,"time":1658636134712,"pid":214804,"hostname":"...","domain":"main","msg":"trace log"}
{"level":30,"time":1658636134713,"pid":214804,"hostname":"...","domain":"main","msg":"info log"}
Errorオブジェクトを出力
pinoはErrorオブジェクトをそのまま出力できます。
let { logger } = require('./logger.js')
try {
throw Error('msg')
} catch (err) {
logger.error(err)
}
{"level":50,"time":1658634277942,"pid":210872,"hostname":"...","err":{"type":"Error","message":"msg","stack":"Error: msg\n at Object.<anonymous> (C:\\pinotest\\app.js:4:9)\n at Module._compile (node:internal/modules/cjs/loader:1097:14)\n at Object.Module._extensions..js (node:internal/modules/cjs/loader:1149:10)\n at Module.load (node:internal/modules/cjs/loader:975:32)\n at Function.Module._load (node:internal/modules/cjs/loader:822:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)\n at node:internal/main/run_main_module:17:47"},"msg":"msg"}
sonic-boom
について
pino/file
トランスポートはsonic-boom
として実装は別のリポジトリになっています。そのためオプション機能はsonic-boom
を参考にしてください。
特徴として、非同期ではログを一旦バッファに溜めておいてから、溜まったらファイルに書き込むという動きをすることがあります。もし出力したはずのログが書き込まれていないと思ったら任意のタイミングでlogger.flush()
を実行するようにすると溜まっているログが書き込まれます。
ログファイルを解析する
pino/file
トランスポートで書き込まれたログファイルは、日付がUNIXタイムスタンプになっていますので人がそのまま読むのには向いていません。
その代わり、1行ごとにJSON形式になっているのでプログラムで簡単に扱うことができます。
プログラムを作らずに、すぐに確認したいという場合はjqコマンドとpino-pretty
を組み合わせるのが便利です。
jq
は出力するログをクエリで選択できます。
pino-pretty
はpinoのログを人が見やすいように整えてくれます。
-
pino-pretty
のインストール(jqは環境に合わせてインストールしてください)
npm install -g pino-pretty
例:エラーログだけを抽出する
cat logs/out.log | jq -c 'select(.level == 50)' | pino-pretty -t 'SYS:standard'
例:ある日付以降を抽出(日本時間の場合です。もっといい書き方ありそう)
cat logs/out.log | jq -c 'select(.time > ((\"2022-07-24T00:00:00Z\" | fromdate)) * 1000 - 32400000)' | pino-pretty -t 'SYS:standard'
ファイル以外に出力する
ログをslackに投げたり、ログローテーションしたりするには、誰かが作ったトランスポートを探すか、トランスポートを自作します。
自作トランスポートの作り方
pino-abstract-transport
を使って簡単にトランスポートを作ることができます。pino-abstract-transport
はpinoをインストールしたら別途インストールしなくても利用できます。
コンソールに出力するだけのトランスポートはこうなります。
let build = require('pino-abstract-transport')
module.exports = function(opts) {
return build(function(source) {
source.on('data', function(obj) {
console.log(obj)
})
})
}
objにJSONとして出力されていたオブジェクトが入っていますので、これを好きなようにカスタマイズして好きなところに出力するだけです。
自作トランスポートの利用
作ったトランスポートはlogger.js
設定時にtarget
にファイル名を渡します。
let pino = require('pino')
module.exports.logger = pino({
level: 'trace',
transport: {
target: './my_transport.js'
}
})
複数のトランスポートの利用
logger.js
設定時にtargets
に複数のトランスポートを設定することができます。設定したすべてのトランスポートにログが出力されます。
このとき、最上位のlevel
とは別に、各トランスポートでもlevel
を設定する必要があります。しないとinfo
になります。トランスポートごとにログレベルを調整できるように、このような仕様になっていると思われます。
let pino = require('pino')
module.exports.logger = pino({
level: 'trace',
transport: {
targets: [
{ target: './my_transport.js', level: 'error' },
{
target: 'pino/file',
options: {
destination: 'logs/out.log',
mkdir: true
},
level: 'trace'
}
]
}
})
ブラウザでも使える
pinoはブラウザ上のJavaScriptでも使えますが、設定方法は異なります。
サーバーサイドほど複雑ではありませんのでドキュメントが参考になります。
Discussion