PHP异常监控与告警系统设计线上应用随时可能出问题。好的监控和告警系统能在第一时间发现问题并通知相关人员。今天说说PHP应用的异常监控和告警实现。应用级别的错误监控phpclass ErrorMonitor{private string $appName;private string $env;private string $logDir;private array $webhookUrls;public function __construct(string $appName, string $env production, string $logDir /var/log/app){$this-appName $appName;$this-env $env;$this-logDir rtrim($logDir, /);$this-webhookUrls [];if (!is_dir($this-logDir)) {mkdir($this-logDir, 0755, true);}$this-registerHandlers();}public function addWebhook(string $url): void{$this-webhookUrls[] $url;}public function addSlackWebhook(string $url): void{$this-webhookUrls[slack] $url;}public function addDingTalkWebhook(string $url): void{$this-webhookUrls[dingtalk] $url;}private function registerHandlers(): void{set_error_handler([$this, handleError]);set_exception_handler([$this, handleException]);register_shutdown_function([$this, handleShutdown]);}public function handleError(int $level, string $message, string $file, int $line): bool{// 忽略被 抑制的错误if (!(error_reporting() $level)) {return false;}$error [level $this-getErrorLevel($level),message $message,file $file,line $line,trace debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5),];$this-logError($error);$this-checkAlertThreshold($error);return true;}public function handleException(Throwable $e): void{$error [type get_class($e),message $e-getMessage(),file $e-getFile(),line $e-getLine(),trace $e-getTraceAsString(),level EXCEPTION,];$this-logError($error);$this-sendAlert($error);}public function handleShutdown(): void{$error error_get_last();if ($error ! null in_array($error[type], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {$this-handleError($error[type], $error[message], $error[file], $error[line]);$this-sendAlert([level FATAL,message $error[message],file $error[file],line $error[line],]);}}private function logError(array $error): void{$entry array_merge([time date(Y-m-d\TH:i:s.vP),app $this-appName,env $this-env,host gethostname(),pid getmypid(),request_uri $_SERVER[REQUEST_URI] ?? cli,request_method $_SERVER[REQUEST_METHOD] ?? CLI,ip $_SERVER[REMOTE_ADDR] ?? 127.0.0.1,], $error);$logFile $this-logDir . /errors- . date(Y-m-d) . .log;file_put_contents($logFile, json_encode($entry, JSON_UNESCAPED_UNICODE) . \n, FILE_APPEND | LOCK_EX);}public function checkAlertThreshold(array $error): void{if (in_array($error[level], [ERROR, FATAL])) {$this-sendAlert($error);}}public function sendAlert(array $error): void{$message *{$error[level]}*: {$error[message]}\n;$message . 文件: {$error[file]}:{$error[line]}\n;$message . 请求: {$_SERVER[REQUEST_URI] ?? cli}\n;$message . 时间: {$error[time] ?? date(Y-m-d H:i:s)}\n;// Slack通知if (isset($this-webhookUrls[slack])) {$this-sendSlackNotification($this-webhookUrls[slack], $message);}// 钉钉通知if (isset($this-webhookUrls[dingtalk])) {$this-sendDingTalkNotification($this-webhookUrls[dingtalk], $message);}// 错误次数超过阈值时发送短信或电话告警$errorCount $this-getRecentErrorCount(300);if ($errorCount 50) {$this-sendUrgentAlert(过去5分钟有{$errorCount}次错误);}}private function sendSlackNotification(string $webhook, string $message): void{$payload json_encode([text {$this-appName}[{$this-env}] 错误告警,attachments [[text $message, color danger],],]);$ch curl_init($webhook);curl_setopt_array($ch, [CURLOPT_POST true,CURLOPT_POSTFIELDS $payload,CURLOPT_HTTPHEADER [Content-Type: application/json],CURLOPT_RETURNTRANSFER true,CURLOPT_TIMEOUT 5,]);curl_exec($ch);curl_close($ch);}private function sendDingTalkNotification(string $webhook, string $message): void{$payload json_encode([msgtype text,text [content {$this-appName}[{$this-env}] 错误告警\n{$message}],]);$ch curl_init($webhook);curl_setopt_array($ch, [CURLOPT_POST true,CURLOPT_POSTFIELDS $payload,CURLOPT_HTTPHEADER [Content-Type: application/json],CURLOPT_RETURNTRANSFER true,CURLOPT_TIMEOUT 5,]);curl_exec($ch);curl_close($ch);}private function sendUrgentAlert(string $message): void{// 短信或电话告警调用第三方APIerror_log(紧急告警: $message);}private function getRecentErrorCount(int $seconds): int{$count 0;$cutoff time() - $seconds;$logFile $this-logDir . /errors- . date(Y-m-d) . .log;if (!file_exists($logFile)) return 0;$handle fopen($logFile, r);while (($line fgets($handle)) ! false) {$entry json_decode($line, true);if ($entry strtotime($entry[time] ?? ) $cutoff) {$count;}}fclose($handle);return $count;}private function getErrorLevel(int $level): string{return match ($level) {E_ERROR, E_USER_ERROR ERROR,E_WARNING, E_USER_WARNING WARNING,E_NOTICE, E_USER_NOTICE NOTICE,E_DEPRECATED, E_USER_DEPRECATED DEPRECATED,default UNKNOWN,};}}$monitor new ErrorMonitor(myapp, production, /var/log/app);$monitor-addSlackWebhook(https://hooks.slack.com/services/xxx);$monitor-addDingTalkWebhook(https://oapi.dingtalk.com/robot/send?access_tokenxxx);// 触发一个测试错误trigger_error(测试错误, E_USER_WARNING);throw new RuntimeException(测试异常);?监控系统的分组和统计功能phpclass ErrorAggregator{private PDO $pdo;public function __construct(PDO $pdo){$this-pdo $pdo;$this-initTable();}private function initTable(): void{$this-pdo-exec(CREATE TABLE IF NOT EXISTS error_groups (id INT AUTO_INCREMENT PRIMARY KEY,fingerprint VARCHAR(64) NOT NULL UNIQUE,type VARCHAR(100),message TEXT,file VARCHAR(500),first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,count INT DEFAULT 1,status ENUM(open, resolved, ignored) DEFAULT open,assigned_to VARCHAR(100),INDEX idx_status (status),INDEX idx_last_seen (last_seen)) ENGINEInnoDB DEFAULT CHARSETutf8mb4);}public function aggregate(array $error): void{$fingerprint md5($error[file] . : . $error[line] . : . $error[message]);$stmt $this-pdo-prepare(SELECT * FROM error_groups WHERE fingerprint ?);$stmt-execute([$fingerprint]);$group $stmt-fetch();if ($group) {$stmt $this-pdo-prepare(UPDATE error_groups SET count count 1, last_seen NOW()WHERE fingerprint ?);$stmt-execute([$fingerprint]);} else {$stmt $this-pdo-prepare(INSERT INTO error_groups (fingerprint, type, message, file, first_seen, last_seen, count)VALUES (?, ?, ?, ?, NOW(), NOW(), 1));$stmt-execute([$fingerprint, $error[type] ?? Unknown, $error[message], $error[file] . : . ($error[line] ?? 0)]);}}public function getTopErrors(int $limit 10): array{return $this-pdo-query(SELECT * FROM error_groups WHERE status openORDER BY count DESC, last_seen DESCLIMIT $limit)-fetchAll();}public function getErrorStats(): array{$stats [];$stmt $this-pdo-query(SELECTCOUNT(*) as total_groups,SUM(CASE WHEN status open THEN 1 ELSE 0 END) as open_groups,SUM(CASE WHEN status resolved THEN 1 ELSE 0 END) as resolved_groups,SUM(count) as total_occurrencesFROM error_groups);$stats[group_stats] $stmt-fetch();$stmt $this-pdo-query(SELECT DATE_FORMAT(last_seen, %Y-%m-%d) as date, COUNT(*) as countFROM error_groupsWHERE last_seen DATE_SUB(NOW(), INTERVAL 7 DAY)GROUP BY DATE_FORMAT(last_seen, %Y-%m-%d));$stats[daily_stats] $stmt-fetchAll();return $stats;}}?完善的监控和告警系统是线上应用的守夜人。错误分级处理严重错误立即通知普通错误汇总分析。配合日志中心可以快速定位和解决问题。监控不是目的尽快恢复服务才是
PHP异常监控与告警系统设计
PHP异常监控与告警系统设计线上应用随时可能出问题。好的监控和告警系统能在第一时间发现问题并通知相关人员。今天说说PHP应用的异常监控和告警实现。应用级别的错误监控phpclass ErrorMonitor{private string $appName;private string $env;private string $logDir;private array $webhookUrls;public function __construct(string $appName, string $env production, string $logDir /var/log/app){$this-appName $appName;$this-env $env;$this-logDir rtrim($logDir, /);$this-webhookUrls [];if (!is_dir($this-logDir)) {mkdir($this-logDir, 0755, true);}$this-registerHandlers();}public function addWebhook(string $url): void{$this-webhookUrls[] $url;}public function addSlackWebhook(string $url): void{$this-webhookUrls[slack] $url;}public function addDingTalkWebhook(string $url): void{$this-webhookUrls[dingtalk] $url;}private function registerHandlers(): void{set_error_handler([$this, handleError]);set_exception_handler([$this, handleException]);register_shutdown_function([$this, handleShutdown]);}public function handleError(int $level, string $message, string $file, int $line): bool{// 忽略被 抑制的错误if (!(error_reporting() $level)) {return false;}$error [level $this-getErrorLevel($level),message $message,file $file,line $line,trace debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5),];$this-logError($error);$this-checkAlertThreshold($error);return true;}public function handleException(Throwable $e): void{$error [type get_class($e),message $e-getMessage(),file $e-getFile(),line $e-getLine(),trace $e-getTraceAsString(),level EXCEPTION,];$this-logError($error);$this-sendAlert($error);}public function handleShutdown(): void{$error error_get_last();if ($error ! null in_array($error[type], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {$this-handleError($error[type], $error[message], $error[file], $error[line]);$this-sendAlert([level FATAL,message $error[message],file $error[file],line $error[line],]);}}private function logError(array $error): void{$entry array_merge([time date(Y-m-d\TH:i:s.vP),app $this-appName,env $this-env,host gethostname(),pid getmypid(),request_uri $_SERVER[REQUEST_URI] ?? cli,request_method $_SERVER[REQUEST_METHOD] ?? CLI,ip $_SERVER[REMOTE_ADDR] ?? 127.0.0.1,], $error);$logFile $this-logDir . /errors- . date(Y-m-d) . .log;file_put_contents($logFile, json_encode($entry, JSON_UNESCAPED_UNICODE) . \n, FILE_APPEND | LOCK_EX);}public function checkAlertThreshold(array $error): void{if (in_array($error[level], [ERROR, FATAL])) {$this-sendAlert($error);}}public function sendAlert(array $error): void{$message *{$error[level]}*: {$error[message]}\n;$message . 文件: {$error[file]}:{$error[line]}\n;$message . 请求: {$_SERVER[REQUEST_URI] ?? cli}\n;$message . 时间: {$error[time] ?? date(Y-m-d H:i:s)}\n;// Slack通知if (isset($this-webhookUrls[slack])) {$this-sendSlackNotification($this-webhookUrls[slack], $message);}// 钉钉通知if (isset($this-webhookUrls[dingtalk])) {$this-sendDingTalkNotification($this-webhookUrls[dingtalk], $message);}// 错误次数超过阈值时发送短信或电话告警$errorCount $this-getRecentErrorCount(300);if ($errorCount 50) {$this-sendUrgentAlert(过去5分钟有{$errorCount}次错误);}}private function sendSlackNotification(string $webhook, string $message): void{$payload json_encode([text {$this-appName}[{$this-env}] 错误告警,attachments [[text $message, color danger],],]);$ch curl_init($webhook);curl_setopt_array($ch, [CURLOPT_POST true,CURLOPT_POSTFIELDS $payload,CURLOPT_HTTPHEADER [Content-Type: application/json],CURLOPT_RETURNTRANSFER true,CURLOPT_TIMEOUT 5,]);curl_exec($ch);curl_close($ch);}private function sendDingTalkNotification(string $webhook, string $message): void{$payload json_encode([msgtype text,text [content {$this-appName}[{$this-env}] 错误告警\n{$message}],]);$ch curl_init($webhook);curl_setopt_array($ch, [CURLOPT_POST true,CURLOPT_POSTFIELDS $payload,CURLOPT_HTTPHEADER [Content-Type: application/json],CURLOPT_RETURNTRANSFER true,CURLOPT_TIMEOUT 5,]);curl_exec($ch);curl_close($ch);}private function sendUrgentAlert(string $message): void{// 短信或电话告警调用第三方APIerror_log(紧急告警: $message);}private function getRecentErrorCount(int $seconds): int{$count 0;$cutoff time() - $seconds;$logFile $this-logDir . /errors- . date(Y-m-d) . .log;if (!file_exists($logFile)) return 0;$handle fopen($logFile, r);while (($line fgets($handle)) ! false) {$entry json_decode($line, true);if ($entry strtotime($entry[time] ?? ) $cutoff) {$count;}}fclose($handle);return $count;}private function getErrorLevel(int $level): string{return match ($level) {E_ERROR, E_USER_ERROR ERROR,E_WARNING, E_USER_WARNING WARNING,E_NOTICE, E_USER_NOTICE NOTICE,E_DEPRECATED, E_USER_DEPRECATED DEPRECATED,default UNKNOWN,};}}$monitor new ErrorMonitor(myapp, production, /var/log/app);$monitor-addSlackWebhook(https://hooks.slack.com/services/xxx);$monitor-addDingTalkWebhook(https://oapi.dingtalk.com/robot/send?access_tokenxxx);// 触发一个测试错误trigger_error(测试错误, E_USER_WARNING);throw new RuntimeException(测试异常);?监控系统的分组和统计功能phpclass ErrorAggregator{private PDO $pdo;public function __construct(PDO $pdo){$this-pdo $pdo;$this-initTable();}private function initTable(): void{$this-pdo-exec(CREATE TABLE IF NOT EXISTS error_groups (id INT AUTO_INCREMENT PRIMARY KEY,fingerprint VARCHAR(64) NOT NULL UNIQUE,type VARCHAR(100),message TEXT,file VARCHAR(500),first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,count INT DEFAULT 1,status ENUM(open, resolved, ignored) DEFAULT open,assigned_to VARCHAR(100),INDEX idx_status (status),INDEX idx_last_seen (last_seen)) ENGINEInnoDB DEFAULT CHARSETutf8mb4);}public function aggregate(array $error): void{$fingerprint md5($error[file] . : . $error[line] . : . $error[message]);$stmt $this-pdo-prepare(SELECT * FROM error_groups WHERE fingerprint ?);$stmt-execute([$fingerprint]);$group $stmt-fetch();if ($group) {$stmt $this-pdo-prepare(UPDATE error_groups SET count count 1, last_seen NOW()WHERE fingerprint ?);$stmt-execute([$fingerprint]);} else {$stmt $this-pdo-prepare(INSERT INTO error_groups (fingerprint, type, message, file, first_seen, last_seen, count)VALUES (?, ?, ?, ?, NOW(), NOW(), 1));$stmt-execute([$fingerprint, $error[type] ?? Unknown, $error[message], $error[file] . : . ($error[line] ?? 0)]);}}public function getTopErrors(int $limit 10): array{return $this-pdo-query(SELECT * FROM error_groups WHERE status openORDER BY count DESC, last_seen DESCLIMIT $limit)-fetchAll();}public function getErrorStats(): array{$stats [];$stmt $this-pdo-query(SELECTCOUNT(*) as total_groups,SUM(CASE WHEN status open THEN 1 ELSE 0 END) as open_groups,SUM(CASE WHEN status resolved THEN 1 ELSE 0 END) as resolved_groups,SUM(count) as total_occurrencesFROM error_groups);$stats[group_stats] $stmt-fetch();$stmt $this-pdo-query(SELECT DATE_FORMAT(last_seen, %Y-%m-%d) as date, COUNT(*) as countFROM error_groupsWHERE last_seen DATE_SUB(NOW(), INTERVAL 7 DAY)GROUP BY DATE_FORMAT(last_seen, %Y-%m-%d));$stats[daily_stats] $stmt-fetchAll();return $stats;}}?完善的监控和告警系统是线上应用的守夜人。错误分级处理严重错误立即通知普通错误汇总分析。配合日志中心可以快速定位和解决问题。监控不是目的尽快恢复服务才是