PHP 健康检查失败告警

wen PHP项目 2

本文目录导读:

PHP 健康检查失败告警

  1. 健康检查脚本方案
  2. Shell 监控脚本
  3. 定时任务配置
  4. 监控平台集成
  5. 告警通知配置
  6. 最佳实践建议
  7. 测试命令

我来提供一个完整的 PHP 健康检查失败告警方案,包括监控脚本、告警配置和通知机制。

健康检查脚本方案

基础健康检查脚本

<?php
/**
 * PHP健康检查脚本
 * 支持命令行和Web访问两种方式
 */
class HealthChecker {
    private $config;
    public function __construct() {
        $this->config = [
            'php_version_min' => '7.4',
            'memory_limit_min' => '128M',
            'disk_space_min' => 1024, // MB
            'cache_backend' => 'redis',
            'services' => [
                'redis' => ['host' => '127.0.0.1', 'port' => 6379],
                'mysql' => ['host' => '127.0.0.1', 'port' => 3306],
                'rabbitmq' => ['host' => '127.0.0.1', 'port' => 5672],
            ],
            'webhook_url' => getenv('ALERT_WEBHOOK_URL') ?: '',
            'notification_services' => [
                'slack' => getenv('SLACK_WEBHOOK_URL') ?: '',
                'dingtalk' => getenv('DINGTALK_WEBHOOK_URL') ?: '',
                'email' => getenv('ADMIN_EMAIL') ?: '',
            ]
        ];
    }
    public function check() {
        $results = [];
        $healthy = true;
        // 基础环境检查
        $results['php_version'] = $this->checkPhpVersion();
        $results['php_extensions'] = $this->checkPhpExtensions();
        $results['memory'] = $this->checkMemory();
        $results['disk'] = $this->checkDiskSpace();
        $results['permissions'] = $this->checkFilePermissions();
        // 服务检查
        $results['services'] = $this->checkServices();
        // 应用检查
        $results['application'] = $this->checkApplication();
        // 汇总结果
        foreach ($results as $category => $checks) {
            foreach ($checks as $check => $result) {
                if (!$result['healthy']) {
                    $healthy = false;
                    $this->handleFailure($check, $result);
                }
            }
        }
        return ['healthy' => $healthy, 'checks' => $results];
    }
    private function checkPhpVersion() {
        $current = PHP_VERSION;
        return [
            'healthy' => version_compare($current, $this->config['php_version_min'], '>='),
            'current' => $current,
            'expected' => $this->config['php_version_min']
        ];
    }
    private function checkPhpExtensions() {
        $required = ['pdo', 'redis', 'json', 'mbstring', 'curl'];
        $result = ['healthy' => true, 'missing' => []];
        foreach ($required as $ext) {
            if (!extension_loaded($ext)) {
                $result['healthy'] = false;
                $result['missing'][] = $ext;
            }
        }
        return $result;
    }
    private function checkMemory() {
        $limit = ini_get('memory_limit');
        return [
            'healthy' => $this->parseMemoryLimit($limit) >= $this->config['memory_limit_min'],
            'current' => $limit,
            'expected' => $this->config['memory_limit_min']
        ];
    }
    private function checkDiskSpace() {
        $free = disk_free_space("/");
        $freeMB = $free != false ? round($free / 1024 / 1024, 2) : 0;
        return [
            'healthy' => $freeMB >= $this->config['disk_space_min'],
            'free_space_mb' => $freeMB,
            'expected' => $this->config['disk_space_min']
        ];
    }
    private function checkFilePermissions() {
        $dirs = ['/var/log', '/tmp', '/var/www/html/storage'];
        $result = ['healthy' => true, 'issues' => []];
        foreach ($dirs as $dir) {
            if (file_exists($dir) && !is_writable($dir)) {
                $result['healthy'] = false;
                $result['issues'][] = $dir . ' is not writable';
            }
        }
        return $result;
    }
    private function checkServices() {
        $result = ['healthy' => true, 'services' => []];
        foreach ($this->config['services'] as $name => $config) {
            $connect = @fsockopen($config['host'], $config['port'], $errno, $errstr, 3);
            $healthy = $connect !== false;
            if ($connect) {
                fclose($connect);
            }
            $result['services'][$name] = [
                'healthy' => $healthy,
                'error' => $healthy ? null : $errstr
            ];
            if (!$healthy) {
                $result['healthy'] = false;
            }
        }
        return $result;
    }
    private function checkApplication() {
        // 应用特定检查,可扩展
        $result = ['healthy' => true, 'checks' => []];
        // 检查缓存
        $result['checks']['cache'] = $this->checkCache();
        // 检查队列
        $result['checks']['queue'] = $this->checkQueue();
        // 检查数据库连接
        $result['checks']['database'] = $this->checkDatabase();
        return $result;
    }
    private function parseMemoryLimit($limit) {
        $unit = strtolower(substr($limit, -1));
        $value = (int)$limit;
        switch ($unit) {
            case 'g': return $value * 1024;
            case 'm': return $value;
            case 'k': return $value / 1024;
            default: return $value;
        }
    }
    private function handleFailure($check, $result) {
        $message = $this->formatAlertMessage($check, $result);
        $this->sendAlert($message);
        $this->logFailure($message);
    }
    private function sendAlert($message) {
        if ($this->config['notification_services']['slack']) {
            $this->sendSlackAlert($message);
        }
        if ($this->config['notification_services']['dingtalk']) {
            $this->sendDingTalkAlert($message);
        }
        if ($this->config['notification_services']['email']) {
            $this->sendEmailAlert($message);
        }
    }
    private function sendSlackAlert($message) {
        $data = json_encode([
            'text' => ":warning: *PHP Health Check Alert*\n$message"
        ]);
        $this->postToWebhook($this->config['notification_services']['slack'], $data);
    }
    private function sendDingTalkAlert($message) {
        $data = json_encode([
            'msgtype' => 'markdown',
            'markdown' => [
                'title' => 'PHP健康检查告警',
                'text' => "### PHP健康检查告警\n\n$message"
            ]
        ]);
        $this->postToWebhook($this->config['notification_services']['dingtalk'], $data);
    }
    private function sendEmailAlert($message) {
        $subject = '[告警] PHP健康检查失败';
        mail($this->config['notification_services']['email'], $subject, $message);
    }
    private function postToWebhook($url, $data) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        curl_exec($ch);
        curl_close($ch);
    }
    private function formatAlertMessage($check, $result) {
        $message = "检查项: $check\n";
        $message .= "结果: " . ($result['healthy'] ? '正常' : '异常') . "\n";
        if (isset($result['current'])) {
            $message .= "当前值: {$result['current']}\n";
        }
        if (isset($result['expected'])) {
            $message .= "期望值: {$result['expected']}\n";
        }
        if (isset($result['error'])) {
            $message .= "错误信息: {$result['error']}\n";
        }
        return $message;
    }
    private function logFailure($message) {
        $logFile = '/var/log/php-healthcheck.log';
        $timestamp = date('Y-m-d H:i:s');
        file_put_contents($logFile, "[$timestamp] $message\n", FILE_APPEND);
    }
}
// 执行检查
$checker = new HealthChecker();
$result = $checker->check();
// CLI模式下输出结果
if (PHP_SAPI === 'cli') {
    echo json_encode($result, JSON_PRETTY_PRINT) . "\n";
    exit($result['healthy'] ? 0 : 1);
}
// Web访问模式
header('Content-Type: application/json');
http_response_code($result['healthy'] ? 200 : 503);
echo json_encode($result, JSON_PRETTY_PRINT);

