PHP 怎么PHP 被动监控

wen PHP项目 1

本文目录导读:

PHP 怎么PHP 被动监控

  1. 基础被动监控实现方式
  2. 使用 register_shutdown_function 实现结束监控
  3. 使用 set_error_handler 错误监控
  4. 使用 PHP 内置函数监控
  5. 完整的被动监控系统
  6. 数据库表结构
  7. Nginx 配置示例(用于被动日志记录)
  8. 使用建议
  9. 优势
  10. 注意事项

我来详细解释PHP中的被动监控实现方式,被动监控指的是不主动轮询,而是等待事件发生或被调用时进行监控记录。

基础被动监控实现方式

自动记录请求信息

<?php
// 在入口文件(如 index.php)开始处添加
class PassiveMonitor {
    private static $instance = null;
    private $startTime;
    public function __construct() {
        $this->startTime = microtime(true);
        $this->recordRequest();
    }
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    public function recordRequest() {
        $data = [
            'timestamp' => date('Y-m-d H:i:s'),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET',
            'uri' => $_SERVER['REQUEST_URI'] ?? '/',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown',
            'execution_time' => 0
        ];
        // 保存到文件或数据库
        $this->saveToFile($data);
    }
    private function saveToFile($data) {
        $logFile = __DIR__ . '/monitor/log.txt';
        $dir = dirname($logFile);
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        $line = json_encode($data) . PHP_EOL;
        file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
    }
}
// 初始化监控
$monitor = PassiveMonitor::getInstance();
?>

使用 register_shutdown_function 实现结束监控

<?php
class ExecutionMonitor {
    private $startTime;
    private $memoryStart;
    public function __construct() {
        $this->startTime = microtime(true);
        $this->memoryStart = memory_get_usage();
        // 注册关闭函数
        register_shutdown_function([$this, 'onShutdown']);
        // 捕获致命错误
        register_shutdown_function([$this, 'handleFatalError']);
    }
    public function onShutdown() {
        $executionTime = microtime(true) - $this->startTime;
        $memoryUsed = memory_get_usage() - $this->memoryStart;
        $peakMemory = memory_get_peak_usage(true);
        $error = error_get_last();
        $data = [
            'time' => date('Y-m-d H:i:s'),
            'url' => ($_SERVER['REQUEST_URI'] ?? 'CLI') . '?' . ($_SERVER['QUERY_STRING'] ?? ''),
            'execution_time' => round($executionTime, 4),
            'memory_used' => $this->formatBytes($memoryUsed),
            'peak_memory' => $this->formatBytes($peakMemory),
            'has_error' => $error !== null,
            'error_message' => $error['message'] ?? '',
            'php_version' => PHP_VERSION
        ];
        $this->logToDatabase($data);
    }
    public function handleFatalError() {
        $error = error_get_last();
        if ($error !== null && $error['type'] === E_ERROR) {
            // 记录致命错误
            $this->logCriticalError($error);
        }
    }
    private function formatBytes($bytes, $precision = 2) {
        $units = ['B', 'KB', 'MB', 'GB'];
        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min($pow, count($units) - 1);
        return round($bytes / pow(1024, $pow), $precision) . ' ' . $units[$pow];
    }
    private function logToDatabase($data) {
        // 使用PDO记录到数据库
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=monitor', 'user', 'pass');
            $sql = "INSERT INTO request_logs (timestamp, url, execution_time, memory_used, peak_memory, has_error, error_message) 
                    VALUES (:time, :url, :exec_time, :memory, :peak_memory, :has_error, :error_msg)";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([
                ':time' => $data['time'],
                ':url' => $data['url'],
                ':exec_time' => $data['execution_time'],
                ':memory' => $data['memory_used'],
                ':peak_memory' => $data['peak_memory'],
                ':has_error' => $data['has_error'],
                ':error_msg' => $data['error_message']
            ]);
        } catch (PDOException $e) {
            error_log("Monitor DB Error: " . $e->getMessage());
        }
    }
}
// 初始化
$monitor = new ExecutionMonitor();
?>

使用 set_error_handler 错误监控

