静态站点告警看板:基于Mermaid + 原生JS的轻量可视化实现
【摘要】 背景前面我们已经实现了埋点采集、日志聚合、阈值环比告警、Webhook推送。但告警消息只推送到IM里有一个短板:没办法直观看到指标趋势、历史告警记录、异常分布,每次排查都要翻原始日志。很多成熟方案会引入Prometheus+Grafana,但对于纯静态站点、无常驻后端的场景太重,维护成本高。我们希望:不引入数据库、不部署额外服务,基于已产出的日志JSON,做一个纯前端、可打包进项目仓库的告...
背景
前面我们已经实现了埋点采集、日志聚合、阈值环比告警、Webhook推送。但告警消息只推送到IM里有一个短板:没办法直观看到指标趋势、历史告警记录、异常分布,每次排查都要翻原始日志。
很多成熟方案会引入Prometheus+Grafana,但对于纯静态站点、无常驻后端的场景太重,维护成本高。我们希望:不引入数据库、不部署额外服务,基于已产出的日志JSON,做一个纯前端、可打包进项目仓库的告警看板。
核心约束:
- 全部静态运行,可直接在浏览器打开;
- 复用之前Node脚本输出的日志与告警结果;
- 使用Mermaid渲染时序/告警拓扑图,降低绘图代码量;
- 保留基础筛选:指标类型、时间区间、告警等级;
- 可以和CI联动,每次构建刷新看板数据。
整体架构
日志分析脚本 → 输出 metrics.json + alerts.json
↓
静态看板页面(HTML + JS + Mermaid)
↓
渲染趋势图表、告警列表、异常链路图
metrics.json:时序指标,LCP、PV、错误数、环比变化率;alerts.json:历史告警,包含触发时间、阈值、告警等级、关联日志ID;- Mermaid负责可视化,原生JS做数据加载、过滤、排序。
核心代码实现
1. 看板页面 index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>站点指标告警看板</title>
<script src="[https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js](https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js)"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:system-ui}
.container{max-width:1200px;margin:24px auto;padding:0 16px}
.card{border:1px solid #e5e7eb;border-radius:8px;padding:16px;margin-bottom:16px}
.alert-critical{border-left:4px solid #ef4444}
.alert-warning{border-left:4px solid #f59e0b}
.filter-bar{display:flex;gap:12px;margin-bottom:20px;align-items:center}
pre{white-space:pre-wrap;font-size:12px;color:#666}
</style>
</head>
<body>
<div class="container">
<h2>静态站点指标 & 告警看板</h2>
<div class="filter-bar">
<select id="levelFilter">
<option value="all">全部告警等级</option>
<option value="critical">严重</option>
<option value="warning">警告</option>
</select>
</div>
<div class="card">
<h3>指标时序图</h3>
<div class="mermaid" id="metricChart"></div>
</div>
<div class="card">
<h3>告警链路拓扑</h3>
<div class="mermaid" id="alertGraph"></div>
</div>
<div class="card">
<h3>告警记录列表</h3>
<div id="alertList"></div>
</div>
</div>
<script>
mermaid.initialize({startOnLoad:false});
let metrics = [];
let alerts = [];
// 加载数据
async function loadData(){
const mRes = await fetch('./metrics.json');
metrics = await mRes.json();
const aRes = await fetch('./alerts.json');
alerts = await aRes.json();
renderChart();
renderGraph();
renderAlerts();
}
// 渲染时序图
function renderChart(){
const times = metrics.map(i => i.time);
const lcp = metrics.map(i => i.lcp);
const pv = metrics.map(i => i.pv);
const mmdText = `
xychart-beta
title "LCP 与访问量时序"
x-axis [${times.join(',')}]
bar [${pv.join(',')}]
line [${lcp.join(',')}]
`;
document.getElementById('metricChart').innerText = mmdText;
mermaid.run({nodes:[document.getElementById('metricChart')]});
}
// 告警关联拓扑
function renderGraph(){
const items = alerts.slice(0,6);
let lines = ['graph LR'];
items.forEach((item,idx)=>{
lines.push(`A${idx}[${item.time}:${item.title}]:::${item.level}`);
})
lines.push(`classDef critical fill:#fee2e2,stroke:#ef4444`);
lines.push(`classDef warning fill:#fffbeb,stroke:#f59e0b`);
document.getElementById('alertGraph').innerText = lines.join('\n');
mermaid.run({nodes:[document.getElementById('alertGraph')]});
}
// 告警列表渲染
function renderAlerts(){
const filterVal = document.getElementById('levelFilter').value;
const container = document.getElementById('alertList');
container.innerHTML = '';
const filtered = filterVal === 'all' ? alerts : alerts.filter(x=>x.level === filterVal);
filtered.forEach(alert=>{
const div = document.createElement('div');
div.className = `card alert-${alert.level}`;
div.innerHTML = `
<div><strong>${alert.time}【${alert.level}】</strong> ${alert.title}</div>
<pre>${alert.detail}</pre>
`
container.appendChild(div);
})
}
document.getElementById('levelFilter').onchange = renderAlerts;
loadData();
</script>
</body>
</html>
2. Node 脚本:生成 metrics.json / alerts.json(集成到原有分析脚本)
// 追加写入看板所需文件
const fs = require('fs');
function exportDashboardData(metricList, alertList){
fs.writeFileSync('./metrics.json', JSON.stringify(metricList, null, 2));
fs.writeFileSync('./alerts.json', JSON.stringify(alertList, null, 2));
console.log('[看板数据导出完成] metrics.json, alerts.json');
}
module.exports = { exportDashboardData };
工程权衡
- 优点
- 零服务、零数据库,直接静态托管;
- Mermaid 声明式绘图,不用维护大量Canvas绘图逻辑;
- 和原有日志告警链路打通,CI每次执行自动刷新看板数据;
- 可以直接放进仓库,团队成员打开页面就能看历史异常。
- 局限
- 不适合超大日志量,数据量大之后JSON加载卡顿;
- 没有自动下钻,复杂的多维度分析能力弱于专业监控;
- 图表交互能力有限,Mermaid图表只适合概览,不适合精细拖拽缩放。
优化与演进方向
- 增加数据分页、懒加载,避免一次性加载全量历史日志;
- 增加告警静默、标记已处理状态,给告警加
resolved字段; - 增加导出CSV按钮,方便把指标导入表格做深度复盘;
- 增加深色模式适配,适合长时间看告警看板;
- 后续可以接入简单本地IndexedDB,在浏览器端缓存历史指标,不用每次拉取全量JSON。
CI 集成要点
在之前的CI流水线,日志分析脚本执行完成后,自动调用 exportDashboardData,然后把看板HTML + 两个JSON文件一起打包发布,访问路径和站点本身在同一个静态域名下,CSP配置放开Mermaid CDN即可。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)