一、日志系统概述:为什么需要日志系统、日志级别、核心设计原则

说实话,我刚开始写 Node.js 那会儿,对日志这事完全不上心。

觉得「console.log 打天下」就够用了。直到有一次线上服务半夜挂了,我盯着黑乎乎的终端,连错误栈都没留下…… 那叫一个抓瞎。

从那以后,我养成了一个习惯:任何上线的服务,日志系统必须优先设计

1.1 为什么需要日志系统?

你想想看,一个 Node.js 应用跑在生产环境,你没法像开发时那样打断点调试。出了问题怎么办?

日志就是你的「黑匣子」。它记录了系统运行过程中的一切关键信息。

我个人总结,日志系统至少解决三个核心问题:

  • 问题定位:线上报错了,是参数问题、数据库挂了、还是第三方接口超时?日志能告诉你真相。
  • 行为审计:谁在什么时候做了什么操作?尤其是金融、电商类项目,日志是合规的底线。
  • 性能分析:接口响应慢在哪一步?通过日志埋点,你能精确到毫秒级。

核心观点:没有日志的系统,就像蒙着眼睛开车。你永远不知道下一秒会撞上什么。

1.2 日志级别:从 debug 到 fatal

日志级别这东西,说白了就是给信息分个「轻重缓急」。我见过不少新手把所有日志都打成 info,结果排查问题时根本找不到重点。

Node.js 社区常用的日志级别,从低到高是这样的:

级别 含义 使用场景 生产环境是否输出
DEBUG 调试信息 开发阶段,打印变量值、函数调用栈
INFO 普通信息 记录系统正常运行状态,如「用户登录成功」「订单创建」
WARN 警告 潜在问题,但不影响当前功能。比如「磁盘使用率超过80%」
ERROR 错误 功能不可用,但系统还能继续运行。比如「数据库连接失败,已重试3次」
FATAL 致命错误 系统无法继续运行,必须立即停机。比如「内存耗尽」「配置文件损坏」

我的经验:DEBUG 日志在生产环境一定要关掉。我曾经见过一个项目,DEBUG 日志里打印了完整的 SQL 语句和用户密码哈希,这要是泄露出去…… 嗯,后果你懂的。

实际编码中,我们通常这样使用:

const logger = require('./logger');

// 调试阶段
logger.debug('用户输入参数:', req.body);

// 正常流程
logger.info('用户 %s 登录成功', userId);

// 潜在风险
logger.warn('第三方支付接口响应超时,已触发降级策略');

// 业务错误
logger.error('订单 %s 支付失败,原因:余额不足', orderId);

// 系统崩溃
logger.fatal('无法加载配置文件,系统退出');

1.3 日志系统的核心设计原则

设计一个日志系统,不是随便写个函数就完事了。我踩过不少坑,总结出下面几条原则:

原则一:结构化

别用纯文本。用 JSON 格式输出日志,这样后续分析、搜索、聚合都方便。

// 不推荐
[2024-01-15 10:30:00] ERROR 用户登录失败

// 推荐
{"timestamp":"2024-01-15T10:30:00.000Z","level":"ERROR","message":"用户登录失败","userId":12345,"ip":"192.168.1.1"}

原则二:可配置

日志级别、输出位置、格式,都应该通过配置文件控制。不要写死在代码里。

// config/logger.js
module.exports = {
  level: process.env.LOG_LEVEL || 'info',
  output: process.env.LOG_OUTPUT || 'file',  // console / file / elasticsearch
  format: process.env.LOG_FORMAT || 'json'
};

原则三:低开销

日志写入不能阻塞主流程。尤其是高并发场景,用异步写入、缓冲区、批量提交。

避坑指南:我曾经在某个高并发项目里,直接用 fs.writeFileSync 写日志。结果 QPS 一上来,整个进程卡死。后来改用 stream 和缓冲区,问题才解决。

原则四:上下文传递

每个请求的日志,应该能关联起来。比如带上 requestId、userId、traceId。

// 使用 AsyncLocalStorage 实现
const { AsyncLocalStorage } = require('async_hooks');
const storage = new AsyncLocalStorage();

// 中间件
app.use((req, res, next) => {
  const context = { requestId: uuid(), userId: req.user?.id };
  storage.run(context, () => next());
});

// 日志函数自动获取上下文
function log(level, message) {
  const ctx = storage.getStore() || {};
  console.log(JSON.stringify({ ...ctx, level, message }));
}

原则五:分级存储

不同级别的日志,存到不同的地方。DEBUG 日志可以本地保留3天,ERROR 日志要长期归档。

级别 存储位置 保留周期
DEBUG 本地文件 3天
INFO 本地文件 + 日志中心 30天
WARN 日志中心 + 告警 90天
ERROR 日志中心 + 即时告警 180天
FATAL 日志中心 + 电话告警 永久

1.4 小结

日志系统不是「锦上添花」,而是「雪中送炭」。

我见过太多项目,前期图省事用 console.log,后期线上出问题只能靠猜。说白了,日志系统设计得好不好,直接决定了你半夜被叫醒的次数。

嗯,这一章先聊到这。下一章我们动手实现一个 Node.js 日志库,从零开始写代码。

一句话总结:日志级别要分明,设计原则要牢记。别让「console.log」成为你职业生涯的绊脚石。


公众号:蓝海资料掘金营,微信deep3321