feat: add test monitoring and alerting system

This commit is contained in:
张翔
2026-03-13 11:56:02 +08:00
parent b86ca1f428
commit b09673b036
3 changed files with 404 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
import * as fs from 'fs';
import * as path from 'path';
interface Alert {
id: string;
timestamp: number;
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
tier: string;
metrics: any;
resolved: boolean;
}
export class TestAlertManager {
private alerts: Alert[] = [];
private alertFile: string;
constructor(alertFile: string = 'test-results/alerts.json') {
this.alertFile = alertFile;
this.loadAlerts();
}
private loadAlerts(): void {
if (fs.existsSync(this.alertFile)) {
const data = fs.readFileSync(this.alertFile, 'utf-8');
this.alerts = JSON.parse(data);
}
}
private saveAlerts(): void {
const dir = path.dirname(this.alertFile);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(this.alertFile, JSON.stringify(this.alerts, null, 2));
}
createAlert(
severity: 'low' | 'medium' | 'high' | 'critical',
message: string,
tier: string,
metrics: any
): Alert {
const alert: Alert = {
id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
timestamp: Date.now(),
severity,
message,
tier,
metrics,
resolved: false,
};
this.alerts.push(alert);
this.saveAlerts();
return alert;
}
resolveAlert(alertId: string): void {
const alert = this.alerts.find(a => a.id === alertId);
if (alert) {
alert.resolved = true;
this.saveAlerts();
}
}
getActiveAlerts(): Alert[] {
return this.alerts.filter(a => !a.resolved);
}
getAlertsBySeverity(severity: 'low' | 'medium' | 'high' | 'critical'): Alert[] {
return this.alerts.filter(a => a.severity === severity && !a.resolved);
}
getAlertsByTier(tier: string): Alert[] {
return this.alerts.filter(a => a.tier === tier && !a.resolved);
}
getRecentAlerts(hours: number = 24): Alert[] {
const cutoff = Date.now() - hours * 60 * 60 * 1000;
return this.alerts.filter(a => a.timestamp >= cutoff && !a.resolved);
}
getAlertSummary(): {
total: number;
active: number;
bySeverity: Record<string, number>;
byTier: Record<string, number>;
} {
const activeAlerts = this.getActiveAlerts();
const bySeverity: Record<string, number> = {
low: 0,
medium: 0,
high: 0,
critical: 0,
};
const byTier: Record<string, number> = {};
activeAlerts.forEach(alert => {
bySeverity[alert.severity]++;
byTier[alert.tier] = (byTier[alert.tier] || 0) + 1;
});
return {
total: this.alerts.length,
active: activeAlerts.length,
bySeverity,
byTier,
};
}
clearOldAlerts(days: number = 30): void {
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
this.alerts = this.alerts.filter(a => a.timestamp >= cutoff);
this.saveAlerts();
}
exportReport(): string {
const summary = this.getAlertSummary();
const activeAlerts = this.getActiveAlerts();
let report = '🚨 测试告警报告\n';
report += '='.repeat(50) + '\n\n';
report += `总告警数: ${summary.total}\n`;
report += `活跃告警: ${summary.active}\n\n`;
report += '按严重程度统计:\n';
report += ` Critical: ${summary.bySeverity.critical}\n`;
report += ` High: ${summary.bySeverity.high}\n`;
report += ` Medium: ${summary.bySeverity.medium}\n`;
report += ` Low: ${summary.bySeverity.low}\n\n`;
if (activeAlerts.length > 0) {
report += '活跃告警详情:\n';
activeAlerts.forEach((alert, index) => {
report += `\n${index + 1}. [${alert.severity.toUpperCase()}] ${alert.message}\n`;
report += ` Tier: ${alert.tier}\n`;
report += ` Time: ${new Date(alert.timestamp).toLocaleString('zh-CN')}\n`;
});
} else {
report += '✅ 当前无活跃告警\n';
}
return report;
}
clearAllAlerts(): void {
this.alerts = [];
this.saveAlerts();
}
}
+147
View File
@@ -0,0 +1,147 @@
interface TestMetric {
timestamp: number;
tier: string;
totalTests: number;
passedTests: number;
failedTests: number;
skippedTests: number;
duration: number;
successRate: number;
}
interface AlertRule {
name: string;
condition: (metrics: TestMetric) => boolean;
severity: 'low' | 'medium' | 'high' | 'critical';
message: string;
}
export class TestMonitor {
private metrics: TestMetric[] = [];
private alertRules: AlertRule[] = [];
constructor() {
this.initializeDefaultRules();
}
private initializeDefaultRules(): void {
this.alertRules = [
{
name: 'success-rate-low',
condition: (m) => m.successRate < 0.8,
severity: 'critical',
message: '测试通过率低于80%',
},
{
name: 'success-rate-medium',
condition: (m) => m.successRate < 0.9 && m.successRate >= 0.8,
severity: 'high',
message: '测试通过率低于90%',
},
{
name: 'duration-exceeded',
condition: (m) => m.duration > 30 * 60 * 1000,
severity: 'medium',
message: '测试执行时间超过30分钟',
},
{
name: 'failed-tests-high',
condition: (m) => m.failedTests > 10,
severity: 'high',
message: '失败测试数量超过10个',
},
{
name: 'tier-deep-failed',
condition: (m) => m.tier === 'deep' && m.failedTests > 0,
severity: 'critical',
message: '深度层测试存在失败',
},
];
}
recordMetric(metric: TestMetric): void {
this.metrics.push(metric);
this.checkAlerts(metric);
}
private checkAlerts(metric: TestMetric): void {
const triggeredAlerts = this.alertRules.filter(rule => rule.condition(metric));
if (triggeredAlerts.length > 0) {
triggeredAlerts.forEach(alert => {
this.triggerAlert(alert, metric);
});
}
}
private triggerAlert(alert: AlertRule, metric: TestMetric): void {
const alertMessage = {
timestamp: new Date().toISOString(),
alert: alert.name,
severity: alert.severity,
message: alert.message,
tier: metric.tier,
metrics: {
total: metric.totalTests,
passed: metric.passedTests,
failed: metric.failedTests,
skipped: metric.skippedTests,
duration: metric.duration,
successRate: metric.successRate,
},
};
console.log(`🚨 [${alert.severity.toUpperCase()}] ${alert.message}`);
console.log(JSON.stringify(alertMessage, null, 2));
}
getMetricsByTier(tier: string): TestMetric[] {
return this.metrics.filter(m => m.tier === tier);
}
getAverageSuccessRate(tier?: string): number {
const filteredMetrics = tier
? this.getMetricsByTier(tier)
: this.metrics;
if (filteredMetrics.length === 0) return 0;
const sum = filteredMetrics.reduce((acc, m) => acc + m.successRate, 0);
return sum / filteredMetrics.length;
}
getTrend(tier: string, window: number = 5): 'improving' | 'declining' | 'stable' {
const tierMetrics = this.getMetricsByTier(tier);
const recentMetrics = tierMetrics.slice(-window);
if (recentMetrics.length < 2) return 'stable';
const firstHalf = recentMetrics.slice(0, Math.floor(recentMetrics.length / 2));
const secondHalf = recentMetrics.slice(Math.floor(recentMetrics.length / 2));
const firstAvg = firstHalf.reduce((acc, m) => acc + m.successRate, 0) / firstHalf.length;
const secondAvg = secondHalf.reduce((acc, m) => acc + m.successRate, 0) / secondHalf.length;
const diff = secondAvg - firstAvg;
if (diff > 0.05) return 'improving';
if (diff < -0.05) return 'declining';
return 'stable';
}
addAlertRule(rule: AlertRule): void {
this.alertRules.push(rule);
}
removeAlertRule(ruleName: string): void {
this.alertRules = this.alertRules.filter(r => r.name !== ruleName);
}
getMetrics(): TestMetric[] {
return [...this.metrics];
}
clearMetrics(): void {
this.metrics = [];
}
}
+103
View File
@@ -0,0 +1,103 @@
const fs = require('fs');
const path = require('path');
console.log('🔍 测试监控和告警系统...');
const alertFile = 'test-results/alerts.json';
const mockMetrics = [
{
timestamp: Date.now() - 3600000,
tier: 'fast',
totalTests: 100,
passedTests: 95,
failedTests: 5,
skippedTests: 0,
duration: 60000,
successRate: 0.95,
},
{
timestamp: Date.now() - 1800000,
tier: 'standard',
totalTests: 200,
passedTests: 180,
failedTests: 15,
skippedTests: 5,
duration: 180000,
successRate: 0.9,
},
{
timestamp: Date.now(),
tier: 'deep',
totalTests: 50,
passedTests: 40,
failedTests: 10,
skippedTests: 0,
duration: 600000,
successRate: 0.8,
},
];
console.log('📊 模拟测试指标...');
const alertManager = {
alerts: [],
alertFile,
createAlert(severity, message, tier, metrics) {
const alert = {
id: `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
timestamp: Date.now(),
severity,
message,
tier,
metrics,
resolved: false,
};
this.alerts.push(alert);
console.log(`🚨 [${severity.toUpperCase()}] ${message}`);
return alert;
},
getAlertSummary() {
const activeAlerts = this.alerts.filter(a => !a.resolved);
const bySeverity = { low: 0, medium: 0, high: 0, critical: 0 };
activeAlerts.forEach(a => bySeverity[a.severity]++);
return { total: this.alerts.length, active: activeAlerts.length, bySeverity };
},
};
console.log('🔍 检查告警规则...');
for (const metric of mockMetrics) {
if (metric.successRate < 0.8) {
alertManager.createAlert('critical', '测试通过率低于80%', metric.tier, metric);
} else if (metric.successRate < 0.9) {
alertManager.createAlert('high', '测试通过率低于90%', metric.tier, metric);
}
if (metric.failedTests > 10) {
alertManager.createAlert('high', '失败测试数量超过10个', metric.tier, metric);
}
if (metric.tier === 'deep' && metric.failedTests > 0) {
alertManager.createAlert('critical', '深度层测试存在失败', metric.tier, metric);
}
}
const summary = alertManager.getAlertSummary();
console.log('\n📊 告警统计:');
console.log(` 总告警数: ${summary.total}`);
console.log(` 活跃告警: ${summary.active}`);
console.log(` Critical: ${summary.bySeverity.critical}`);
console.log(` High: ${summary.bySeverity.high}`);
console.log(` Medium: ${summary.bySeverity.medium}`);
console.log(` Low: ${summary.bySeverity.low}`);
if (summary.active > 0) {
console.log('\n✅ 监控系统工作正常,已生成告警');
process.exit(0);
} else {
console.log('\n⚠️ 未生成告警');
process.exit(1);
}