首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 錯誤與異常日誌體系深度解析:從配置到最佳實踐

     編輯:PHP 綜合知識

前言

線上環境「沒有日誌等於沒有發生」。本文系統梳理 PHP 錯誤、異常的區別、內置日誌機制、Nginx/PHP-FPM 多層日誌關係、自定義處理器實現、以及生產環境落地的最佳實踐與工具推薦。

一、錯誤 vs 異常:本質區別

維度Error (錯誤)Exception (異常)
性質腳本運行過程中不可預期的問題(語法、環境、資源)業務邏輯預期之外但可控的情況(驗證失敗、第三方調用異常)
捕獲方式set_error_handler()、register_shutdown_function()try-catch、set_exception_handler()
類型層級E_ERROR, E_WARNING, E_NOTICE, E_DEPRECATED 等所有繼承自 Throwable (PHP 7+) 的類
可恢復性嚴重錯誤導致腳本終止捕獲後可繼續執行

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 內置日誌機制三層架構

  1. PHP-FPM Error Log (php-fpm.conf: error_log)
    記錄進程啟動/停止、核心錯誤、擴展加載失敗、OOM Kill 等系統級信息
  2. PHP-FPM Slow Log (php-fpm.conf: slowlog, request_slowlog_timeout)
    記錄執行超時請求的堆棧追蹤,定位性能瓶頸利器
  3. PHP Error Log (php.ini: error_log, log_errors, error_reporting)
    記錄腳本運行時的 Warning、Notice、Deprecated、自定義 error_log() 等應用級日誌

關鍵配置 (php.ini)

; 生產環境推薦
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 配置片段

; 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 ; 請求硬性超時

三、Nginx 雙日誌與關聯分析

  • Access Log:記錄所有請求(IP、Method、URL、Status、UA、耗時 $request_time/$upstream_response_time)
  • Error Log:Nginx 自身錯誤、上游超時/502/503、權限拒絕、SSL 握手失敗

排查鏈路:
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');
    }
});

五、Monolog:企業級日誌庫標準實踐

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 採集分析

六、生產環境最佳實踐清單

  1. 分級存儲:error.log (Warning+)、exception.log (Throwable)、slow.log (慢請求)、audit.log (關鍵業務操作)
  2. 結構化 JSON:統一字段 time, level, type, message, file, line, trace, request_id, context,便於日誌平台索引
  3. Request ID 鏈路追蹤:入口生成 uniqid() 或 UUID,透傳到所有日誌、HTTP Header、數據庫事務、消息隊列
  4. 敏感數據脫敏:Processor 自動過濾 password, token, secret, card_no, id_card 等字段
  5. 日誌輪轉與清理:logrotate 或 RotatingFileHandler,保留 7-30 天,壓縮舊文件
  6. 告警接入:Error/Fatal 級別推送到 Sentry、企業微信/钉钉機器人、Prometheus Alertmanager
  7. 性能影響最小化:異步寫入(Monolog BufferHandler + SwiftMailer 模式)、避免阻塞 I/O
  8. 容器化環境:標準輸出 stdout/stderr,由 Docker/K8s 採集,不落本地盤

七、總結

  • 理解 Error 與 Exception 的異同,PHP 7+ 統一用 Throwable 捕獲
  • 三層日誌(PHP-FPM Error/Slow、PHP Error)各司其職,Nginx Access/Error 關聯排查
  • 自定義處理器 + register_shutdown_function 實現全捕獲,轉為結構化 JSON 落盤
  • 引入 Monolog 遵循 PSR-3,享受成熟生態(輪轉、格式化、處理器鏈、多通道)
  • 生產環境落地:分級、結構化、Request ID、脫敏、輪轉、告警、異步、容器友好

建立完善的日誌體系,不是為了「有日誌可看」,而是為了在故障發生的第一分鐘,精準定位根因,將 MTTR(平均修復時間)壓縮到分鐘級。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved