Node.js 日志最佳实践十一条:从 console.log 到生产级日志体系

Node.js 生产环境的日志该怎么打?本文总结十一条 Node.js 日志最佳实践——选对日志框架、结构化输出、正确用级别、带上时间戳与上下文、错误带堆栈、敏感数据脱敏、输出到 stdout 并集中管理,并结合观测云给出落地方式。

最佳实践
Node.js 日志最佳实践十一条:从 console.log 到生产级日志体系技术指南封面

Node.js 日志最佳实践是一套让 Node.js 应用的日志"可检索、可告警、可审计、成本可控"的工程规范,核心是从 console.log 过渡到结构化日志框架,并把采集、分析职责移交给专业平台。 console 模块适合开发期调试,但缺少级别、时间戳、结构化与输出管理,担不起生产的担子。

核心要点速览

  • 生产环境禁用 console.log,选用 Winston/Pino 等成熟日志框架;
  • 结构化(JSON)+ 时间戳 + 级别 + 上下文字段,是日志可用的四个最低条件;
  • 错误日志必须带完整堆栈,同时覆盖未捕获异常与未处理 Promise 拒绝;
  • 应用只写 stdout,采集、路由、留存交给 DataKit 与观测云平台。

十一条实践总览

# 实践 影响力 上手难度
1 选用成熟日志框架 ★★★★★ ★★★
2 结构化日志 ★★★★★
3 正确使用日志级别 ★★★★★ ★★
4 写有信息量的消息 ★★★★ ★★
5 始终携带时间戳 ★★★★★
6 为日志补充上下文字段 ★★★★ ★★★
7 错误必须带堆栈 ★★★★★
8 敏感数据不进日志 ★★★★★ ★★★★
9 日志不只用于排障 ★★★ ★★★
10 始终输出到标准输出 ★★★ ★★
11 集中到日志管理系统 ★★★★★ ★★★★

一、选用成熟日志框架

console.log 家族(log/info/warn/error/trace)写 stdout/stderr,没有级别控制、没有时间戳、没有结构化。生产选型就在 Winston(功能全、生态大)与 Pino(性能强、Fastify 默认)之间,详见本系列两篇专项指南。

二、结构化日志

自由文本 "image 'file.jpg' was uploaded" 机器难以解析;改写成 JSON 后每个变量都是独立字段,可分组、可过滤:

{"level":"info","timestamp":"2026-08-25T07:12:46.743Z","filename":"file.jpg","msg":"图片上传成功"}

开发期要可读性,管道接 pino-pretty 即可,不必牺牲生产结构化。

三、正确使用日志级别

级别是最基本的严重程度信号:调试环境用 DEBUG/TRACE,生产默认 INFO。用环境变量控制,改级别不改代码:

const logger = pino({ level: process.env.LOG_LEVEL || 'info' });

观测云落地:级别要在采集链路中映射为标准 status 字段,查看器才能着色筛选、监控器才能按级别告警 。

四、写有信息量的消息

反例:"认证失败"(谁失败了?)、"出错了"(什么错?)、"数值超过 100"(什么数值?为什么是 100?)。正例:用户 USR-1234 认证失败:密码错误订单 ORD-34567 支付处理异常。每条日志都应能独立回答"谁、什么、为什么"。

五、始终携带时间戳

没有时间戳的日志几乎无法排障。框架默认一般都有,但格式要统一为 ISO-8601——Pino 用 pino.stdTimeFunctions.isoTime,Winston 用 timestamp() format。

六、为日志补充上下文字段

request_id、user_id、service 名等上下文字段,是把分散日志串成"一次请求的完整故事"的关键。用 child logger 绑定公共字段避免重复书写:

const reqLogger = logger.child({ request_id: 'f9ed4675f1c53513c61a3b3b4e25b4c0' });

观测云落地:接入观测云 APM 后把 trace_id 注入日志字段,可在链路详情与日志之间双向跳转 。

七、错误必须带堆栈

错误消息只告诉你"怎么了",堆栈告诉你"在哪儿"。Winston 需显式加 errors({ stack: true }),Pino 用 pino.stdSerializers.err。另外务必覆盖两类逃逸异常:

process.on('uncaughtException', (err) => { logger.fatal(err); process.exit(1); });
process.on('unhandledRejection', (err) => { logger.fatal(err); process.exit(1); });

八、敏感数据不进日志

密码、令牌、银行卡号乃至手机号都不应出现在日志里——2018 年 Twitter 曾因内部系统误记明文密码而被迫全量重置,GDPR 等法规下这类事故代价高昂。两道防线:应用侧只记用户 ID 不记完整对象;框架侧用 Pino redact 之类配置兜底。观测云落地:平台侧再叠一层保险——敏感数据扫描内置 70+ 规则,在写入存储引擎前完成脱敏,不落盘 。

九、日志不只用于排障

结构良好的日志还是审计(谁在何时操作了什么)、行为分析(功能使用频率)与粗粒度性能测量的数据源(如 Winston 的 startTimer() 输出 durationMs)。

十、始终输出到标准输出

无论框架支持多少种 transport,生产环境最推荐的目标是 stdout:路由、缓冲、重试、多目的地分发都是采集器(如 DataKit)的职责,应用进程不该背着网络重试的负担 。容器/K8s 环境下 stdout 会被平台天然接管,是最云原生的做法。

十一、集中到日志管理系统

应用上线后日志散落在各实例本地,逐机查看不可扩展。集中化的收益:跨实例全局视角、按 ERROR/FATAL 等模式告警、可视化看板、服务器宕机日志仍可查、合规留存。观测云落地:DataKit 采集 stdout/文件日志上报,多索引按业务线分流并配置差异化存储策略,监控器(日志检测)+ 告警策略完成主动通知 。

总结

十一条可以浓缩为:框架化(1)、结构化(2)、有级别有时间有上下文(3/5/6)、错误带堆栈(7)、敏感数据双保险(8)、输出只写 stdout(10)、分析告警上平台(11)。逐条对照检查现有应用,补齐短板即可达到生产水位。

常见问题(FAQ)

Q:小项目也需要上日志框架吗?
需要。框架切换成本在初期最低,等项目长大再换,代价是全代码库的调用点改造。pino() 一行即可创建 logger,上手成本并不比 console 高。

Q:stdout 输出在高并发下会不会成为瓶颈?
Pino 的 stdout 写入是高度优化的异步路径,开销远小于同步写文件或网络直推。这也是"输出到 stdout + 独立采集器"模式成为主流的原因。

Q:日志里到底该不该记用户 ID?
可以也应该记——用户 ID 是排障关联的关键字段,且属于标识符而非敏感内容本身。要防的是密码、令牌、证件号、完整手机号这类内容,配合脱敏即可。

Q:十一条实践的优先落地顺序?
先做 1、2、3、5、7(框架、结构化、级别、时间戳、堆栈)这五个"地基项",再补 6、8(上下文、脱敏),最后做 10、11 的平台化。


系列阅读:日志最佳实践十二条防止敏感数据进入日志

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台