Menu

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

取得手順:

  1. sentry.io にログインし、プロジェクトに移動します(または Next.js タイプの新規プロジェクトを作成します)
  2. 左側メニュー → SettingsProjects → プロジェクトを選択
  3. 左側 → Client Keys (DSN)
  4. DSN フィールドの値をコピーします。形式は https://[email protected]/xxx です。
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/789
dsn
dsn

SENTRY_AUTH_TOKEN(任意、source map のアップロードに使用)

取得手順:

  1. sentry.io にログイン → 左側 SettingsOrganization Tokens
  2. Create New Token をクリック
  3. 生成されたトークンをコピーします(sntrys_ から始まります)
SENTRY_AUTH_TOKEN=sntrys_eyJ0eXBlIjoiYXV0aF90b2tlbiJ9...
org token

SENTRY_ORGSENTRY_PROJECT(AUTH_TOKEN と組み合わせて使用)

取得手順:

  • SENTRY_ORGSettingsGeneral SettingsOrganization Slug(sentry.io の URL 内の組織名でもあります。例:https://my-company.sentry.io の場合は my-company
  • SENTRY_PROJECTSettingsProjects → プロジェクトをクリック → Project Slug
SENTRY_ORG=my-company
SENTRY_PROJECT=my-nextjs-app

SENTRY_DEBUG(任意)

開発環境では Sentry はデフォルトでイベントを送信しません(データ汚染を防ぐため)。true に設定すると、開発環境でも実際にイベントが送信されるようになります。Sentry 連携自体のデバッグに役立ちます。

SENTRY_DEBUG=true

Pino 関連

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 の動作
traceSQL クエリ、詳細な実行パスなし
debugリクエストパラメータ、中間状態なし
infoユーザー操作、注文作成、ログインなし
warnリトライ、フォールバック、レート制限の発動Breadcrumb を追加
errorAPI 失敗、データベースエラー、決済失敗例外を上報
fatalサービス停止、初期化失敗例外を上報(fatal レベル)

本番環境のデフォルトレベルは info です。つまり、tracedebug のログは出力されません。デバッグ時は LOG_LEVEL=debug を一時的に設定してください。

各プラットフォームへのデプロイガイド

Vercel

追加の設定は不要です。Next.js 関数の stdout は自動的に Vercel のログシステムに送信されます。

ログの確認: Vercel Dashboard → プロジェクト → LogsFunctions

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 ダッシュボードで直接確認できます

ファイルへの永続化が必要な場合:

  1. Coolify Dashboard → アプリ → StoragesAdd Storage
  2. 以下を入力します:
    • Namelogs
    • Source Path(Host):ホストマシンのパス(例:/var/lib/coolify/myapp/logs
    • Destination Path(Container):コンテナ内のパス(例:/app/logs
  3. 環境変数に以下を設定します:
LOG_DIR=/app/logs
  1. 再デプロイ後、ログファイルはホストマシンの /var/lib/coolify/myapp/logs/ に書き込まれます。SSH で確認できます:
tail -f /var/lib/coolify/myapp/logs/payment-service.2025-03-08.1.log

VPS — Dokploy

Dokploy も Docker ベースです。stdout ログはダッシュボードから直接確認できます。

stdout ログの確認: Dokploy Dashboard → アプリ → Logs タブ

NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
LOG_LEVEL=info
# LOG_DIR は空のまま。stdout を使用

ファイルへの永続化が必要な場合:

  1. Dokploy Dashboard → アプリ → AdvancedMountsAdd Mount
  2. Volume Mount を選択し、以下を入力します:
    • Host Path:ホストマシンのパス(例:/var/dokploy/myapp/logs
    • Container Path:コンテナ内のパス(例:/app/logs
  3. 環境変数に以下を設定します:
LOG_DIR=/app/logs
  1. 再デプロイ後、SSH で確認できます:
tail -f /var/dokploy/myapp/logs/payment-service.2025-03-08.1.log

VPS — 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