Shell 监控脚本

#!/bin/bash
# healthcheck_wrapper.sh - 包装脚本用于定期监控
#!/bin/bash
# healthcheck_wrapper.sh - 包装脚本用于定期监控
# 配置
PHP_BIN="/usr/bin/php"
SCRIPT_PATH="/var/www/html/healthcheck.php"
LOG_FILE="/var/log/php-healthcheck.log"
ALERT_LOG="/var/log/php-healthcheck-alerts.log"
WEBHOOK_URL_ENV="ALERT_WEBHOOK_URL"
MAX_RETRIES=3
RETRY_INTERVAL=60 # 秒
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
    echo -e "$1"
}
# 执行健康检查
run_healthcheck() {
    local output
    local exit_code
    output=$($PHP_BIN "$SCRIPT_PATH" 2>&1)
    exit_code=$?
    if [ $exit_code -ne 0 ]; then
        log_message "${RED}健康检查失败${NC}"
        log_message "$output"
        return 1
    else
        log_message "${GREEN}健康检查通过${NC}"
        echo "$output" | jq . >> "$LOG_FILE" 2>/dev/null || true
        return 0
    fi
}
# 重试逻辑
retry_check() {
    local attempts=0
    local max_retries=$MAX_RETRIES
    while [ $attempts -lt $max_retries ]; do
        if run_healthcheck; then
            return 0
        fi
        attempts=$((attempts + 1))
        log_message "重试 $attempts/$max_retries (等待${RETRY_INTERVAL}秒)"
        sleep $RETRY_INTERVAL
    done
    return 1
}
# 发送告警
send_alert() {
    local alert_message="PHP健康检查连续${MAX_RETRIES}次失败"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    # 记录告警
    echo "[$timestamp] $alert_message" >> "$ALERT_LOG"
    # 发送到webhook
    if [ ! -z "$WEBHOOK_URL_ENV" ]; then
        curl -X POST -H 'Content-Type: application/json' \
             -d "{\"text\": \"🚨 $alert_message\n时间: $timestamp\"}" \
             "$WEBHOOK_URL_ENV"
    fi
    # 发送邮件(如果配置了mail命令)
    if command -v mail &> /dev/null; then
        echo "$alert_message" | mail -s "PHP健康检查告警" "$ADMIN_EMAIL" 2>/dev/null
    fi
}
# 主函数
main() {
    log_message "开始执行健康检查..."
    if retry_check; then
        log_message "健康检查最终结果: 正常"
        return 0
    else
        log_message "健康检查最终结果: 失败"
        send_alert
        return 1
    fi
}
# 运行
main
exit $?

