
很多独立开发者早期写日志,基本就是在代码里加几行 console.log。
这在本地调试时够用,但产品一上线,问题就来了:用户说“刚才报错了”,你不知道是哪次请求;支付回调失败了,你不知道失败在哪一步;后台任务跑丢了,你只能翻服务器文件;线上偶发异常,你很难复现。
日志系统的目标不是“多打印一点东西”,而是让你在出问题时能回答三个问题:
发生了什么?
发生在谁身上?
为什么会发生?
本文参考了几类官方资料:
日志是系统运行过程中的事件记录。
它不像监控指标那样回答“现在整体是否正常”,也不像错误监控那样只关注异常堆栈。日志更适合回答细节问题。
比如:
这个用户刚才点了什么?
这次请求经过了哪些服务?
第三方接口返回了什么状态?
后台任务处理到哪一步?
为什么这条订单没有变成 paid?
日志是排查问题时最常用的“现场记录”。没有日志,很多线上问题只能靠猜。
早期最常见的日志是这样:
payment failed
user login success
send email error
这类日志可读,但不适合查询、聚合和定位。
更好的做法是写结构化日志。也就是每条日志都包含固定字段,方便后续搜索、过滤和关联。
一条基础日志可以包含:
timestamp
level
service
environment
request_id
user_id
team_id
route
action
status_code
duration_ms
error_code
message
metadata
不要把所有内容都塞进 message。真正需要查询的字段,应该成为结构化字段。
日志级别不是装饰,它决定你如何过滤噪音。
常见级别可以这样理解:
debug:本地或临时排查细节
info:正常业务事件
warn:不符合预期但系统还能继续
error:请求或任务失败,需要关注
fatal:进程级严重错误,需要立即处理
早期产品最容易犯的错,是把所有日志都打成 info,或者把所有异常都打成 error。
如果级别不清楚,线上排查时你会被日志淹没。
一个简单规则是:
用户正常操作成功:info
第三方接口偶发超时但已重试:warn
请求最终失败:error
进程即将退出:fatal
本地调试细节:debug
只要你的系统有 HTTP 请求、前端调用、后台任务、Webhook、支付回调,就应该尽早引入 request_id 或 trace_id。
它的作用是把分散日志串起来。
例如一次支付流程可能经过:
前端点击支付
后端创建 checkout session
支付平台回调 webhook
数据库更新订单
邮件系统发送收据
后台任务同步订阅状态
如果每一步都有同一个 request_id、trace_id 或业务相关的 order_id,你就能把整个过程串成一条线。
OpenTelemetry 的日志资料也强调,日志可以和 trace、span 关联,这样日志不再是孤立文本,而是可观测性链路的一部分。
不要把日志加在每一行代码里。优先覆盖关键边界。
最应该打日志的位置:
用户登录、注册、退出
权限变更
支付创建、支付成功、支付失败、退款
Webhook 接收、校验、处理结果
邮件发送结果
文件上传和删除
后台任务开始、完成、失败
第三方 API 调用结果
重要配置变更
安全相关事件
这些事件要么影响钱,要么影响权限,要么影响用户资产,要么影响系统稳定性。
普通页面浏览、普通按钮点击,不一定都应该进服务端日志。用户行为分析可以交给埋点系统,日志系统应该重点保留可排障、可审计、可追溯的信息。
一个产品里至少会有几类不同日志。
应用日志:业务代码产生的运行记录
访问日志:HTTP 请求、路径、状态码、耗时
错误日志:异常、堆栈、失败上下文
审计日志:谁在什么时候改了什么
安全日志:登录失败、权限拒绝、异常访问
任务日志:后台任务、队列、定时任务执行过程
它们可以进入同一个日志平台,但字段和保存时间不一定相同。
审计日志和安全日志通常要更稳定、更完整,不能随意删除;调试日志可以短期保留;访问日志量大,成本要重点控制。
日志一旦进入系统,就会被搜索、转发、备份、聚合和长期保存。
所以不要把敏感信息写进日志。
尤其不要记录:
密码
验证码
Access Token
Refresh Token
银行卡号
身份证件图片
完整手机号或邮箱
用户私密内容原文
第三方平台密钥
Cookie 和 Authorization Header
OWASP Logging Cheat Sheet 也强调,日志需要考虑安全、隐私和数据保护问题。
如果确实需要定位用户,可以记录内部 user_id、脱敏邮箱、订单号、团队 ID,而不是完整隐私数据。
如果你的日志只在服务器本地文件里,排查会很痛苦。
一旦有多台机器、多种服务、Serverless 函数、后台任务、容器实例,你需要一个集中位置查询日志。
早期可以先用平台自带能力:
Vercel Logs
Cloudflare Logs
Render Logs
Railway Logs
Fly.io Logs
云服务器控制台日志
等日志量和查询需求上来,再考虑专门平台:
ELK / OpenSearch
Grafana Loki
Datadog
Better Stack
New Relic
Sentry Logs
不要一开始就上很重的日志架构,但也不要等线上出事时才发现日志散在十几个地方。
结构化日志不代表每个字段都要成为索引。
在 Loki 这类系统里,标签会影响查询和存储模型。Grafana Loki 文档明确提醒,高基数字段会带来性能问题。
高基数字段通常包括:
user_id
request_id
order_id
email
ip
uuid
session_id
这些字段可以放在日志内容或结构化元数据里,但不一定适合作为顶层标签或索引字段。
早期你可以把低基数字段作为主要过滤条件:
service
environment
level
region
route
job_name
高基数字段用于精确搜索,不要让它们把日志系统拖垮。
日志成本通常不是单条日志贵,而是量大、留得久、查得频繁。
你需要提前定义保留策略:
debug 日志:本地或短期
info 日志:7 到 30 天
error 日志:30 到 90 天
审计日志:按合规和业务要求长期保存
安全日志:按安全策略更长保存
访问日志:按流量和成本单独设计
具体时间没有统一答案,但一定要有规则。
没有保留策略的日志系统,最后会变成成本黑洞。
日志可以产生告警,但不要每条 error 都直接报警。
更好的做法是按规则聚合:
5 分钟内登录失败暴增
支付 webhook 连续失败
同一个任务重试多次仍失败
错误率超过阈值
某个第三方接口超时比例升高
出现安全相关事件
日志负责记录事实,告警负责提醒你“需要处理”。如果告警太吵,团队很快就会无视它。
如果你是独立开发者,可以从这个方案开始:
1. 所有服务输出 JSON 结构化日志
2. 每次请求生成 request_id
3. 日志字段包含 service、env、level、user_id、route、duration_ms
4. 支付、登录、权限、Webhook、后台任务必须打日志
5. 禁止记录密码、Token、完整隐私信息
6. 日志先进入部署平台自带日志系统
7. error 和关键业务失败接入告警
8. 日志保留时间按类型区分
这套方案不重,但足够让你在早期产品里定位大部分问题。
日志系统不是为了“看起来专业”,而是为了降低线上问题的不确定性。
早期最重要的不是工具,而是字段、级别、关键事件、请求关联、敏感信息控制和集中查询。
一个好日志系统的标准很简单:当用户说“刚才失败了”,你能在几分钟内找到那次请求、看到经过的步骤、判断失败原因,并知道下一步该修哪里。
下一篇,我们继续聊线上稳定性:错误监控怎么做。