ハンズオン
ロギングとオブザーバビリティ
Node・Edge・Workers で共通の構造化ログ。リクエストID、秘匿情報のマスキング、ルート別の例。Vercel/Cloudflare/Node サーバに対応。
基本
- 構造化(JSON)ログ、
request_idによる相関。 - レベル:
debug/info/warn/error。 - 秘匿情報(クッキー・トークン)はマスク。
含まれるもの
- Node/Edge 共通のロガー API(同じ使い方)。
- Node: Pino による高速 JSON 出力。
- Edge/Workers:
console.logによる軽量 JSON。
- Middleware で
request_idを応答ヘッダに付与(リクエストは変更しない)。 - ストレージ presign ルートに実装例あり。
ファイル
- Node:
src/lib/logger/server.ts - Edge:
src/lib/logger/edge.ts - 型:
src/lib/logger/types.ts - Middleware:
src/middleware.ts - 例:
src/app/api/storage/uploads/route.ts
ファイル
- Node:
src/lib/logger/server.ts - Edge:
src/lib/logger/edge.ts - 型:
src/lib/logger/types.ts - Middleware:
src/middleware.ts - 例:
src/app/api/storage/uploads/route.ts
ログの確認
- ローカル: ターミナル(一行JSON)。見やすくするには
pino-prettyを利用:pnpm dev 2>&1 | pnpx pino-pretty- ファイル保存:
pnpm dev 2>&1 | pnpx pino-pretty | tee logs/dev.log
- Vercel: Deployments → Functions → Logs /
vercel logs。 - Cloudflare Workers:
wrangler tail。
使い方(Node)
import { logger, requestIdFromHeaders } from '@/lib/logger/server'
export async function POST(req: Request){
const rid = requestIdFromHeaders(req.headers)
const log = logger.child({ request_id: rid, route: '/api/example' })
log.info({ event: 'example.start' })
// ...
log.info({ event: 'example.ok' })
return new Response('ok')
}使い方(Edge)
import { logger } from '@/lib/logger/edge'
export const runtime = 'edge'
export function GET(){
logger.info({ event: 'edge.heartbeat' })
return new Response('ok')
}Tips
LOG_LEVELとマスキングルールを必要に応じて調整。- 既定でログファイルは作成しません(stdout/stderrへ出力)。ローカルで保存したい場合は
teeを使用:pnpm dev 2>&1 | tee logs/dev.json。 - サーバコードの
console.*は logger に置き換え。
使い方(Node)
import { logger, requestIdFromHeaders } from '@/lib/logger/server'
export async function POST(req: Request){
const rid = requestIdFromHeaders(req.headers)
const log = logger.child({ request_id: rid, route: '/api/example' })
const start = Date.now()
try {
log.info({ event: 'example.start' })
// ...
log.info({ event: 'example.ok', duration_ms: Date.now() - start })
return new Response('ok')
} catch (e:any) {
log.error({ event: 'example.error', message: e?.message })
return new Response('error', { status: 500 })
}
}使い方(Edge)
import { logger } from '@/lib/logger/edge'
export const runtime = 'edge'
export function GET(){
logger.info({ event: 'edge.heartbeat' })
return new Response('ok')
}ヘルパー: withApiLogging (Node)
import { withApiLogging } from '@/lib/logger/server'
export const POST = withApiLogging(async (req) => new Response('ok'), {
route: '/api/foo', event: 'foo.process'
})ベストプラクティス
request_idを子ロガーに付与、完了時にduration_msを記録。- メッセージは短く、フィールド主体で。
- 本文全体のログは避け、ハッシュや必要最小限のフィールドに。
脱敏のカスタマイズ
- Node:
src/lib/logger/server.tsのredactPaths。 - Edge:
src/lib/logger/edge.tsのredactKeys。
オプション連携
- Sentry(
@sentry/nextjs)や Axiom / Better Stack / Datadog など。
トラブルシュート
- 予期しないログインリダイレクト: middleware がリクエストヘッダ/クッキーを上書きしていないか確認。
- Edge でログが見えない:
runtime='edge'とログ閲覧方法を確認。