PHP异常监控与告警系统设计
线上应用随时可能出问题。好的监控和告警系统能在第一时间发现问题并通知相关人员。今天说说PHP应用的异常监控和告警实现。
应用级别的错误监控:
```php
class 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
{
// 短信或电话告警(调用第三方API)
error_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_token=xxx');
// 触发一个测试错误
trigger_error('测试错误', E_USER_WARNING);
throw new RuntimeException('测试异常');
?>
```
监控系统的分组和统计功能:
```php
class 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)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
");
}
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 = 'open'
ORDER BY count DESC, last_seen DESC
LIMIT $limit
")->fetchAll();
}
public function getErrorStats(): array
{
$stats = [];
$stmt = $this->pdo->query("
SELECT
COUNT(*) 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_occurrences
FROM error_groups
");
$stats['group_stats'] = $stmt->fetch();
$stmt = $this->pdo->query("
SELECT DATE_FORMAT(last_seen, '%Y-%m-%d') as date, COUNT(*) as count
FROM error_groups
WHERE 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异常监控与告警系统设计
张小明
前端开发工程师
MATLAB版风电功率LSTM多步滚动预测工具包(含实测数据与完整可运行代码)
本文还有配套的精品资源,点击获取 简介:直接导入风电场实测功率Excel数据,就能跑通LSTM递归预测全流程:自动完成数据标准化、滑动窗口序列构造、模型训练、多步滚动预测,并输出MAE、RMSE等误差结果。包里包含主程序…
别再乱加镜像源了!Conda Channel优先级与配置避坑指南(附清华源最新地址)
Conda镜像源管理进阶:优先级机制与高效配置实战刚接触Conda时,我们总习惯性地添加各种镜像源,天真地以为"源越多下载越快"。直到某天发现安装tensorflow时莫名报错,或是pandas版本始终无法匹配,才意识到镜像…
效率直接起飞 AI论文写作工具测评:2026最新推荐与对比
2026年真正好用的AI论文写作工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 …
高性能移动端UI架构解析:Arco Design Mobile React组件库深度实践指南
高性能移动端UI架构解析:Arco Design Mobile React组件库深度实践指南 【免费下载链接】arco-design-mobile React mobile UI components library based on Arco Design 项目地址: https://gitcode.com/gh_mirrors/ar/arco-design-mobile Arco Design Mobile…
毕业论文是你的“产品”,答辩PPT就是它的“发布会”
毕业答辩是数年学业生涯的收官之战,而答辩PPT则是整场答辩的核心门面。不少同学耗费数月打磨论文,却卡在PPT制作环节:格式不符合院校规范、内容堆砌毫无逻辑、重点模糊抓不住评委关注点、临场汇报节奏混乱……繁琐的排版修改、复杂的内容提炼…
不止于SSH登录:解锁Linux Expect的5个隐藏用法,让你的Shell脚本更智能
不止于SSH登录:解锁Linux Expect的5个隐藏用法,让你的Shell脚本更智能 当大多数开发者提起Expect时,脑海中浮现的往往是自动化SSH登录的场景。这个诞生于1990年代的Tcl扩展工具,其价值远不止于此。在真实的运维开发生态中…