3、日志级别设计:debug、info、warn、error、fatal的划分标准与使用场景

日志级别这东西,说白了就是给日志贴个标签。

告诉看日志的人:这条消息有多重要,需不需要立刻处理。

我见过不少团队,日志打得满天飞,结果线上出问题,翻半天找不到关键信息。为什么?级别没分清楚。

3.1 五个级别的核心定义

前端工程化里,我习惯把日志分成五级。从低到高,依次是:

级别 数值 含义 颜色标识
DEBUG 0 调试信息 灰色
INFO 1 常规信息 蓝色
WARN 2 警告 黄色
ERROR 3 错误 红色
FATAL 4 致命错误 深红/紫色

数值越小,优先级越低。生产环境通常只显示 WARN 及以上级别。

3.2 每个级别的使用场景

DEBUG:开发者的私房话

DEBUG 级别的日志,是写给开发者自己看的。

比如某个函数的入参、某个中间变量的值、某个异步请求的返回结果。这些信息在开发调试时很有用,但上线后基本没人关心。

使用场景:

  • 组件渲染过程中的状态变化
  • API 请求的详细参数和响应
  • 复杂计算过程的中间结果
  • 条件分支的走向判断
// 示例:DEBUG 日志
logger.debug('用户信息请求参数', { userId: 123, page: 1 });
logger.debug('组件渲染完成,耗时', `${renderTime}ms`);

我个人习惯,DEBUG 日志只在开发环境开启。生产环境默认关闭,除非临时排查问题。

INFO:系统的日常汇报

INFO 级别的日志,记录的是系统正常运行时的关键节点。

说白了,就是告诉运维和开发:系统还活着,一切正常。

使用场景:

  • 用户登录成功
  • 页面初始化完成
  • 数据加载完成
  • 定时任务执行
// 示例:INFO 日志
logger.info('用户登录成功', { userId: 123, loginTime: Date.now() });
logger.info('首页数据加载完成,共加载', `${count}条记录`);

INFO 日志不要打太多。你想想看,如果每分钟产生几千条 INFO,那跟没打有什么区别?

WARN:出问题了,但还能忍

WARN 级别的日志,表示系统遇到了异常情况,但业务还能继续。

它不像 ERROR 那么严重,但也不能忽视。

注意:WARN 是很多团队容易忽略的级别。我见过有人把所有异常都打成 ERROR,结果 ERROR 日志泛滥,真正严重的问题反而被淹没了。

使用场景:

  • API 接口返回了非预期的状态码(如 429 限流)
  • 用户输入了不合法的数据,但前端做了兜底处理
  • 某个功能降级了,但核心流程不受影响
  • 资源加载超时,但重试成功了
// 示例:WARN 日志
logger.warn('图片加载超时,已启用默认占位图', { imgUrl: 'xxx.jpg' });
logger.warn('接口返回限流状态码,已启用缓存数据', { api: '/user/info' });

我曾经在一个项目中,把 WARN 当成 ERROR 来用。结果线上出了真正的错误,运维翻日志翻了半天,全是 WARN 的假警报。嗯,从那以后我严格区分了这两个级别。

ERROR:真的出事了

ERROR 级别的日志,表示系统发生了错误,影响了某个功能或流程。

需要立刻有人去处理。

使用场景:

  • API 请求失败,且重试也失败了
  • JavaScript 运行时抛出了未捕获的异常
  • 关键数据加载失败,导致页面白屏
  • 用户操作触发了不可恢复的错误
// 示例:ERROR 日志
logger.error('用户信息加载失败', { 
  userId: 123, 
  error: err.message,
  stack: err.stack 
});
logger.error('支付接口调用异常', { 
  orderId: 'ORD2024xxx',
  errorCode: 'PAY_TIMEOUT' 
});

我的建议:ERROR 日志一定要带上堆栈信息和上下文。光说「出错了」没用,得告诉别人「哪里出错了」和「当时发生了什么」。

