Pino+pino-prettyでログを「読むもの」から「武器」にしよう
今回の記事について
最近ログについて勉強しています。
Next.js × Vercel 環境でアプリケーションを開発していると、
「ログは JSON が正しいと分かっているけど、開発中は正直つらい」
という瞬間に必ず出会います。
本番運用では問題ないのですが、そのままではローカルデバッグ時の可読性が低い。。。
そこで今回は、
- 本番では 構造化ログ(JSON)
- 開発時は 人間が読めるログ
この両立を実現するために
pino-pretty をデバッグ用途で導入してみたことを書きます。
先に結論を3つ
- Next.js × Vercel では Pino はとても快適
- pino-pretty は「開発時限定」で使うのが良さそう!
- ログの可読性が上がると、デバッグ速度が段違いに上がる
環境
- ランタイム:Node.js
- フレームワーク: Next.js 16
- 言語: TypeScript
- 紹介するライブラリ
- pino 10.1.0
- pino-pretty 13.1.3
pino と導入後の課題
pinoとは
Pino は 高速・軽量な構造化ログライブラリです。
- JSONで出力される
- 非常に高速で本番環境でのパフォーマンス影響を抑えられる
- Serverless / Edge 環境と相性が良い
特に Vercel Logs は JSON ログを前提としているため、Pino の思想と非常に噛み合っています。元々console.log ベースでログのようなものを仕込んでいたので、そこからの移行コストも低いと思い、pinoを扱うことにしました。
[補足] Vercel × Next.js と相性の良いログ出力ライブラリ比較
| ライブラリ | 特徴 | 長所 | 注意点 | おすすめ用途 |
|---|---|---|---|---|
| Pino | 高速・軽量な構造化ログ(JSON) | ・JSON 1行出力がデフォルト ・Vercel Logs との相性が非常に良い ・redaction(秘匿情報マスク)対応 |
・pretty表示は本番ではOFF推奨 | Next.js + Vercel の 標準ロガーとして最有力 |
| Winston | 多機能ロガー | ・フォーマット/トランスポートが柔軟 ・JSON出力対応 |
・設定がやや重い ・Serverlessではファイル出力が使いにくい |
複数出力先(将来の外部連携)を想定する場合 |
| Roarr | 構造化ログ特化 | ・シンプル ・Node/Browser両対応 |
・トランスポート機能がない | 軽量な構造化ログを最小構成で使いたい場合 |
| Morgan | HTTPアクセスログ用 | ・導入が簡単 ・リクエスト/レスポンスを自動記録 |
・HTTPログ専用 ・Next.jsでは用途が限定的 |
APIリクエストの簡易ログのみ |
実際に入れてみた
- 実際に導入して動かしてみると以下のようなログが表示されました (少しぼかしています)
{"level": "INFO","time":"2025-12-20T07:45:24.415Z","env":"local","traceId": "01JXXXX","kind":"api","route":"/api/v1/content","method":"GET","message":"request"}
{"level": "INFO","time":"2025-12-20T07:45:24.438Z","env":"local","traceId": "01JXXXX","kind":"api","route":"/api/v1/content","method":"GET","status":200,"durationMs":24,"message":"success"}
直感的に見づらいですよね。「これはデバッグに向いていないな」と思い、pino-pretty を導入して解決することにしました。
pino-prettyの導入
pino-prettyとは
pino-pretty は、Pino の JSON ログを人間が読める形に整形するツールです。
使い方
2つの方法があります
1:設定ファイルを書く方法 (おすすめ)
pinoのログ設定ファイルに以下のように書いてみてください
import pino from "pino";
const isProd = env.APP_ENV === "production";
export const logger = pino({
level: isProd ? "info" : "debug",
// ISO8601(ms)
timestamp: pino.stdTimeFunctions.isoTime,
// JSON の共通フィールド
base: {
env: env.APP_ENV,
},
// level を大文字に
formatters: {
level(label) {
return { level: label.toUpperCase() };
},
},
// 👇 開発時のみ pino-pretty を有効化
transport: isProd
? undefined
: {
target: "pino-pretty",
options: {
colorize: true,
singleLine: true,
customColors: {
info: "white",
debug: "gray",
warn: "yellow",
error: "red",
},
messageFormat: "[{traceId}] status:{status} {message}",
ignore: "env,message,status,traceId",
timestampFormat: "HH:MM:ss.l",
},
},
});
2:CLI オプションで指定する方法もあります
# package.json
{
"scripts": {
"dev": "next dev | pino-pretty --colorize --customColors \"info:white,debug:gray,warn:yellow,error:red\" --messageFormat \"[{traceId}] status:{status} {message}\" --ignore env,message,status,traceId --timestampFormat \"HH:MM:ss.l\"",
}
}
何をしているかは以下の通りです
| オプション | 内容 |
|---|---|
--colorize |
ログレベルごとに色付け |
--customColors |
level 別の色を明示指定 |
--messageFormat |
表示するログフォーマットを指定 |
--ignore |
JSON に含まれるが表示しないキー |
--timestampFormat |
時刻の表示フォーマット |
導入後の見た目
[20:21:01.899] INFO: [f1390666-f8c1-4deb-9ebf-7009f1cd0729] status: request {"kind":"action","action":"getCompanyListAction","userId":"1"}
[20:21:01.908] INFO: [f1390666-f8c1-4deb-9ebf-7009f1cd0729] status:200 success {"kind":"action","action":"getCompanyListAction","userId":"1","durationMs":10}
JSON形式の時よりもとてもみやすくなりましたね。singleLine: trueは必須です。
また、色も指定しているのでWARNやERRORにも気づきやすくなりました
本番で使うとパフォーマンスが落ちるとのことなので、開発時限定で使用するようにしましょう
最後に
Pino は 本番運用ではとても良い選択肢ですが、開発体験まで含めるとまだ足りません。pino-pretty と組み合わせて初めて完成します。
2つのツールを使いこなし、ログは「読むもの」から「武器」にしていきましょう。
参考
Discussion