上手实践
日志与可观测性
Node、Edge 与 Workers 通用的结构化日志:请求ID、敏感信息脱敏与路由示例;适配 Vercel、Cloudflare 与 Node 服务器。
基础概念
- 结构化(JSON)日志,便于查询与关联。
- 关联ID:为每个请求统一
request_id
。 - 等级:
debug
/info
/warn
/error
。 - 脱敏:不要记录 cookies、tokens、密钥等。
包含内容
- Node 与 Edge/Workers 统一的日志 API(相同用法)。
- Node:Pino 输出一行 JSON(快速、稳定)。
- Edge/Workers:
console.log
输出一行 JSON(轻量)。
- 中间件在响应头附加
x-request-id
(不修改请求头/ Cookie)。 - 存储 presign 路由提供示例。
关键文件
- Node:
src/lib/logger/server.ts
- Edge:
src/lib/logger/edge.ts
- 类型:
src/lib/logger/types.ts
- 中间件:
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
- 中间件:
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')
}
建议
- 配置
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'
})
最佳实践
- 每个请求创建子 logger,附带
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 等。
故障排查
- 出现意外登录重定向:确认中间件未覆盖请求头/ Cookie。
- Edge 无日志:确保
runtime='edge'
并使用wrangler tail
/Vercel 日志查看。