前端监控体系设计
前端监控体系设计
监控什么
前端监控分四大类:
前端监控
├── 1. 性能监控(页面加载快不快)
├── 2. 错误监控(有没有报错)
├── 3. 行为监控(用户在做什么)
└── 4. 接口监控(请求成功率、耗时)
1. 性能监控
采集指标
// utils/monitor/performance.ts
export function collectPerformance() {
// FCP
new PerformanceObserver((list) => {
const entry = list.getEntries()[0];
report({ type: "performance", name: "FCP", value: entry.startTime });
}).observe({ type: "paint", buffered: true });
// LCP
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
report({ type: "performance", name: "LCP", value: lastEntry.startTime });
}).observe({ type: "largest-contentful-paint", buffered: true });
// CLS
let clsScore = 0;
new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
clsScore += (entry as any).value;
});
report({ type: "performance", name: "CLS", value: clsScore });
}).observe({ type: "layout-shift", buffered: true });
// TTFB + 其他导航指标
window.addEventListener("load", () => {
const timing = performance.getEntriesByType(
"navigation",
)[0] as PerformanceNavigationTiming;
report({
type: "performance",
name: "TTFB",
value: timing.responseStart - timing.requestStart,
});
report({
type: "performance",
name: "DOMContentLoaded",
value: timing.domContentLoadedEventEnd,
});
report({ type: "performance", name: "Load", value: timing.loadEventEnd });
});
}
2. 错误监控
JS 运行时错误
// utils/monitor/error.ts
// 全局 JS 错误
window.addEventListener("error", (event) => {
report({
type: "js-error",
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
stack: event.error?.stack,
});
});
// Promise 未捕获错误
window.addEventListener("unhandledrejection", (event) => {
report({
type: "promise-error",
message: event.reason?.message || String(event.reason),
stack: event.reason?.stack,
});
});
资源加载错误(图片、JS、CSS 加载失败)
window.addEventListener(
"error",
(event) => {
const target = event.target as HTMLElement;
if (target.tagName) {
report({
type: "resource-error",
tagName: target.tagName,
src:
(target as HTMLImageElement).src || (target as HTMLScriptElement).src,
});
}
},
true,
); // 注意用捕获阶段,资源错误不冒泡
React 错误边界
// components/ErrorBoundary.tsx
import React from "react";
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
report({
type: "react-error",
message: error.message,
stack: error.stack,
componentStack: info.componentStack,
});
}
render() {
if (this.state.hasError) {
return <div>页面出错了,请刷新重试</div>;
}
return this.props.children;
}
}
3. 行为监控
PV / UV 统计
// 页面访问
export function trackPageView() {
report({
type: "pv",
url: window.location.href,
referrer: document.referrer,
timestamp: Date.now(),
});
}
用户行为路径(面包屑)
// 记录用户操作路径,出错时一起上报
const breadcrumbs: any[] = [];
document.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
breadcrumbs.push({
type: "click",
tagName: target.tagName,
text: target.innerText?.slice(0, 50),
path: getXPath(target),
timestamp: Date.now(),
});
// 只保留最近 20 条
if (breadcrumbs.length > 20) breadcrumbs.shift();
});
// 报错时带上行为路径
function reportWithBreadcrumbs(error: any) {
report({
...error,
breadcrumbs: [...breadcrumbs],
});
}
4. 接口监控
拦截 fetch / XMLHttpRequest
// 拦截 fetch
const originalFetch = window.fetch;
window.fetch = async (input, init) => {
const url = typeof input === "string" ? input : input.url;
const startTime = Date.now();
try {
const response = await originalFetch(input, init);
report({
type: "api",
url,
method: init?.method || "GET",
status: response.status,
duration: Date.now() - startTime,
success: response.ok,
});
return response;
} catch (error) {
report({
type: "api-error",
url,
method: init?.method || "GET",
duration: Date.now() - startTime,
message: (error as Error).message,
});
throw error;
}
};
数据上报
// utils/monitor/report.ts
const reportQueue: any[] = [];
let timer: ReturnType<typeof setTimeout> | null = null;
export function report(data: any) {
reportQueue.push({
...data,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
});
// 批量上报(每 5 秒或满 10 条)
if (reportQueue.length >= 10) {
flush();
} else if (!timer) {
timer = setTimeout(flush, 5000);
}
}
function flush() {
if (reportQueue.length === 0) return;
const data = [...reportQueue];
reportQueue.length = 0;
timer = null;
// 用 sendBeacon 上报(页面关闭时也能发出去)
if (navigator.sendBeacon) {
navigator.sendBeacon("/api/monitor", JSON.stringify(data));
} else {
fetch("/api/monitor", {
method: "POST",
body: JSON.stringify(data),
keepalive: true,
});
}
}
// 页面关闭时上报剩余数据
window.addEventListener("beforeunload", flush);
在项目中怎么接入
统一初始化入口
// utils/monitor/index.ts
import { collectPerformance } from "./performance";
import { initErrorMonitor } from "./error";
import { trackPageView } from "./behavior";
import { interceptFetch } from "./api";
export function initMonitor() {
collectPerformance();
initErrorMonitor();
trackPageView();
interceptFetch();
}
在应用入口调用
// app/layout.tsx(Next.js)
"use client";
import { useEffect } from "react";
import { initMonitor } from "@/utils/monitor";
export default function RootLayout({ children }) {
useEffect(() => {
initMonitor();
}, []);
return (
<html>
<body>
<ErrorBoundary>{children}</ErrorBoundary>
</body>
</html>
);
}
完整项目结构
utils/
└── monitor/
├── index.ts // 统一入口
├── performance.ts // 性能监控
├── error.ts // 错误监控
├── behavior.ts // 行为监控
├── api.ts // 接口监控
└── report.ts // 数据上报
components/
└── ErrorBoundary.tsx // React 错误边界
上报后端做什么
前端上报数据 → API 接收 → 存储(ES/ClickHouse)→ 可视化面板(Grafana)
↓
报警规则(错误率突增、接口慢)
↓
通知(钉钉/企微/邮件)
也可以用现成方案
| 方案 | 说明 |
|---|---|
| Sentry | 错误监控,开源,最流行 |
| 阿里 ARMS | 全链路监控 |
| 腾讯 Aegis | 前端监控 |
| 自建 | 灵活,但维护成本高 |