定时任务配置

# crontab 配置示例(每5分钟执行一次)
*/5 * * * * /usr/bin/php /var/www/html/healthcheck.php --quiet > /dev/null 2>&1
# 或使用Shell脚本(每5分钟检查,失败则立即告警)
*/5 * * * * /usr/local/bin/healthcheck_wrapper.sh >> /var/log/php-healthcheck-cron.log 2>&1
# 更复杂的监控(每5分钟常规检查,每小时详细检查)
*/5 * * * * /usr/local/bin/healthcheck_wrapper.sh --quick
0 * * * * /usr/local/bin/healthcheck_wrapper.sh --full
# 启动时检查
@reboot /usr/local/bin/healthcheck_wrapper.sh

监控平台集成

Prometheus 导出器

<?php
// prometheus_exporter.php - Prometheus metrics 导出器
class PrometheusExporter {
    private $healthChecker;
    public function __construct() {
        $this->healthChecker = new HealthChecker();
    }
    public function export() {
        $result = $this->healthChecker->check();
        $metrics = [];
        // 基础指标
        $metrics[] = '# HELP php_health_check_total PHP Health Check Status';
        $metrics[] = '# TYPE php_health_check_total gauge';
        $metrics[] = "php_health_check_total " . ($result['healthy'] ? 1 : 0);
        // 服务状态
        foreach ($result['checks']['services']['services'] as $name => $status) {
            $metrics[] = "# HELP php_service_{$name} Status of {$name} service";
            $metrics[] = "# TYPE php_service_{$name} gauge";
            $metrics[] = "php_service_{$name} " . ($status['healthy'] ? 1 : 0);
        }
        // 资源使用
        $metrics[] = '# HELP php_memory_usage Current memory usage in MB';
        $metrics[] = '# TYPE php_memory_usage gauge';
        $metrics[] = "php_memory_usage " . memory_get_usage(true) / 1024 / 1024;
        return implode("\n", $metrics);
    }
}
header('Content-Type: text/plain; version=0.0.4');
$exporter = new PrometheusExporter();
echo $exporter->export();

Grafana 告警规则

# grafana-alerts.yml
apiVersion: 1
groups:
  - name: PHP Health Alerts
    rules:
      - alert: PHPHealthCheckFailed
        expr: php_health_check_total == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "PHP Health Check Failed"
          description: "PHP application health check has been failing for 5 minutes"
      - alert: PHPHighMemoryUsage
        expr: php_memory_usage_percent > 85
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High PHP Memory Usage"
          description: "PHP memory usage is above 85%"
      - alert: PHPRedisDown
        expr: php_service_redis == 0
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Redis Service Down"
          description: "Redis service is not responding"

告警通知配置

Slack 告警配置

// slack-webhook-config.json
{
  "channel": "#php-alerts",
  "username": "HealthBot",
  "icon_emoji": ":ghost:",
  "text": "PHP Monitoring System",
  "attachments": [
    {
      "color": "danger",
      "title": "PHP Health Check Failed",
      "fields": [
        {
          "title": "Environment",
          "value": "production",
          "short": true
        },
        {
          "title": "Timestamp",
          "value": "2024-01-01 12:00:00",
          "short": true
        }
      ],
      "actions": [
        {
          "type": "button",
          "text": "View Dashboard",
          "url": "http://monitoring.example.com"
        }
      ]
    }
  ]
}

钉钉/企业微信告警配置

{
  "msgtype": "markdown",
  "markdown": {: "PHP监控告警",
    "text": "## ⚠️ PHP健康检查告警\n\n**检查项:** PHP Version\n**状态:** ❌ 异常\n**详情:** 版本过低\n\n[查看监控面板](http://monitoring.example.com)"
  },
  "at": {
    "isAtAll": false,
    "atMobiles": ["13800138000"]
  }
}

最佳实践建议

  1. 分级告警:区分紧急(服务宕机)和警告(资源使用率高)
  2. 告警聚合:避免重复告警,设置聚合窗口
  3. 自动恢复:实现简单的自动重启脚本
  4. 监控可视化:使用 Grafana 展示趋势
  5. 告警分流:根据服务重要性分配不同通知渠道
  6. 留档记录:保存历史告警记录用于分析

测试命令

# 手动测试健康检查
curl http://localhost/healthcheck.php
# 测试告警
curl -X POST http://localhost/healthcheck.php --test-alert
# 查看日志
tail -f /var/log/php-healthcheck.log
# 强制触发告警(模拟故障)
PHP_ALERT_TEST=true php healthcheck.php

这个方案提供了完整的监控告警体系,可以根据实际需要调整和扩展,记得根据你的部署环境调整路径、端口和通知渠道配置。

抱歉,评论功能暂时关闭!