FATAL:系统要挂了

FATAL 级别的日志,是最严重的。

它表示系统已经无法正常运行,必须立即停机排查。

注意:FATAL 日志一旦出现,通常意味着整个应用需要重启。前端场景下,FATAL 比较少见,但一旦出现就是大事。

使用场景:

  • WebSocket 连接彻底断开,且无法重连
  • 关键依赖(如 SDK)初始化失败
  • 内存泄漏导致页面崩溃
  • 安全校验失败,可能遭受攻击
// 示例:FATAL 日志
logger.fatal('WebSocket 连接永久断开,页面将无法使用', { 
  reconnectAttempts: 5,
  lastError: err.message 
});
logger.fatal('安全校验失败,疑似 CSRF 攻击', { 
  userId: 123,
  token: 'invalid',
  ip: '192.168.1.1' 
});

3.3 级别过滤:生产环境该看什么

不同环境,日志的可见级别不一样。这是工程化的基本要求。

环境 可见级别 说明
开发环境 DEBUG 及以上 所有日志都显示,方便调试
测试环境 INFO 及以上 过滤掉 DEBUG,关注业务逻辑
预发布环境 WARN 及以上 模拟生产环境,只关注异常
生产环境 WARN 及以上 默认只显示警告和错误

你想想看,如果生产环境把 DEBUG 日志也打开了,那日志量会爆炸。不仅浪费存储,还会拖慢性能。

3.4 避坑指南:我踩过的几个坑

做日志级别设计这么多年,我总结了几条经验:

  • 不要滥用 ERROR:我曾经把接口超时都打成 ERROR,结果线上 ERROR 日志一天几万条,真正的 Bug 反而被淹没了。后来改成 WARN,效果好了很多。
  • FATAL 要配合告警:FATAL 级别的日志,一定要触发告警。短信、电话、钉钉都行。别打了 FATAL 没人知道。
  • DEBUG 日志要加开关:生产环境偶尔需要 DEBUG 日志排查问题,但平时必须关掉。我习惯在日志工具里加一个动态开关,通过 URL 参数或配置中心控制。
  • 日志级别要统一:整个团队用同一套标准。别张三把超时当 WARN,李四当 ERROR。不然看日志的人会疯掉。

3.5 代码实现:一个简单的日志级别过滤器

最后,给一个简单的实现示例。这是我在项目中常用的写法:

// 日志级别枚举
const LogLevel = {
  DEBUG: 0,
  INFO: 1,
  WARN: 2,
  ERROR: 3,
  FATAL: 4
};

// 根据环境设置可见级别
const getVisibleLevel = () => {
  const env = process.env.NODE_ENV;
  if (env === 'development') return LogLevel.DEBUG;
  if (env === 'test') return LogLevel.INFO;
  return LogLevel.WARN; // 生产环境默认 WARN 及以上
};

// 日志过滤器
class Logger {
  constructor() {
    this.visibleLevel = getVisibleLevel();
  }

  log(level, message, data) {
    if (level >= this.visibleLevel) {
      // 实际输出日志
      console.log(`[${level}] ${message}`, data);
    }
  }

  debug(msg, data) { this.log(LogLevel.DEBUG, msg, data); }
  info(msg, data) { this.log(LogLevel.INFO, msg, data); }
  warn(msg, data) { this.log(LogLevel.WARN, msg, data); }
  error(msg, data) { this.log(LogLevel.ERROR, msg, data); }
  fatal(msg, data) { this.log(LogLevel.FATAL, msg, data); }
}

export default new Logger();

嗯,这个实现虽然简单,但够用。实际项目中,你还可以加上日志上报、格式化、颜色输出等功能。但核心思想不变:级别要清晰,过滤要严格,使用要规范

日志级别设计,说白了就是给日志分个三六九等。分好了,排查问题事半功倍。分不好,日志就是一堆噪音。