本文将详细介绍如何为前端监控平台设计并实现一套完整的邮件告警系统,包括架构设计、核心原理、代码实现和最佳实践。
一、为什么需要告警系统?
在前端监控平台中,我们通常会采集大量的错误和性能数据。但如果只是被动地等待开发者登录 Dashboard 查看,很多问题可能已经影响了大量用户。
告警系统的核心价值:
- 🚨 实时感知:错误发生时第一时间通知相关人员
- 🎯 精准触达:根据规则过滤,避免告警轰炸
- ⏰ 快速响应:缩短问题发现到修复的时间窗口
二、整体架构设计
scss
┌─────────────────────────────────────────────────────────────────┐
│ 前端应用 │
│ (SDK 错误采集) │
└─────────────────────┬───────────────────────────────────────────┘
│ HTTP POST /api/report
▼
┌─────────────────────────────────────────────────────────────────┐
│ Server 层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 数据接收 │──▶│ 错误聚合 │──▶│ 告警检查 │ │
│ │ (report) │ │ (fingerprint)│ │ (rules) │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 规则评估 │ │
│ │ (evaluate) │ │
│ └──────┬──────┘ │
│ │ 触发 │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 告警历史 │◀──│ 邮件发送 │◀──│ 冷却检查 │ │
│ │ (history) │ │ (SMTP) │ │ (cooldown) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼ SMTP
┌─────────────────────────────────────────────────────────────────┐
│ 邮件服务器 │
│ (QQ邮箱/163/企业邮箱) │
└─────────────────────────────────────────────────────────────────┘
│
▼ 📧
开发者邮箱
三、核心模块设计
3.1 告警规则模型
告警规则是整个系统的核心,定义了"什么情况下触发告警":
typescript
interface AlertRule {
id: number;
dsn: string; // 项目标识
name: string; // 规则名称
type: AlertType; // 告警类型
enabled: boolean; // 是否启用
threshold?: number; // 阈值
timeWindow?: number; // 时间窗口(分钟)
recipients: string[]; // 收件人列表
cooldown: number; // 冷却时间(分钟)
}
type AlertType =
| 'new_error' // 新错误首次出现
| 'error_threshold' // 错误累计次数超过阈值
| 'error_spike'; // 时间窗口内错误激增
三种告警类型的适用场景:
| 类型 | 场景 | 示例 |
|---|---|---|
| new_error | 捕获未知错误 | 新上线功能出现 bug |
| error_threshold | 监控已知问题 | 某接口错误超过 100 次 |
| error_spike | 检测异常波动 | 5 分钟内错误数突增 |
3.2 冷却机制
为了避免同一个错误短时间内重复告警(告警轰炸),我们引入了冷却机制:
typescript
// 内存缓存:记录最近告警时间
const alertCooldowns = new Map<string, number>();
function shouldTrigger(rule: AlertRule, fingerprint: string): boolean {
const cooldownKey = `${rule.id}-${fingerprint}`;
const lastAlert = alertCooldowns.get(cooldownKey);
// 检查是否在冷却期内
if (lastAlert && Date.now() - lastAlert < rule.cooldown * 60 * 1000) {
return false; // 冷却中,不触发
}
return true;
}
// 触发告警后更新冷却时间
function updateCooldown(rule: AlertRule, fingerprint: string) {
const cooldownKey = `${rule.id}-${fingerprint}`;
alertCooldowns.set(cooldownKey, Date.now());
}
冷却机制的关键点:
- 按规则+错误指纹组合作为冷却 key,而不是全局冷却
- 使用内存 Map 存储,重启后冷却状态重置(可接受)
- 冷却时间可配置,建议默认 30 分钟
3.3 规则评估引擎
规则评估是告警系统的"大脑",决定是否触发告警:
typescript
async function evaluateRule(rule: AlertRule, errorData: ErrorData): Promise<boolean> {
// 1. 先检查冷却
if (!shouldTrigger(rule, errorData.fingerprint)) {
return false;
}
// 2. 根据规则类型评估
switch (rule.type) {
case 'new_error':
// 新错误:检查是否首次出现
return errorData.isNew;
case 'error_threshold':
// 阈值:检查累计次数
return errorData.count >= rule.threshold;
case 'error_spike': {
// 激增:查询时间窗口内的错误数
const windowStart = Date.now() - rule.timeWindow * 60 * 1000;
const recentCount = await getErrorCountSince(
errorData.dsn,
errorData.fingerprint,
windowStart
);
return recentCount >= rule.threshold;
}
default:
return false;
}
}
四、邮件服务实现
4.1 Nodemailer 配置
使用 nodemailer 库发送邮件,支持主流 SMTP 服务:
typescript
import nodemailer, { Transporter } from 'nodemailer';
let transporter: Transporter | null = null;
function initEmailService(config: EmailConfig) {
transporter = nodemailer.createTransport({
host: config.host, // smtp.qq.com
port: config.port, // 465
secure: true, // 使用 SSL
auth: {
user: config.user, // 邮箱账号
pass: config.pass // 授权码(不是登录密码!)
}
});
}
常见 SMTP 配置:
| 服务商 | Host | Port | 备注 |
|---|---|---|---|
| QQ邮箱 | smtp.qq.com | 465 | 需开启 SMTP 服务,使用授权码 |
| 163邮箱 | smtp.163.com | 465 | 需开启 SMTP 服务 |
| Gmail | smtp.gmail.com | 587 | 需开启两步验证,使用应用密码 |
| 企业微信 | smtp.exmail.qq.com | 465 | 企业邮箱 |
4.2 邮件模板设计
告警邮件需要清晰展示关键信息:

