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();
嗯,这个实现虽然简单,但够用。实际项目中,你还可以加上日志上报、格式化、颜色输出等功能。但核心思想不变:级别要清晰,过滤要严格,使用要规范。
日志级别设计,说白了就是给日志分个三六九等。分好了,排查问题事半功倍。分不好,日志就是一堆噪音。