<?php
class ErrorMonitor {
    private static $errors = [];
    public static function init() {
        set_error_handler([__CLASS__, 'handleError']);
        set_exception_handler([__CLASS__, 'handleException']);
    }
    public static function handleError($severity, $message, $file, $line) {
        $error = [
            'type' => self::getErrorType($severity),
            'message' => $message,
            'file' => $file,
            'line' => $line,
            'time' => date('Y-m-d H:i:s'),
            'url' => $_SERVER['REQUEST_URI'] ?? 'CLI'
        ];
        self::$errors[] = $error;
        self::logError($error);
        // 根据错误级别决定是否继续执行
        if ($severity & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR)) {
            return false; // 停止脚本
        }
        return true;
    }
    public static function handleException($exception) {
        $data = [
            'type' => 'Exception',
            'message' => $exception->getMessage(),
            'file' => $exception->getFile(),
            'line' => $exception->getLine(),
            'trace' => $exception->getTraceAsString(),
            'time' => date('Y-m-d H:i:s')
        ];
        self::logCriticalError($data);
        // 显示友好的错误页面
        if (!ini_get('display_errors')) {
            http_response_code(500);
            include 'error_page.php';
        }
    }
    private static function getErrorType($severity) {
        $types = [
            E_ERROR => 'Fatal Error',
            E_WARNING => 'Warning',
            E_PARSE => 'Parse Error',
            E_NOTICE => 'Notice',
            E_CORE_ERROR => 'Core Error',
            E_CORE_WARNING => 'Core Warning',
            E_COMPILE_ERROR => 'Compile Error',
            E_COMPILE_WARNING => 'Compile Warning',
            E_USER_ERROR => 'User Error',
            E_USER_WARNING => 'User Warning',
            E_USER_NOTICE => 'User Notice',
            E_STRICT => 'Strict',
            E_RECOVERABLE_ERROR => 'Recoverable Error',
            E_DEPRECATED => 'Deprecated',
            E_USER_DEPRECATED => 'User Deprecated'
        ];
        return $types[$severity] ?? 'Unknown Error';
    }
    private static function logError($error) {
        $logFile = __DIR__ . '/errors/error.log';
        $dir = dirname($logFile);
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        $logLine = sprintf(
            "[%s] %s: %s in %s:%d\n",
            $error['time'],
            $error['type'],
            $error['message'],
            $error['file'],
            $error['line']
        );
        file_put_contents($logFile, $logLine, FILE_APPEND | LOCK_EX);
    }
}
// 在脚本开始处调用
ErrorMonitor::init();
?>

使用 PHP 内置函数监控

<?php
class PerformanceMonitor {
    private static $marks = [];
    public static function mark($name) {
        self::$marks[$name] = [
            'time' => microtime(true),
            'memory' => memory_get_usage()
        ];
    }
    public static function getReport() {
        $report = [];
        $previous = null;
        foreach (self::$marks as $name => $data) {
            $row = [
                'name' => $name,
                'time' => date('H:i:s', (int)$data['time']),
                'memory' => self::formatBytes($data['memory'])
            ];
            if ($previous) {
                $row['time_diff'] = round($data['time'] - $previous['time'], 4) . 's';
                $row['memory_diff'] = self::formatBytes($data['memory'] - $previous['memory']);
            } else {
                $row['time_diff'] = '0s';
                $row['memory_diff'] = '0B';
            }
            $report[] = $row;
            $previous = $data;
        }
        return $report;
    }
    private static function formatBytes($bytes) {
        return round($bytes / 1024, 2) . ' KB';
    }
}
// 使用示例
PerformanceMonitor::mark('start');
// ... 业务代码 ...
PerformanceMonitor::mark('after_db_query');
// ... 更多业务代码 ...
PerformanceMonitor::mark('end');
print_r(PerformanceMonitor::getReport());
?>

完整的被动监控系统