邮件设计要点:
- 使用内联样式(邮件客户端不支持外部 CSS)
- 关键信息突出显示(错误消息、次数)
- 提供快捷操作入口(查看详情按钮)
- 移动端适配(响应式布局)
4.3 发送邮件
typescript
async function sendAlertEmail(data: AlertEmailData): Promise<boolean> {
if (!transporter) {
console.warn('[Email] Service not initialized');
return false;
}
try {
const info = await transporter.sendMail({
from: '"Sentinel 监控" <monitor@example.com>',
to: data.recipients.join(', '),
subject: `🚨 [${data.ruleName}] ${data.errorMessage.slice(0, 50)}`,
html: generateAlertEmailHtml(data)
});
console.log('[Email] Sent:', info.messageId);
return true;
} catch (error) {
console.error('[Email] Failed:', error);
return false;
}
}
五、完整告警流程
5.1 错误上报时触发检查
在错误数据入库后,异步检查告警规则:
typescript
// routes/report.ts
async function saveErrorEvent(dsn: string, event: ErrorEvent) {
const { fingerprint } = generateFingerprint(event);
// 1. 检查是否新错误
const existing = await db.query(
'SELECT id, count FROM errors WHERE fingerprint = $1',
[fingerprint]
);
const isNew = existing.rows.length === 0;
const count = isNew ? 1 : existing.rows[0].count + 1;
// 2. 保存/更新错误记录
if (isNew) {
await db.query('INSERT INTO errors ...', [...]);
} else {
await db.query('UPDATE errors SET count = $1 ...', [count]);
}
// 3. 异步检查告警(不阻塞响应)
checkAndTriggerAlerts({
dsn,
type: event.type,
message: event.message,
fingerprint,
url: event.url,
isNew,
count
}).catch(err => console.error('[Alert] Check failed:', err));
}
5.2 告警检查主流程
typescript
async function checkAndTriggerAlerts(errorData: ErrorData) {
// 1. 检查邮件服务是否可用
if (!isEmailConfigured()) return;
// 2. 获取该项目的所有启用规则
const rules = await getAlertRules(errorData.dsn);
const enabledRules = rules.filter(r => r.enabled);
// 3. 逐个评估规则
for (const rule of enabledRules) {
const shouldTrigger = await evaluateRule(rule, errorData);
if (shouldTrigger) {
// 4. 触发告警
await triggerAlert(rule, errorData);
}
}
}
async function triggerAlert(rule: AlertRule, errorData: ErrorData) {
// 1. 发送邮件
const emailSent = await sendAlertEmail({
to: rule.recipients,
subject: `🚨 [${rule.name}] ${errorData.message.slice(0, 50)}`,
errorMessage: errorData.message,
errorType: errorData.type,
errorCount: errorData.count,
url: errorData.url,
timestamp: Date.now()
});
// 2. 记录告警历史
await saveAlertHistory(rule.id, errorData, emailSent);
// 3. 更新冷却时间
updateCooldown(rule, errorData.fingerprint);
console.log(`[Alert] Triggered: ${rule.name}, sent: ${emailSent}`);
}
六、数据库设计
6.1 告警规则表
sql
CREATE TABLE alert_rules (
id SERIAL PRIMARY KEY,
dsn TEXT NOT NULL, -- 项目标识
name VARCHAR(100) NOT NULL, -- 规则名称
type VARCHAR(20) NOT NULL, -- 告警类型
enabled BOOLEAN DEFAULT true, -- 是否启用
threshold INTEGER, -- 阈值
time_window INTEGER DEFAULT 60, -- 时间窗口(分钟)
recipients TEXT[] NOT NULL, -- 收件人数组
cooldown INTEGER DEFAULT 30, -- 冷却时间(分钟)
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_alert_rules_dsn ON alert_rules(dsn);
6.2 告警历史表
sql
CREATE TABLE alert_history (
id SERIAL PRIMARY KEY,
rule_id INTEGER REFERENCES alert_rules(id),
dsn TEXT NOT NULL,
fingerprint TEXT, -- 错误指纹
error_message TEXT, -- 错误消息
triggered_at TIMESTAMP DEFAULT NOW(), -- 触发时间
email_sent BOOLEAN DEFAULT false -- 邮件是否发送成功
);
CREATE INDEX idx_alert_history_dsn ON alert_history(dsn, triggered_at);
七、API 设计
7.1 告警规则 CRUD
typescript
// 获取规则列表
GET /api/alerts/rules?dsn=demo-app
// 创建规则
POST /api/alerts/rules
{
"dsn": "demo-app",
"name": "生产环境错误告警",
"type": "new_error",
"recipients": ["dev@example.com"],
"cooldown": 30
}
// 更新规则
PATCH /api/alerts/rules/:id
{
"enabled": false,
"threshold": 50
}
// 删除规则
DELETE /api/alerts/rules/:id
7.2 告警历史查询
typescript
// 获取告警历史
GET /api/alerts/history?dsn=demo-app&limit=50
// 响应
{
"history": [
{
"id": 1,
"ruleId": 1,
"errorMessage": "Cannot read property 'x' of undefined",
"triggeredAt": "2024-01-15T10:30:00Z",
"emailSent": true
}
]
}
7.3 邮件服务状态
typescript
// 检查邮件服务状态
GET /api/alerts/email-status
// { "configured": true, "connected": true }
// 发送测试邮件
POST /api/alerts/test-email
{ "email": "test@example.com" }
八、最佳实践
8.1 告警规则配置建议
typescript
// 推荐的规则组合
const recommendedRules = [
{
name: '新错误告警',
type: 'new_error',
cooldown: 60, // 1小时内同一错误不重复告警
recipients: ['oncall@team.com']
},
{
name: '错误激增告警',
type: 'error_spike',
threshold: 100, // 5分钟内超过100次
timeWindow: 5,
cooldown: 30,
recipients: ['oncall@team.com', 'manager@team.com']
},
{
name: '关键错误阈值',
type: 'error_threshold',
threshold: 1000, // 累计超过1000次
cooldown: 120, // 2小时冷却
recipients: ['dev@team.com']
}
];
8.2 避免告警疲劳
- 合理设置冷却时间:避免同一问题反复告警
- 分级告警:不同严重程度发送给不同人员
- 聚合告警:相似错误合并为一条告警
- 静默时段:非工作时间降低告警频率
8.3 邮件发送优化
typescript
// 使用队列异步发送,避免阻塞主流程
import { Queue } from 'bull';
const emailQueue = new Queue('email-alerts');
emailQueue.process(async (job) => {
await sendAlertEmail(job.data);
});
// 触发告警时加入队列
async function triggerAlert(rule, errorData) {
await emailQueue.add({
to: rule.recipients,
subject: `🚨 ${errorData.message}`,
// ...
});
}
九、扩展方向
当前实现了邮件告警,后续可以扩展:
-
多渠道通知
- 钉钉/飞书 Webhook
- 企业微信机器人
- Slack 集成
- 短信通知(严重告警)
-
智能告警
- 基于历史数据的异常检测
- 告警收敛和去重
- 根因分析关联
-
告警升级
- 未处理告警自动升级
- 值班表集成
- 告警认领机制
十、总结
本文介绍了前端监控告警系统的完整实现方案:
- 架构设计:错误上报 → 规则评估 → 冷却检查 → 邮件发送
- 核心机制:三种告警类型 + 冷却防抖 + 异步处理
- 技术选型:Node.js + Nodemailer + PostgreSQL
告警系统是监控平台的"最后一公里",让被动查看变为主动通知,大大提升了问题响应效率。
完整代码已开源:GitHub - Sentinel 前端监控平台
如果觉得有帮助,欢迎 Star ⭐️