true, 'max_alerts' => 500, 'retention_period' => 7200, 'channels' => ['log'], 'thresholds' => [ 'error_rate' => 0.05, 'memory_usage' => 0.9, 'disk_usage' => 0.95, 'response_time' => 2.0, 'critical_errors' => 5 ], 'cooldown' => [ 'error_rate' => 300, 'memory_usage' => 600, 'disk_usage' => 600, 'response_time' => 300, 'critical_errors' => 1800 ] ], $config); self::$enabled = self::$config['enabled']; self::initializeChannels(); } public static function triggerAlert(string $type, string $message, array $context = [], string $severity = 'warning'): void { if (!self::$enabled) { return; } $alert = [ 'id' => uniqid('alert_', true), 'type' => $type, 'message' => $message, 'context' => $context, 'severity' => $severity, 'timestamp' => microtime(true), 'datetime' => date('Y-m-d H:i:s'), 'status' => 'active', 'acknowledged' => false, 'acknowledged_by' => null, 'acknowledged_at' => null, 'resolved' => false, 'resolved_at' => null ]; self::$alerts[] = $alert; self::cleanupOldAlerts(); // 发送告警到各个渠道 self::sendToChannels($alert); // 记录告警 Logger::warning("Alert triggered: {$type} - {$message}", $alert); } public static function checkErrorRate(float $errorRate, int $totalErrors, int $timeWindow): void { if ($errorRate > self::$config['thresholds']['error_rate']) { self::triggerAlert('error_rate', "High error rate detected: " . round($errorRate * 100, 2) . "%", [ 'error_rate' => $errorRate, 'total_errors' => $totalErrors, 'time_window' => $timeWindow, 'threshold' => self::$config['thresholds']['error_rate'] ], $errorRate > 0.1 ? 'critical' : 'warning' ); } } public static function checkMemoryUsage(float $usagePercent, int $usedBytes, int $totalBytes): void { if ($usagePercent > self::$config['thresholds']['memory_usage']) { self::triggerAlert('memory_usage', "High memory usage: " . round($usagePercent * 100, 2) . "%", [ 'usage_percent' => $usagePercent, 'used_bytes' => $usedBytes, 'total_bytes' => $totalBytes, 'threshold' => self::$config['thresholds']['memory_usage'] ], $usagePercent > 0.95 ? 'critical' : 'warning' ); } } public static function checkDiskUsage(string $path, float $usagePercent, int $usedBytes, int $totalBytes): void { if ($usagePercent > self::$config['thresholds']['disk_usage']) { self::triggerAlert('disk_usage', "Low disk space on {$path}: " . round($usagePercent * 100, 2) . "% used", [ 'path' => $path, 'usage_percent' => $usagePercent, 'used_bytes' => $usedBytes, 'total_bytes' => $totalBytes, 'threshold' => self::$config['thresholds']['disk_usage'] ], $usagePercent > 0.98 ? 'critical' : 'warning' ); } } public static function checkResponseTime(string $endpoint, float $avgTime, float $threshold = null): void { $threshold = $threshold ?? self::$config['thresholds']['response_time']; if ($avgTime > $threshold) { self::triggerAlert('response_time', "Slow response time for {$endpoint}: " . round($avgTime, 3) . "s", [ 'endpoint' => $endpoint, 'avg_time' => $avgTime, 'threshold' => $threshold ], $avgTime > $threshold * 2 ? 'critical' : 'warning' ); } } public static function checkCriticalErrors(int $criticalCount, int $timeWindow): void { if ($criticalCount >= self::$config['thresholds']['critical_errors']) { self::triggerAlert('critical_errors', "Multiple critical errors detected: {$criticalCount} in {$timeWindow}s", [ 'critical_count' => $criticalCount, 'time_window' => $timeWindow, 'threshold' => self::$config['thresholds']['critical_errors'] ], 'critical' ); } } public static function checkServiceDown(string $service, array $context = []): void { self::triggerAlert('service_down', "Service unavailable: {$service}", array_merge($context, ['service' => $service]), 'critical' ); } public static function acknowledgeAlert(string $alertId, string $acknowledgedBy): bool { foreach (self::$alerts as &$alert) { if ($alert['id'] === $alertId && $alert['status'] === 'active') { $alert['acknowledged'] = true; $alert['acknowledged_by'] = $acknowledgedBy; $alert['acknowledged_at'] = microtime(true); Logger::info("Alert acknowledged: {$alertId} by {$acknowledgedBy}"); return true; } } return false; } public static function resolveAlert(string $alertId): bool { foreach (self::$alerts as &$alert) { if ($alert['id'] === $alertId && $alert['status'] === 'active') { $alert['status'] = 'resolved'; $alert['resolved_at'] = microtime(true); Logger::info("Alert resolved: {$alertId}"); return true; } } return false; } public static function getActiveAlerts(): array { return array_filter(self::$alerts, fn($alert) => $alert['status'] === 'active'); } public static function getAlerts(array $filters = []): array { $alerts = self::$alerts; // 应用过滤器 if (!empty($filters)) { $alerts = array_filter($alerts, function($alert) use ($filters) { foreach ($filters as $key => $value) { if (isset($alert[$key]) && $alert[$key] !== $value) { return false; } } return true; }); } // 按时间倒序排列 usort($alerts, function($a, $b) { return $b['timestamp'] <=> $a['timestamp']; }); return array_values($alerts); } public static function getAlertStatistics(): array { $stats = [ 'total_alerts' => count(self::$alerts), 'active_alerts' => 0, 'acknowledged_alerts' => 0, 'resolved_alerts' => 0, 'by_type' => [], 'by_severity' => [], 'by_hour' => [], 'recent_alerts' => 0 ]; $now = time(); $oneHourAgo = $now - 3600; foreach (self::$alerts as $alert) { // 状态统计 if ($alert['status'] === 'active') { $stats['active_alerts']++; } if ($alert['acknowledged']) { $stats['acknowledged_alerts']++; } if ($alert['resolved']) { $stats['resolved_alerts']++; } // 按类型统计 $type = $alert['type']; $stats['by_type'][$type] = ($stats['by_type'][$type] ?? 0) + 1; // 按严重程度统计 $severity = $alert['severity']; $stats['by_severity'][$severity] = ($stats['by_severity'][$severity] ?? 0) + 1; // 按小时统计 $hour = date('Y-m-d H:00', (int)$alert['timestamp']); $stats['by_hour'][$hour] = ($stats['by_hour'][$hour] ?? 0) + 1; // 最近告警 if ($alert['timestamp'] > $oneHourAgo) { $stats['recent_alerts']++; } } return $stats; } public static function clearAlerts(): void { self::$alerts = []; } public static function clearResolvedAlerts(): void { self::$alerts = array_filter(self::$alerts, fn($alert) => !$alert['resolved']); } public static function addChannel(string $name, callable $handler): void { self::$channels[$name] = $handler; } public static function removeChannel(string $name): void { unset(self::$channels[$name]); } private static function initializeChannels(): void { // 默认日志渠道 self::$channels['log'] = function($alert) { $level = match ($alert['severity']) { 'critical' => 'critical', 'warning' => 'warning', default => 'info' }; Logger::$level("ALERT [{$alert['type']}]: {$alert['message']}", $alert); }; // 邮件渠道(如果配置了) if (isset(self::$config['email']) && self::$config['email']['enabled']) { self::$channels['email'] = [self::class, 'sendEmailAlert']; } // 钉钉渠道(如果配置了) if (isset(self::$config['dingtalk']) && self::$config['dingtalk']['enabled']) { self::$channels['dingtalk'] = [self::class, 'sendDingTalkAlert']; } // Slack渠道(如果配置了) if (isset(self::$config['slack']) && self::$config['slack']['enabled']) { self::$channels['slack'] = [self::class, 'sendSlackAlert']; } } private static function sendToChannels(array $alert): void { foreach (self::$config['channels'] as $channelName) { if (isset(self::$channels[$channelName])) { try { self::$channels[$channelName]($alert); } catch (\Throwable $e) { Logger::error("Failed to send alert to channel {$channelName}: " . $e->getMessage()); } } } } private static function sendEmailAlert(array $alert): void { $config = self::$config['email']; $to = $config['to'] ?? []; $subject = "[Fendx Alert] {$alert['type']} - {$alert['severity']}"; $message = self::formatAlertMessage($alert, 'email'); // 这里应该使用实际的邮件发送库 Logger::info("Email alert would be sent to: " . implode(', ', $to)); } private static function sendDingTalkAlert(array $alert): void { $config = self::$config['dingtalk']; $webhook = $config['webhook'] ?? ''; if (empty($webhook)) { return; } $message = self::formatAlertMessage($alert, 'dingtalk'); // 这里应该使用实际的HTTP客户端发送到钉钉 Logger::info("DingTalk alert would be sent: {$message}"); } private static function sendSlackAlert(array $alert): void { $config = self::$config['slack']; $webhook = $config['webhook'] ?? ''; if (empty($webhook)) { return; } $message = self::formatAlertMessage($alert, 'slack'); // 这里应该使用实际的HTTP客户端发送到Slack Logger::info("Slack alert would be sent: {$message}"); } private static function formatAlertMessage(array $alert, string $format): string { $timestamp = date('Y-m-d H:i:s', (int)$alert['timestamp']); $severity = strtoupper($alert['severity']); return match ($format) { 'email' => "

[{$severity}] {$alert['type']}

Message: {$alert['message']}

Time: {$timestamp}

Context:

" . json_encode($alert['context'], JSON_PRETTY_PRINT) . "

", 'dingtalk' => "【{$severity}】{$alert['type']}\n{$alert['message']}\n时间: {$timestamp}", 'slack' => "*[{$severity}] {$alert['type']}*\n{$alert['message']}\nTime: {$timestamp}", default => "[{$severity}] {$alert['type']}: {$alert['message']} at {$timestamp}" }; } private static function cleanupOldAlerts(): void { if (count(self::$alerts) <= self::$config['max_alerts']) { return; } // 按时间排序,保留最新的告警 usort(self::$alerts, function($a, $b) { return $a['timestamp'] <=> $b['timestamp']; }); self::$alerts = array_slice(self::$alerts, -self::$config['max_alerts']); } }