Pino ログと Sentry エラー監視
NEXTY.DEV が提供する Pino + Sentry による本番グレードのログ収集とエラー監視ソリューションです。
アーキテクチャ概要
コードリポジトリ
│
└── getLogger("module-name")
│
├── Pino(構造化ログ)
│ ├── 開発環境 → カラー整形出力(pino-pretty)
│ ├── 本番環境 → JSON を stdout に書き出し(クラウドプラットフォームが自動収集)
│ └── VPS + LOG_DIR → ファイルローテーション(pino-roll)
│
└── Sentry(エラー上報)
├── warn → Breadcrumb を追加(パンくずトレース)
├── error → captureException(エラー上報)
└── fatal → captureException(level: fatal)2 つのツールがそれぞれの役割を担います:
- Pino はすべてのログを記録します。高パフォーマンスな構造化 JSON 出力が特徴です。
- Sentry はエラーの集約・アラート・スタックトレース・ユーザー影響の分析を担当します。
環境変数の設定
すべての変数は任意です。未設定の場合、Sentry の呼び出しはすべて無視され、Pino はデフォルトで info レベルを stdout に出力します。
Sentry 関連
NEXT_PUBLIC_SENTRY_DSN
取得手順:
- sentry.io にログインし、プロジェクトに移動します(または Next.js タイプの新規プロジェクトを作成します)
- 左側メニュー → Settings → Projects → プロジェクトを選択
- 左側 → Client Keys (DSN)
- DSN フィールドの値をコピーします。形式は
https://[email protected]/xxxです。
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/789

