工程化与框架系列(36)--前端监控告警实践
前端监控告警实践 🔔
引言
前端监控是保障应用质量和用户体验的重要手段。本文将深入探讨前端监控的实现方案,包括性能监控、错误监控、用户行为监控等方面,以及相应的告警机制。
监控系统概述
前端监控系统主要包括以下方面:
- 性能监控:页面加载、资源加载、接口性能等
- 错误监控:JS错误、接口错误、资源加载错误等
- 用户行为:PV/UV、点击行为、路由变化等
- 业务监控:转化率、留存率、业务指标等
- 告警系统:规则配置、通知分发、告警处理等
监控系统实现
监控管理器
// 监控管理器类
class MonitoringManager {
private static instance: MonitoringManager;
private config: MonitorConfig;
private collectors: Map<string, Collector>;
private processors: Map<string, Processor>;
private reporters: Map<string, Reporter>;
private alerter: Alerter;
private constructor() {
this.collectors = new Map();
this.processors = new Map();
this.reporters = new Map();
this.config = {
appId: '',
userId: '',
sessionId: '',
environment: 'production',
version: '1.0.0',
sampling: 100
};
}
// 获取单例实例
static getInstance(): MonitoringManager {
if (!MonitoringManager.instance) {
MonitoringManager.instance = new MonitoringManager();
}
return MonitoringManager.instance;
}
// 初始化监控系统
init(config: MonitorConfig): void {
this.config = { ...this.config, ...config };
// 初始化收集器
this.initCollectors();
// 初始化处理器
this.initProcessors();
// 初始化上报器
this.initReporters();
// 初始化告警器
this.initAlerter();
// 启动监控
this.start();
}
// 初始化收集器
private initCollectors(): void {
// 性能收集器
this.collectors.set(
'performance',
new PerformanceCollector()
);
// 错误收集器
this.collectors.set(
'error',
new ErrorCollector()
);
// 行为收集器
this.collectors.set(
'behavior',
new BehaviorCollector()
);
// 业务收集器
this.collectors.set(
'business',
new BusinessCollector()
);
}
// 初始化处理器
private initProcessors(): void {
// 性能处理器
this.processors.set(
'performance',
new PerformanceProcessor()
);
// 错误处理器
this.processors.set(
'error',
new ErrorProcessor()
);
// 行为处理器
this.processors.set(
'behavior',
new BehaviorProcessor()
);
// 业务处理器
this.processors.set(
'business',
new BusinessProcessor()
);
}
// 初始化上报器
private initReporters(): void {
// HTTP上报器
this.reporters.set(
'http',
new HttpReporter(this.config.reportUrl)
);
// 信标上报器
this.reporters.set(
'beacon',
new BeaconReporter()
);
// 日志上报器
this.reporters.set(
'log',
new LogReporter()
);
}
// 初始化告警器
private initAlerter(): void {
this.alerter = new Alerter({
rules: this.config.alertRules,
channels: this.config.alertChannels
});
}
// 启动监控
private start(): void {
// 启动收集器
this.collectors.forEach(collector => {
collector.start();
});
// 启动处理器
this.processors.forEach(processor => {
processor.start();
});
// 启动上报器
this.reporters.forEach(reporter => {
reporter.start();
});
// 启动告警器
this.alerter.start();
}
// 停止监控
stop(): void {
// 停止收集器
this.collectors.forEach(collector => {
collector.stop();
});
// 停止处理器
this.processors.forEach(processor => {
processor.stop();
});
// 停止上报器
this.reporters.forEach(reporter => {
reporter.stop();
});
// 停止告警器
this.alerter.stop();
}
// 手动上报
report(data: MonitorData): void {
// 采样判断
if (!this.shouldSample()) {
return;
}
// 数据处理
const processor = this.processors.get(data.type);
if (processor) {
data = processor.process(data);
}
// 数据上报
const reporter = this.reporters.get(this.config.reportType);
if (reporter) {
reporter.report(data);
}
// 告警检查
this.alerter.check(data);
}
// 采样判断
private shouldSample(): boolean {
return Math.random() * 100 < this.config.sampling;
}
}
// 收集器基类
abstract class Collector {
protected config: CollectorConfig;
protected callback: (data: MonitorData) => void;
constructor(config: CollectorConfig) {
this.config = config;
}
setCallback(callback: (data: MonitorData) => void): void {
this.callback = callback;
}
abstract start(): void;
abstract stop(): void;
}
// 性能收集器
class PerformanceCollector extends Collector {
private observer: PerformanceObserver | null = null;
start(): void {
// 收集性能指标
this.collectMetrics();
// 观察性能事件
this.observePerformance();
}
stop(): void {
this.observer?.disconnect();
this.observer = null;
}
private collectMetrics(): void {
// 收集导航计时
const navigation = performance.getEntriesByType('navigation')[0];
this.callback?.({
type: 'performance',
subType: 'navigation',
data: navigation
});
// 收集资源计时
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
this.callback?.({
type: 'performance',
subType: 'resource',
data: resource
});
});
// 收集First Paint
const paint = performance.getEntriesByType('paint');
paint.forEach(entry => {
this.callback?.({
type: 'performance',
subType: 'paint',
data: entry
});
});
}
private observePerformance(): void {
this.observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
this.callback?.({
type: 'performance',
subType: entry.entryType,
data: entry
});
});
});
// 观察的性能指标类型
this.observer.observe({
entryTypes: [
'navigation',
'resource',
'paint',
'largest-contentful-paint',
'first-input',
'layout-shift'
]
});
}
}
// 错误收集器
class ErrorCollector extends Collector {
private errorHandler: (event: ErrorEvent) => void;
private unhandledRejectionHandler: (event: PromiseRejectionEvent) => void;
start(): void {
// 监听JS错误
this.errorHandler = (event: ErrorEvent) => {
this.callback?.({
type: 'error',
subType: 'javascript',
data: {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
}
});
};
window.addEventListener('error', this.errorHandler);
// 监听Promise错误
this.unhandledRejectionHandler = (event: PromiseRejectionEvent) => {
this.callback?.({
type: 'error',
subType: 'promise',
data: {
reason: event.reason
}
});
};
window.addEventListener(
'unhandledrejection',
this.unhandledRejectionHandler
);
}
stop(): void {
window.removeEventListener('error', this.errorHandler);
window.removeEventListener(
'unhandledrejection',
this.unhandledRejectionHandler
);
}
}
// 行为收集器
class BehaviorCollector extends Collector {
private clickHandler: (event: MouseEvent) => void;
private routeHandler: () => void;
start(): void {
// 监听点击事件
this.clickHandler = (event: MouseEvent) => {
const target = event.target as HTMLElement;
this.callback?.({
type: 'behavior',
subType: 'click',
data: {
path: this.getElementPath(target),
timestamp: Date.now()
}
});
};
document.addEventListener('click', this.clickHandler);
// 监听路由变化
this.routeHandler = () => {
this.callback?.({
type: 'behavior',
subType: 'route',
data: {
path: location.pathname,
timestamp: Date.now()
}
});
};
window.addEventListener('popstate', this.routeHandler);
}
stop(): void {
document.removeEventListener('click', this.clickHandler);
window.removeEventListener('popstate', this.routeHandler);
}
private getElementPath(element: HTMLElement): string {
const path: string[] = [];
let current: HTMLElement | null = element;
while (current && current !== document.body) {
let selector = current.tagName.toLowerCase();
if (current.id) {
selector += `#${current.id}`;
} else if (current.className) {
selector += `.${current.className.split(' ').join('.')}`;
}
path.unshift(selector);
current = current.parentElement;
}
return path.join(' > ');
}
}
// 业务收集器
class BusinessCollector extends Collector {
private metrics: Map<string, number> = new Map();
start(): void {
// 定时上报业务指标
setInterval(() => {
this.reportMetrics();
}, this.config.reportInterval);
}
stop(): void {
this.metrics.clear();
}
// 记录业务指标
record(name: string, value: number): void {
this.metrics.set(name, value);
}
private reportMetrics(): void {
this.metrics.forEach((value, name) => {
this.callback?.({
type: 'business',
subType: 'metric',
data: {
name,
value,
timestamp: Date.now()
}
});
});
}
}
// 处理器基类
abstract class Processor {
protected config: ProcessorConfig;
constructor(config: ProcessorConfig) {
this.config = config;
}
abstract start(): void;
abstract stop(): void;
abstract process(data: MonitorData): MonitorData;
}
// 性能处理器
class PerformanceProcessor extends Processor {
start(): void {}
stop(): void {}
process(data: MonitorData): MonitorData {
if (data.type !== 'performance') {
return data;
}
// 处理性能数据
switch (data.subType) {
case 'navigation':
return this.processNavigation(data);
case 'resource':
return this.processResource(data);
case 'paint':
return this.processPaint(data);
default:
return data;
}
}
private processNavigation(data: MonitorData): MonitorData {
const entry = data.data as PerformanceNavigationTiming;
return {
...data,
data: {
dns: entry.domainLookupEnd - entry.domainLookupStart,
tcp: entry.connectEnd - entry.connectStart,
ttfb: entry.responseStart - entry.requestStart,
download: entry.responseEnd - entry.responseStart,
domReady: entry.domContentLoadedEventEnd - entry.navigationStart,
load: entry.loadEventEnd - entry.navigationStart
}
};
}
private processResource(data: MonitorData): MonitorData {
const entry = data.data as PerformanceResourceTiming;
return {
...data,
data: {
name: entry.name,
type: entry.initiatorType,
duration: entry.duration,
size: entry.transferSize
}
};
}
private processPaint(data: MonitorData): MonitorData {
const entry = data.data as PerformancePaintTiming;
return {
...data,
data: {
name: entry.name,
time: entry.startTime
}
};
}
}
// 错误处理器
class ErrorProcessor extends Processor {
start(): void {}
stop(): void {}
process(data: MonitorData): MonitorData {
if (data.type !== 'error') {
return data;
}
// 处理错误数据
return {
...data,
data: {
...data.data,
timestamp: Date.now(),
url: location.href,
userAgent: navigator.userAgent
}
};
}
}
// 行为处理器
class BehaviorProcessor extends Processor {
start(): void {}
stop(): void {}
process(data: MonitorData): MonitorData {
if (data.type !== 'behavior') {
return data;
}
// 处理行为数据
return {
...data,
data: {
...data.data,
url: location.href,
title: document.title
}
};
}
}
// 业务处理器
class BusinessProcessor extends Processor {
start(): void {}
stop(): void {}
process(data: MonitorData): MonitorData {
if (data.type !== 'business') {
return data;
}
// 处理业务数据
return {
...data,
data: {
...data.data,
env: this.config.environment
}
};
}
}
// 上报器基类
abstract class Reporter {
protected config: ReporterConfig;
protected queue: MonitorData[] = [];
protected timer: number | null = null;
constructor(config: ReporterConfig) {
this.config = config;
}
start(): void {
// 定时上报
this.timer = window.setInterval(() => {
this.flush();
}, this.config.flushInterval);
}
stop(): void {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
}
report(data: MonitorData): void {
this.queue.push(data);
// 队列满时立即上报
if (this.queue.length >= this.config.maxBatchSize) {
this.flush();
}
}
protected abstract flush(): void;
}
// HTTP上报器
class HttpReporter extends Reporter {
protected async flush(): void {
if (this.queue.length === 0) {
return;
}
try {
const data = this.queue.slice();
this.queue = [];
await fetch(this.config.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
} catch (error) {
console.error('Failed to report data:', error);
// 失败重试
this.queue.push(...data);
}
}
}
// 信标上报器
class BeaconReporter extends Reporter {
protected flush(): void {
if (this.queue.length === 0) {
return;
}
const data = this.queue.slice();
this.queue = [];
const blob = new Blob(
[JSON.stringify(data)],
{ type: 'application/json' }
);
navigator.sendBeacon(this.config.url, blob);
}
}
// 日志上报器
class LogReporter extends Reporter {
protected flush(): void {
if (this.queue.length === 0) {
return;
}
const data = this.queue.slice();
this.queue = [];
console.log('Monitor Data:', data);
}
}
// 告警器
class Alerter {
private config: AlertConfig;
private rules: Map<string, AlertRule> = new Map();
private state: Map<string, AlertState> = new Map();
constructor(config: AlertConfig) {
this.config = config;
}
start(): void {
// 初始化告警规则
this.config.rules.forEach(rule => {
this.rules.set(rule.id, rule);
});
}
stop(): void {
this.rules.clear();
this.state.clear();
}
// 检查告警
check(data: MonitorData): void {
this.rules.forEach(rule => {
if (this.matchRule(rule, data)) {
this.processAlert(rule, data);
}
});
}
// 匹配规则
private matchRule(rule: AlertRule, data: MonitorData): boolean {
// 类型匹配
if (rule.type !== data.type) {
return false;
}
// 子类型匹配
if (rule.subType && rule.subType !== data.subType) {
return false;
}
// 条件匹配
return this.evaluateCondition(rule.condition, data.data);
}
// 评估条件
private evaluateCondition(
condition: AlertCondition,
data: any
): boolean {
const value = data[condition.field];
switch (condition.operator) {
case '>':
return value > condition.value;
case '<':
return value < condition.value;
case '>=':
return value >= condition.value;
case '<=':
return value <= condition.value;
case '==':
return value == condition.value;
case '!=':
return value != condition.value;
default:
return false;
}
}
// 处理告警
private processAlert(rule: AlertRule, data: MonitorData): void {
const state = this.state.get(rule.id) || {
count: 0,
firstTime: Date.now(),
lastTime: Date.now()
};
// 更新状态
state.count++;
state.lastTime = Date.now();
this.state.set(rule.id, state);
// 检查告警条件
if (this.shouldAlert(rule, state)) {
this.sendAlert(rule, state, data);
}
}
// 判断是否需要告警
private shouldAlert(rule: AlertRule, state: AlertState): boolean {
// 检查告警间隔
const interval = state.lastTime - state.firstTime;
if (interval < rule.timeWindow) {
return false;
}
// 检查告警次数
return state.count >= rule.threshold;
}
// 发送告警
private async sendAlert(
rule: AlertRule,
state: AlertState,
data: MonitorData
): Promise<void> {
const alert: Alert = {
id: rule.id,
name: rule.name,
level: rule.level,
message: this.formatMessage(rule, state, data),
time: Date.now()
};
// 发送到不同渠道
await Promise.all(
this.config.channels.map(channel => {
return this.sendToChannel(channel, alert);
})
);
// 重置状态
this.state.delete(rule.id);
}
// 格式化告警消息
private formatMessage(
rule: AlertRule,
state: AlertState,
data: MonitorData
): string {
return `
告警:${rule.name}
级别:${rule.level}
时间:${new Date().toLocaleString()}
次数:${state.count}
详情:${JSON.stringify(data)}
`;
}
// 发送到告警渠道
private async sendToChannel(
channel: AlertChannel,
alert: Alert
): Promise<void> {
try {
await fetch(channel.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel: channel.type,
alert
})
});
} catch (error) {
console.error(
`Failed to send alert to ${channel.type}:`,
error
);
}
}
}
// 接口定义
interface MonitorConfig {
appId: string;
userId: string;
sessionId: string;
environment: string;
version: string;
sampling: number;
reportUrl?: string;
reportType?: string;
alertRules?: AlertRule[];
alertChannels?: AlertChannel[];
}
interface CollectorConfig {
reportInterval?: number;
}
interface ProcessorConfig {
environment: string;
}
interface ReporterConfig {
url: string;
flushInterval: number;
maxBatchSize: number;
}
interface AlertConfig {
rules: AlertRule[];
channels: AlertChannel[];
}
interface MonitorData {
type: string;
subType: string;
data: any;
}
interface AlertRule {
id: string;
name: string;
type: string;
subType?: string;
level: 'info' | 'warning' | 'error' | 'critical';
condition: AlertCondition;
timeWindow: number;
threshold: number;
}
interface AlertCondition {
field: string;
operator: '>' | '<' | '>=' | '<=' | '==' | '!=';
value: any;
}
interface AlertState {
count: number;
firstTime: number;
lastTime: number;
}
interface Alert {
id: string;
name: string;
level: string;
message: string;
time: number;
}
interface AlertChannel {
type: string;
url: string;
}
// 使用示例
const monitor = MonitoringManager.getInstance();
// 初始化监控
monitor.init({
appId: 'my-app',
userId: 'user-123',
sessionId: 'session-456',
environment: 'production',
version: '1.0.0',
sampling: 100,
reportUrl: 'https://monitor.example.com/report',
reportType: 'http',
alertRules: [
{
id: 'error-rate',
name: '错误率告警',
type: 'error',
level: 'error',
condition: {
field: 'count',
operator: '>',
value: 10
},
timeWindow: 60000,
threshold: 5
}
],
alertChannels: [
{
type: 'webhook',
url: 'https://alert.example.com/webhook'
}
]
});
// 手动上报
monitor.report({
type: 'business',
subType: 'conversion',
data: {
name: 'purchase',
value: 100
}
});
// 停止监控
monitor.stop();
最佳实践与建议
-
监控范围
- 性能指标
- 错误信息
- 用户行为
- 业务数据
-
采集策略
- 采样控制
- 批量上报
- 优先级分级
- 实时性要求
-
告警机制
- 规则配置
- 级别定义
- 通知方式
- 处理流程
-
数据处理
- 数据清洗
- 聚合分析
- 存储策略
- 查询优化
总结
前端监控系统需要考虑以下方面:
- 监控指标的全面性
- 数据采集的可靠性
- 处理分析的及时性
- 告警通知的准确性
- 系统运维的可维护性
通过完善的监控体系,可以及时发现和解决问题,提高应用质量。
学习资源
- 性能监控指标
- 错误监控方案
- 用户行为分析
- 告警系统设计
- 监控平台实践
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