🔍

Pino+pino-prettyでログを「読むもの」から「武器」にしよう

に公開

今回の記事について

最近ログについて勉強しています。
Next.js × Vercel 環境でアプリケーションを開発していると、
「ログは JSON が正しいと分かっているけど、開発中は正直つらい」
という瞬間に必ず出会います。

本番運用では問題ないのですが、そのままではローカルデバッグ時の可読性が低い。。。
そこで今回は、

  • 本番では 構造化ログ(JSON)
  • 開発時は 人間が読めるログ

この両立を実現するために
pino-pretty をデバッグ用途で導入してみたことを書きます。

先に結論を3つ

  1. Next.js × Vercel では Pino はとても快適
  2. pino-pretty は「開発時限定」で使うのが良さそう!
  3. ログの可読性が上がると、デバッグ速度が段違いに上がる

環境

  • ランタイム:Node.js
  • フレームワーク: Next.js 16
  • 言語: TypeScript
  • 紹介するライブラリ
    • pino 10.1.0
    • pino-pretty 13.1.3

pino と導入後の課題

pinoとは

Pino は 高速・軽量な構造化ログライブラリです。

  • JSONで出力される
  • 非常に高速で本番環境でのパフォーマンス影響を抑えられる
  • Serverless / Edge 環境と相性が良い

https://github.com/pinojs/pino

特に 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 ログを人間が読める形に整形するツールです。

https://github.com/pinojs/pino-pretty

使い方

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つのツールを使いこなし、ログは「読むもの」から「武器」にしていきましょう。

参考

https://github.com/pinojs/pino-pretty

https://zenn.dev/kei1232/articles/9e603c2f9f04fb

https://zenn.dev/tomon9086/articles/5c5f3f245596c9

Discussion