SENTRY_AUTH_TOKEN(任意、source map のアップロードに使用)
取得手順:
- sentry.io にログイン → 左側 Settings → Organization Tokens
- Create New Token をクリック
- 生成されたトークンをコピーします(
sntrys_から始まります)
SENTRY_AUTH_TOKEN=sntrys_eyJ0eXBlIjoiYXV0aF90b2tlbiJ9...
SENTRY_ORG と SENTRY_PROJECT(AUTH_TOKEN と組み合わせて使用)
取得手順:
SENTRY_ORG:Settings → General Settings → Organization Slug(sentry.io の URL 内の組織名でもあります。例:https://my-company.sentry.ioの場合はmy-company)SENTRY_PROJECT:Settings → Projects → プロジェクトをクリック → Project Slug
SENTRY_ORG=my-company
SENTRY_PROJECT=my-nextjs-appSENTRY_DEBUG(任意)
開発環境では Sentry はデフォルトでイベントを送信しません(データ汚染を防ぐため)。true に設定すると、開発環境でも実際にイベントが送信されるようになります。Sentry 連携自体のデバッグに役立ちます。
SENTRY_DEBUG=truePino 関連
LOG_LEVEL
出力するログレベルを制御します。このレベルより低いログは無視されます。指定可能な値:trace | debug | info | warn | error | fatal
LOG_LEVEL=info # 本番環境での推奨値
LOG_LEVEL=debug # 問題調査時に一時的に使用LOG_DIR(任意、VPS のみ必要)
設定すると pino-roll によるファイルローテーションが有効になり、ログが指定ディレクトリに書き込まれます。Vercel や Cloudflare Workers では空のままにしておけば、プラットフォームが stdout を収集します。
LOG_DIR=./logs # 相対パス(プロジェクトルートからの相対)
LOG_DIR=/var/log/myapp # または絶対パス使い方
基本的な使い方
import { getLogger } from "@/lib/logger";
const logger = getLogger("payment-service");
// trace / debug:開発時のデバッグ用。本番環境ではデフォルトで出力されません
logger.trace({ sql: "SELECT ..." }, "Executing query");
logger.debug({ params }, "Request received");
// info:通常のビジネスイベント
logger.info({ userId, planId }, "User subscribed");
// warn:回復可能な例外(Sentry Breadcrumb も追加されます)
logger.warn({ retryCount: 3, url }, "Webhook delivery failed, retrying");
// error:エラー(自動的に Sentry へ上報されます)
logger.error(new Error("Stripe API timeout"), "Payment failed");
// fatal:重大なエラー(Sentry へ上報、level: fatal)
logger.fatal(new Error("DB connection lost"), "Service unavailable");コンテキスト付きエラー
// Error オブジェクトを渡す
logger.error(new Error("Invalid signature"), "Webhook verification failed");
// オブジェクトを渡す(err フィールドがある場合、自動的に抽出して Sentry へ上報されます)
logger.error({ err: error, webhookId, payload }, "Webhook processing error");
// 手動キャプチャ:error() と同等ですが、意図がより明確になります
logger.captureError(error, { userId, action: "checkout" });低レベルの Pino logger へのアクセス
子 logger の作成や Pino の高度な機能を使用したい場合:
const logger = getLogger("api");
const childLogger = logger.pino.child({ requestId: "req-123" });
childLogger.info("Request started");Server Action での使用
// app/actions/payment.ts
import { getLogger } from "@/lib/logger";
const logger = getLogger("payment-action");
export async function createCheckout(planId: string) {
try {
const session = await stripe.checkout.sessions.create({ ... });
logger.info({ planId, sessionId: session.id }, "Checkout session created");
return { url: session.url };
} catch (error) {
logger.error(error instanceof Error ? error : new Error(String(error)), "Checkout failed");
throw error;
}
}Route Handler での使用
// app/api/webhooks/stripe/route.ts
import { getLogger } from "@/lib/logger";
const logger = getLogger("stripe-webhook");
export async function POST(request: Request) {
try {
// ...
logger.info({ event: event.type }, "Webhook processed");
return Response.json({ ok: true });
} catch (error) {
logger.error(error instanceof Error ? error : new Error(String(error)), "Webhook error");
return Response.json({ error: "Internal error" }, { status: 500 });
}
}ログレベルの説明
| レベル | 使用シーン | Sentry の動作 |
|---|---|---|
trace | SQL クエリ、詳細な実行パス | なし |
debug | リクエストパラメータ、中間状態 | なし |
info | ユーザー操作、注文作成、ログイン | なし |
warn | リトライ、フォールバック、レート制限の発動 | Breadcrumb を追加 |
error | API 失敗、データベースエラー、決済失敗 | 例外を上報 |
fatal | サービス停止、初期化失敗 | 例外を上報(fatal レベル) |
本番環境のデフォルトレベルは info です。つまり、trace と debug のログは出力されません。デバッグ時は LOG_LEVEL=debug を一時的に設定してください。
各プラットフォームへのデプロイガイド
Vercel
追加の設定は不要です。Next.js 関数の stdout は自動的に Vercel のログシステムに送信されます。
ログの確認: Vercel Dashboard → プロジェクト → Logs → Functions
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# LOG_DIR は空のまま注意: Vercel 関数のログは無料プランでは 1 時間しか保持されません。本番環境では Sentry を主なエラー追跡手段として併用することを推奨します。
Cloudflare Workers
Workers は Edge ランタイム上で動作し、ファイルシステムは利用できません。logger は Edge 環境を自動検出し、純粋な JSON stdout モードを使用します(pino-roll と pino-pretty はスキップされます)。
ログの確認: Cloudflare Dashboard → Workers & Pages → プロジェクト → Logs
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# LOG_DIR は必ず空のまま。Cloudflare Workers にはファイルシステムがありませんリアルタイムログ: 開発・デバッグ時は wrangler tail でターミナルからリアルタイム出力を確認できます。
VPS — Coolify
Coolify は Docker コンテナとしてアプリを実行します。stdout は Docker によって自動的にキャプチャされ、ダッシュボードから確認できます。
stdout ログの確認: Coolify Dashboard → アプリ → Logs タブ(リアルタイムストリーミング)
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# LOG_DIR は空のまま。stdout を使用し、Coolify ダッシュボードで直接確認できますファイルへの永続化が必要な場合:
- Coolify Dashboard → アプリ → Storages → Add Storage
- 以下を入力します:
- Name:
logs - Source Path(Host):ホストマシンのパス(例:
/var/lib/coolify/myapp/logs) - Destination Path(Container):コンテナ内のパス(例:
/app/logs)
- Name:
- 環境変数に以下を設定します:
LOG_DIR=/app/logs- 再デプロイ後、ログファイルはホストマシンの
/var/lib/coolify/myapp/logs/に書き込まれます。SSH で確認できます:
tail -f /var/lib/coolify/myapp/logs/payment-service.2025-03-08.1.logVPS — Dokploy
Dokploy も Docker ベースです。stdout ログはダッシュボードから直接確認できます。
stdout ログの確認: Dokploy Dashboard → アプリ → Logs タブ
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# LOG_DIR は空のまま。stdout を使用ファイルへの永続化が必要な場合:
- Dokploy Dashboard → アプリ → Advanced → Mounts → Add Mount
- Volume Mount を選択し、以下を入力します:
- Host Path:ホストマシンのパス(例:
/var/dokploy/myapp/logs) - Container Path:コンテナ内のパス(例:
/app/logs)
- Host Path:ホストマシンのパス(例:
- 環境変数に以下を設定します:
LOG_DIR=/app/logs- 再デプロイ後、SSH で確認できます:
tail -f /var/dokploy/myapp/logs/payment-service.2025-03-08.1.logVPS — Node.js / PM2
方法 A:PM2(推奨。LOG_DIR の設定が不要)
PM2 は stdout を自動的にファイルに収集します。コードの変更は一切不要です:
pm2 start npm --name "myapp" -- start
pm2 logs myapp # リアルタイムで確認
# ログファイルの場所:~/.pm2/logs/myapp-out.log方法 B:LOG_DIR によるファイルローテーション
PM2 を使わない場合に適しています:
LOG_DIR=./logs
# ログファイル:logs/payment-service.2025-03-08.1.log(日次ローテーション、10MB 超で自動分割)cron で古いログを定期的に削除することを推奨します:
# 直近 30 日分を保持
0 2 * * * find /path/to/app/logs -name "*.log" -mtime +30 -deleteベストプラクティス
構造化フィールドを使い、文字列の結合を避ける
// ✅ 推奨:フィールドをログプラットフォームでインデックス化・フィルタリングできます
logger.info({ userId, amount, currency }, "Payment completed");
// ❌ 非推奨:検索・フィルタリングができません
logger.info({}, `User ${userId} paid ${amount} ${currency}`);機密情報をログに含めない
// ❌ パスワード、トークン、カード番号の完全な値は絶対に記録しないでください
logger.info({ password, stripeKey }, "User data");
// ✅ 必要最低限の非機密な識別情報のみ記録します
logger.info({ userId, last4: card.last4 }, "Payment method added");モジュールごとに独立した logger を使う
const logger = getLogger("stripe-webhook"); // 決済 webhook
const logger = getLogger("ai-chat"); // AI 機能
const logger = getLogger("auth"); // 認証クライアントコンポーネントでは使用しない
logger は Node.js / Edge ランタイムに依存しており、サーバーサイドでのみ使用できます:
// ✅ Server Component、Server Action、Route Handler、middleware
import { getLogger } from "@/lib/logger";
// ❌ "use client" コンポーネント内では使用できませんクライアントサイドのエラーは sentry.client.config.ts の Sentry SDK によって自動的にキャプチャされます。手動で logger を呼び出す必要はありません。
関連ファイル
lib/
logger/
index.ts # Logger のコア実装
sentry.client.config.ts # クライアントサイドの Sentry 設定(Session Replay を含む)
sentry.server.config.ts # サーバーサイドの Sentry 設定
sentry.edge.config.ts # Edge ランタイムの Sentry 設定
instrumentation.ts # Next.js 初期化フック
next.config.mjs # serverExternalPackages + withSentryConfig