本文目录导读:

- 基础被动监控实现方式
- 使用 register_shutdown_function 实现结束监控
- 使用 set_error_handler 错误监控
- 使用 PHP 内置函数监控
- 完整的被动监控系统
- 数据库表结构
- Nginx 配置示例(用于被动日志记录)
- 使用建议
- 优势
- 注意事项
我来详细解释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);
?>
优势
- 零侵入性 - 不需要修改业务代码
- 自动触发 - 每次请求自动记录
- 实时性 - 立即记录问题
- 全面覆盖 - 监控所有请求
注意事项
- 注意磁盘空间管理
- 合理设置日志级别
- 考虑性能影响(建议使用队列异步写入)
- 敏感信息脱敏处理
这样实现的被动监控系统可以在不修改业务代码的情况下,自动记录所有请求信息和异常情况。