<?php
class PassiveMonitorSystem {
    private $config;
    private $startTime;
    public function __construct($config = []) {
        $this->config = array_merge([
            'log_db' => false,
            'log_file' => true,
            'log_errors' => true,
            'slow_request_threshold' => 2.0, // 秒
            'log_memory' => true
        ], $config);
        $this->startTime = microtime(true);
        $this->initialize();
    }
    private function initialize() {
        // 注册各种钩子
        register_shutdown_function([$this, 'finalize']);
        if ($this->config['log_errors']) {
            set_error_handler([$this, 'handleError']);
            set_exception_handler([$this, 'handleException']);
        }
    }
    public function finalize() {
        $executionTime = microtime(true) - $this->startTime;
        $data = [
            'request_id' => uniqid('req_', true),
            'timestamp' => date('Y-m-d H:i:s'),
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'CLI',
            'url' => $this->getCurrentUrl(),
            'ip' => $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'execution_time' => $executionTime,
            'memory_usage' => memory_get_usage(true),
            'peak_memory' => memory_get_peak_usage(true),
            'is_slow' => $executionTime > $this->config['slow_request_threshold'],
            'php_version' => PHP_VERSION,
            'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? ''
        ];
        $this->save($data);
    }
    private function getCurrentUrl() {
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
        $uri = $_SERVER['REQUEST_URI'] ?? '/';
        return "$protocol://$host$uri";
    }
    private function save($data) {
        if ($this->config['log_file']) {
            $this->saveToFile($data);
        }
        if ($this->config['log_db']) {
            $this->saveToDatabase($data);
        }
        // 慢请求告警
        if ($data['is_slow']) {
            $this->slowRequestAlert($data);
        }
    }
    private function saveToFile($data) {
        $logFile = __DIR__ . '/monitor/' . date('Y-m-d') . '.log';
        $dir = dirname($logFile);
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        $line = json_encode($data) . PHP_EOL;
        file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
    }
    private function slowRequestAlert($data) {
        // 发送告警,例如邮件或短信
        $message = sprintf(
            "Slow Request Alert!\nURL: %s\nTime: %.2fs\nMemory: %s\nTime: %s",
            $data['url'],
            $data['execution_time'],
            $this->formatBytes($data['memory_usage']),
            $data['timestamp']
        );
        // 记录到单独的慢请求日志
        $slowLogFile = __DIR__ . '/monitor/slow_requests.log';
        file_put_contents($slowLogFile, $message . PHP_EOL, FILE_APPEND | LOCK_EX);
    }
    private function formatBytes($bytes) {
        return round($bytes / 1024 / 1024, 2) . ' MB';
    }
}
// 使用
$monitor = new PassiveMonitorSystem([
    'log_db' => false,
    'log_file' => true,
    'slow_request_threshold' => 1.0
]);
?>

数据库表结构

CREATE TABLE request_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    request_id VARCHAR(50) UNIQUE,
    timestamp DATETIME,
    method VARCHAR(10),
    url TEXT,
    ip VARCHAR(45),
    user_agent TEXT,
    execution_time DECIMAL(10,4),
    memory_usage BIGINT,
    peak_memory BIGINT,
    is_slow BOOLEAN DEFAULT FALSE,
    php_version VARCHAR(10),
    server_software VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_timestamp (timestamp),
    INDEX idx_is_slow (is_slow),
    INDEX idx_execution_time (execution_time)
);
CREATE TABLE error_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    timestamp DATETIME,
    type VARCHAR(50),
    message TEXT,
    file VARCHAR(255),
    line INT,
    url TEXT,
    trace TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Nginx 配置示例(用于被动日志记录)

server {
    listen 80;
    server_name example.com;
    # 访问日志(被动记录)
    access_log /var/log/nginx/example_access.log;
    error_log /var/log/nginx/example_error.log;
    # 记录 POST 数据
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        # 记录请求体大小
        client_body_buffer_size 1k;
        client_max_body_size 10m;
    }
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        # 传递额外信息给 PHP
        fastcgi_param REQUEST_TIME $msec;
        fastcgi_param SERVER_ADDR $server_addr;
    }
}

使用建议

<?php
// 1. 在框架入口文件引入
require_once 'PassiveMonitor.php';
$monitor = new PassiveMonitor();
// 2. 或者在 composer.json 中添加自动加载
// "autoload": {
//     "files": [
//         "app/Monitor.php"
//     ]
// }
// 3. 数据库日志清理策略
// 定期清理旧数据
$cleanupDays = 30;
$pdo->exec("DELETE FROM request_logs WHERE timestamp < DATE_SUB(NOW(), INTERVAL $cleanupDays DAY)");
// 4. 日志文件轮转
// 使用 logrotate 或 PHP 自带的轮转
ini_set('log_rotate_max_files', 10);
?>

优势

  1. 零侵入性 - 不需要修改业务代码
  2. 自动触发 - 每次请求自动记录
  3. 实时性 - 立即记录问题
  4. 全面覆盖 - 监控所有请求

注意事项

  • 注意磁盘空间管理
  • 合理设置日志级别
  • 考虑性能影响(建议使用队列异步写入)
  • 敏感信息脱敏处理

这样实现的被动监控系统可以在不修改业务代码的情况下,自动记录所有请求信息和异常情况。

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