日志与告警
🎯 引言
前面几篇解决了「数据怎么收」,本篇解决「数据怎么让人看到」:用日志分级让记录可查,用告警规则让异常主动通知到人,而不是出了问题才去翻日志。
学完本篇,你将能够:
- 正确使用 debug、info、warn、error 四个日志级别。
- 封装一个带级别控制和采样能力的简易 logger。
- 设计阈值告警和同比环比告警规则,并避开告警疲劳。
🧱 日志是什么,和埋点有什么区别
日志是程序运行时主动留下的文字记录,回答「程序此刻在做什么、状态如何」,主要给开发和运维看;第三篇讲的埋点回答「用户做了什么」,主要给产品和运营看。打个比方:埋点像商场里统计客流和购买行为的摄像头,关心「人」;日志像设备的运行记录仪,关心「机器转得正不正常」。两者配合,才能完整还原线上发生了什么。
🧱 日志分级:四个级别的使用场景
线上项目一天可能产生大量日志,不分轻重堆在一起,出问题只能一条条翻。按严重程度分级后,排查时先看 error 就能快速锁定方向。
级别从低到高是 debug → info → warn → error:
- debug:开发阶段的调试细节,如变量的当前值。量大、价值低,线上环境通常关闭。
- info:关键流程正常推进的记录,如「用户登录成功」,事后可顺着它还原一次操作的过程。
- warn:没出错但不对劲,如「接口超时后自动重试」「参数缺失使用了默认值」,值得回头关注。
- error:功能确实失败,如接口报错、提交失败,通常要接告警通知到人。
🛠 封装一个简易 logger
到处散写 console.log 有两个隐患:上线后想批量关闭低级别日志要逐文件改,而且打印内容只存在用户浏览器里,出了问题你拿不到。封装统一的 logger 可以把级别控制、采样和上报集中管理。下面的实现不依赖任何第三方包,浏览器直接可用:
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; // 数字越大越严重
const minLevel = 'info'; // 允许输出的最低级别,线上设为 'info' 即可关掉 debug
function log(level, message, extra) {
if (LEVELS[level] < LEVELS[minLevel]) {
return; // 级别不够,直接丢弃
}
const record = { level, message, extra, time: new Date().toISOString() };
console[level](`[${level}]`, message, extra ?? '');
if (LEVELS[level] >= LEVELS.warn) {
report(record); // warn 和 error 额外上报到服务端
}
}
function report(record) {
// 用 sendBeacon 上报,页面关闭时也能发出去
navigator.sendBeacon('/api/logs', JSON.stringify(record));
}
export const logger = {
debug: (msg, extra) => log('debug', msg, extra),
info: (msg, extra) => log('info', msg, extra),
warn: (msg, extra) => log('warn', msg, extra),
error: (msg, extra) => log('error', msg, extra),
};
使用时和 console 一样自然,如 logger.error('登录接口报错', { status: 500 }),但每条日志都带上了级别和时间。想关掉某个级别,只改 minLevel 这一个配置,业务代码不用动。生产项目里常用 Sentry 自带的日志和上报能力,接入方式见上一篇《Sentry 接入实践》。
💡 采样:控制日志量
高频低价值的日志(如每次操作都产生的 info)全量上报既浪费带宽,也会淹没重要信息,可以按比例抽样保留一部分:
// 采样上报:只有 10% 的日志真正发送
function reportWithSample(record, rate = 0.1) {
if (Math.random() < rate) {
report(record);
}
}
使用原则一句话:warn 和 error 全量上报,一条不丢;高频的 info、debug 才采样。
🧱 告警规则设计
告警就是按规则持续检查指标、发现异常就发通知的机制。常用的规则有两种思路。
阈值告警:超过固定的线就报警。
适合有明确安全线的指标,比如「一分钟内 error 日志超过 50 条」就触发:
// 最近一分钟的 error 数量超过 50 条就告警
function checkErrorCount(errorCountInLastMinute) {
if (errorCountInLastMinute > 50) {
sendAlert(`一分钟内错误数达到 ${errorCountInLastMinute} 条,请尽快排查`);
}
}
短板是线不好定:白天和凌晨流量差异大,用同一条线,不是白天误报就是凌晨漏报。
同比环比告警:不盯固定线,和自己的历史值比。
- 环比:和上一个周期比,如「这一小时 vs 上一小时」。
- 同比:和上周期的同一时段比,如「今天上午 10 点 vs 昨天上午 10 点」。
比如「本小时错误数比昨天同一小时上涨了 3 倍」就告警。这样白天和凌晨各有各的参照系,正常的流量波动不容易误报,突然异常却藏不住:
// 同比告警:和昨天同一时段对比,涨幅超过 3 倍就告警
function checkBySamePeriod(todayCount, yesterdayCount) {
const hasBaseline = yesterdayCount > 0;
const ratio = hasBaseline ? todayCount / yesterdayCount : 0;
if (hasBaseline && ratio > 3) {
sendAlert(`错误数同比上涨 ${Math.round(ratio)} 倍,请尽快排查`);
}
}
🧰 告警发到哪里去
常见渠道有邮件、企业微信群机器人、飞书群机器人等;已接入 Sentry 的项目也可以直接在它的控制台配置 Alert Rule,不用自己写这部分代码。以群机器人为例,平台会给你一个 webhook 地址,往它发一条 POST 请求,消息就会出现在群里:
function sendAlert(text) {
fetch('https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的key', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ msgtype: 'text', text: { content: text } }),
});
}
🪤 小心告警疲劳
如果告警群一天弹几百条消息、大多又不需要处理,大家很快会把它设成免打扰,真正重要的告警反而被淹没,这就是告警疲劳。几条应对经验:
- 每条告警都必须可行动:收到的人有明确的事情可做,否则降级成普通报表。
- 同类告警要合并:同一接口一分钟报错 100 次,发一条汇总即可。
- 加冷却时间:同一问题告警后的一段时间内(如 10 分钟)不再重复通知。
- 分级通知:warn 发群里留意,error 才打电话或发短信。
🧾 小节总结
- 日志记录「程序发生了什么」,埋点记录「用户做了什么」,两者分工不同、互相配合。
- 日志分 debug、info、warn、error 四级,线上通常关闭 debug,warn 和 error 要上报;封装统一 logger 集中管理级别控制、采样和上报。
- 采样用来控制日志量:高频低价值的日志按比例抽样,warn 和 error 必须全量保留。
- 阈值告警管「绝对灾难」,同比环比告警管「异常波动」,实际项目常搭配使用。
- 告警要可行动、要合并、要有冷却,否则会陷入告警疲劳,真正的问题反而被忽略。
❓ 知识问答
Q1:warn 和 error 怎么区分?
A:看功能是否失败。功能失败、用户操作没走通,用 error;功能还能跑但过程中有异常迹象(重试、降级、用了默认值),用 warn。
Q2:采样会不会漏掉关键问题?
A:关键是只对高频低价值的日志采样,error 和 warn 永远全量。另外采样丢失的是数量精度而不是有无:1000 条同类 info 采到 100 条,照样能看出流程在正常运转。
Q3:告警阈值定多少合适?
A:没有标准答案,建议先观察。上线初期阈值定宽松些、只记录不通知,积累一两周数据后按「正常波动范围的上沿」定线,再根据误报漏报持续调整。
🧪 小练习
练习一:给下面的 logger 补上采样逻辑,要求 info 级别按 20% 的比例上报,error 级别全量上报:
function report(level, record) {
// 请在这里编写代码:info 按 20% 采样,error 全量上报
navigator.sendBeacon('/api/logs', JSON.stringify(record));
}
练习二:补全这个环比告警函数,要求「本周期的错误数比上一周期上涨超过 2 倍」时触发告警,注意处理上一周期为 0 的情况:
function checkByLastPeriod(currentCount, lastCount) {
// 请在这里编写代码:环比上涨超过 2 倍时调用 sendAlert
}
🎉 恭喜你已经掌握日志与告警技能啦!到这里,前端监控课程就全部完成了,回到 前端监控课程 复习一遍,挑一个自己的项目动手实践吧!
