線上環境「沒有日誌等於沒有發生」。本文系統梳理 PHP 錯誤、異常的區別、內置日誌機制、Nginx/PHP-FPM 多層日誌關係、自定義處理器實現、以及生產環境落地的最佳實踐與工具推薦。
| 維度 | Error (錯誤) | Exception (異常) |
|---|---|---|
| 性質 | 腳本運行過程中不可預期的問題(語法、環境、資源) | 業務邏輯預期之外但可控的情況(驗證失敗、第三方調用異常) |
| 捕獲方式 | set_error_handler()、register_shutdown_function() | try-catch、set_exception_handler() |
| 類型層級 | E_ERROR, E_WARNING, E_NOTICE, E_DEPRECATED 等 | 所有繼承自 Throwable (PHP 7+) 的類 |
| 可恢復性 | 嚴重錯誤導致腳本終止 | 捕獲後可繼續執行 |
Throwable 接口,Error 和 Exception 同為其子類TypeError、ParseError、DivisionByZeroError 等原 Fatal Error 現為 Error 可捕獲set_error_handler() 無法捕獲 E_ERROR、E_PARSE、E_CORE_ERROR 等致命錯誤,需用 register_shutdown_function() 兜底// PHP 7 統一捕獲示例
try {
1 / 0; // 拋出 DivisionByZeroError (Error 子類)
} catch (Throwable $e) {
echo "捕獲: " . get_class($e) . " - " . $e->getMessage();
}
// 輸出: 捕獲: DivisionByZeroError - Division by zero
php-fpm.conf: error_log)php-fpm.conf: slowlog, request_slowlog_timeout)php.ini: error_log, log_errors, error_reporting)error_log() 等應用級日誌; 生產環境推薦 display_errors = Off log_errors = On error_log = /var/log/php/error.log error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT ; 忽略用戶棄用提示,保留核心棄用 ; 開發環境 display_errors = On error_reporting = E_ALL
; php-fpm.conf / www.conf error_log = /var/log/php-fpm/error.log slowlog = /var/log/php-fpm/slow.log request_slowlog_timeout = 5s ; 超過 5 秒記錄慢請求堆棧 request_terminate_timeout = 30s ; 請求硬性超時
$request_time/$upstream_response_time)排查鏈路:
1. Access Log 看 Status Code (5xx/499) →
2. PHP Error Log 看堆棧 →
3. PHP-FPM Slow Log 看慢查詢/死鎖 →
4. PHP-FPM Error Log 看進程重啟/段錯誤 →
5. Nginx Error Log 看上游連接異常
// bootstrap/app.php 或框架入口文件最早引入
// 1. 異常處理器(捕獲所有未捕獲的 Exception/Error)
set_exception_handler(function (Throwable $e) {
$log = [
'time' => date('c'),
'type' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
'request' => [
'method' => $_SERVER['REQUEST_METHOD'],
'uri' => $_SERVER['REQUEST_URI'],
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'body' => json_decode(file_get_contents('php://input'), true) ?? [],
],
];
error_log(json_encode($log, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n", 3, '/var/log/php/exception.log');
// 生產環境返回友好頁面/JSON
if (php_sapi_name() === 'cli') return;
http_response_code(500);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['code' => 500, 'msg' => '服務器內部錯誤', 'request_id' => uniqid()]);
});
// 2. 錯誤處理器(捕獲 Warning/Notice 等非致命錯誤)
set_error_handler(function (int $severity, string $message, string $file, int $line) {
if (!(error_reporting() & $severity)) return false; // 忽略被 error_reporting 屏蔽的級別
throw new ErrorException($message, 0, $severity, $file, $line); // 轉為異常,走上方統一處理
});
// 3. 致命錯誤兜底(E_ERROR, E_PARSE 等無法被 set_error_handler 捕獲)
register_shutdown_function(function () {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// 手動構造 Throwable 走異常處理器
$e = new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']);
// 復用異常處理邏輯(這裡簡化直接寫日誌)
$log = ['time' => date('c'), 'type' => 'FatalError', 'message' => $error['message'], 'file' => $error['file'], 'line' => $error['line']];
error_log(json_encode($log, JSON_UNESCAPED_UNICODE) . "\n", 3, '/var/log/php/fatal.log');
}
});
PSR-3 標準實現,Laravel/Symfony 默認集成,支持多通道、處理器鏈、格式化器、指纹去重。
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\LineFormatter;
use Monolog\Processor\IntrospectionProcessor;
$log = new Logger('app');
// 每日輪轉,保留 30 天
$handler = new RotatingFileHandler('/var/log/php/app.log', 30, Logger::DEBUG);
$handler->setFormatter(new LineFormatter(
"[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n",
"Y-m-d H:i:s.u"
));
$handler->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, ['Monolog\\']));
$log->pushHandler($handler);
// 使用
$log->info('用戶登錄', ['uid' => 1001, 'ip' => '1.2.3.4']);
$log->error('支付回調失敗', ['order_id' => '20240101001', 'error' => '簽名驗證失敗']);
// 結構化日誌便於 ELK/Filebeat 採集分析
error.log (Warning+)、exception.log (Throwable)、slow.log (慢請求)、audit.log (關鍵業務操作)time, level, type, message, file, line, trace, request_id, context,便於日誌平台索引uniqid() 或 UUID,透傳到所有日誌、HTTP Header、數據庫事務、消息隊列password, token, secret, card_no, id_card 等字段logrotate 或 RotatingFileHandler,保留 7-30 天,壓縮舊文件BufferHandler + SwiftMailer 模式)、避免阻塞 I/Ostdout/stderr,由 Docker/K8s 採集,不落本地盤Error 與 Exception 的異同,PHP 7+ 統一用 Throwable 捕獲register_shutdown_function 實現全捕獲,轉為結構化 JSON 落盤建立完善的日誌體系,不是為了「有日誌可看」,而是為了在故障發生的第一分鐘,精準定位根因,將 MTTR(平均修復時間)壓縮到分鐘級。