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)两个工具各司其职:
- 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
- 复制生成的 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
SENTRY_DSN=https://[email protected]/xxx
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
SENTRY_DSN=https://[email protected]/xxx
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 标签页(实时流式输出)
SENTRY_DSN=https://[email protected]/xxx
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 标签页
SENTRY_DSN=https://[email protected]/xxx
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}`);敏感信息不进日志
// ❌ 绝对不要记录密码、token、完整卡号
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