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

PHP 異常處理完整指南:錯誤與異常的統一捕獲、日誌記錄與優雅降級

     編輯:PHP 相關知識

PHP 7+ 將大多數錯誤轉為 Error 異常,配合傳統 Exception,構建了完整的錯誤處理體系。本文系統講解 set_error_handler、set_exception_handler、register_shutdown_function 三大註冊函數的協作機制,實現「所有錯誤/異常/致命錯誤統一捕獲、結構化日誌、用戶友好提示、開發環境詳盡除錯」的生產級方案。

錯誤類型層級對照 (PHP 8.1+)

統一處理器架構

<?php
declare(strict_types=1);

// 1. 配置:區分環境
$isDev = (getenv('APP_ENV') ?? 'prod') === 'dev';
error_reporting($isDev ? E_ALL : E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
ini_set('display_errors', $isDev ? '1' : '0');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/logs/php_errors.log');

// 2. 統一日誌格式化器
function formatLogEntry(string $level, string $message, string $file, int $line, array $context = []): string {
    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
    $caller = $trace[1] ?? [];
    return json_encode([
        'time'     => date('c'),
        'level'    => $level,
        'message'  => $message,
        'file'     => $file,
        'line'     => $line,
        'caller'   => $caller['function'] ?? 'unknown',
        'class'    => $caller['class'] ?? '',
        'context'  => $context,
        'request_id' => $_SERVER['HTTP_X_REQUEST_ID'] ?? uniqid('req_'),
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
}

// 3. 錯誤處理器 (捕獲 Warning, Notice, Deprecated 等)
set_error_handler(function(int $errno, string $errstr, string $errfile, int $errline): bool {
    if (!(error_reporting() & $errno)) return false; // 未在報告範圍內

    $level = match ($errno) {
        E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR => 'ERROR',
        E_WARNING, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING => 'WARNING',
        E_NOTICE, E_USER_NOTICE => 'NOTICE',
        E_DEPRECATED, E_USER_DEPRECATED => 'DEPRECATED',
        default => 'UNKNOWN',
    };

    $log = formatLogEntry($level, $errstr, $errfile, $errline);
    file_put_contents(ini_get('error_log'), $log, FILE_APPEND | LOCK_EX);

    if ($isDev) {
        // 開發環境直接輸出 (避免 headers_sent 問題用 CLI 風格)
        echo "
[$level] $errstr in $errfile:$errline
"; } return true; // 阻止內建處理器 }); // 4. 異常處理器 (捕獲未捕獲的 Exception/Error) set_exception_handler(function(Throwable $e): void { $log = formatLogEntry('EXCEPTION', $e->getMessage(), $e->getFile(), $e->getLine(), [ 'code' => $e->getCode(), 'trace' => $e->getTraceAsString(), 'previous'=> $e->getPrevious()?->getMessage(), ]); file_put_contents(ini_get('error_log'), $log, FILE_APPEND | LOCK_EX); http_response_code(500); if ($isDev) { echo "

Uncaught Exception

"; echo "
" . htmlspecialchars($e->__toString()) . "
"; } else { echo "

服務暫時不可用

我們已記錄錯誤,請稍後重試。

"; } }); // 5. 關閉函數 (捕獲致命錯誤 E_ERROR, E_PARSE 等) register_shutdown_function(function(): void { $error = error_get_last(); if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR], true)) { // 避免重複記錄 (若 error_handler 已處理) $log = formatLogEntry('FATAL', $error['message'], $error['file'], $error['line']); file_put_contents(ini_get('error_log'), $log, FILE_APPEND | LOCK_EX); if (!$isDev) { // 生產環境輸出友好頁面 (注意:此時可能已有輸出) echo "

系統錯誤

請聯繫管理員。

"; } } }); // --- 業務代碼示範 --- class UserService { public function getProfile(int $id): array { if ($id <= 0) throw new InvalidArgumentException('Invalid user ID'); // 模擬資料庫錯誤 if ($id === 999) trigger_error('DB connection failed', E_USER_WARNING); return ['id' => $id, 'name' => 'User ' . $id]; } } try { $svc = new UserService(); echo json_encode($svc->getProfile(1)); // 故意觸發錯誤 $svc->getProfile(-1); } catch (Throwable $e) { // 业务层捕获并处理 http_response_code(400); echo json_encode(['error' => $e->getMessage()]); } ?>

關鍵協作機制圖解

請求進入
    │
    ├─ 非致命錯誤 (Warning, Notice...) ──→ set_error_handler ──→ 記錄日誌 ──→ 返回 true (阻止內建)
    │
    ├─ 拋出異常 (throw) ──→ try-catch 捕獲 ──→ 業務處理
    │       │
    │       └─ 未捕獲 ──→ set_exception_handler ──→ 記錄日誌 ──→ 返回 500 頁面
    │
    └─ 致命錯誤 (E_ERROR, Parse Error...) ──→ 腳本中止
            │
            └─ register_shutdown_function ──→ error_get_last() ──→ 記錄日誌 ──→ 友好頁面

生產環境最佳實踐清單

  1. 結構化日誌:JSON 格式,含 request_id、trace_id (分散式追蹤)、環境標籤,便於 ELK/Splunk/Datadog 接入。
  2. 錯誤碼規範:定義業務錯誤碼 (如 1001: USER_NOT_FOUND),前端據此顯示國際化提示。
  3. 敏感資訊過濾:日誌中脫敏密碼、Token、信用卡號、PII (個人身份信息)。
  4. 異步寫入:高併發下 file_put_contents(LOCK_EX) 會阻塞。改用 Monolog + Redis/Redis Handler 或 Kafka 非同步寫入。
  5. 錯誤追蹤平台:接入 Sentry、Rollbar、Bugsnag,自動聚合、去重、告警、關聯版本發布。
  6. 優雅降級:非核心功能 (推薦、日誌、統計) 失敗不阻塞主流程,用 try-catch 靜默吞掉並記錄。

常用工具庫推薦

  • Monolog:PSR-3 標準日誌庫,支援無數 Handler (文件、Redis、Slack、Sentry、Elasticsearch)。
  • Whoops:開發環境絕美錯誤頁面,支援代碼片段、變數檢查、AJAX/CLI 友好輸出。
  • Symfony ErrorHandler:生產級錯誤渲染、HTTP 狀態碼映射、調試工具欄整合。

Monolog 快速整合範例

composer require monolog/monolog

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SlackWebhookHandler;
use Monolog\Formatter\JsonFormatter;

$log = new Logger('app');
$log->pushHandler(new StreamHandler(__DIR__.'/logs/app.log', Logger::DEBUG));
$log->pushHandler(new SlackWebhookHandler('https://hooks.slack.com/...', Logger::ERROR));

$log->info('User login', ['user_id' => 123, 'ip' => $_SERVER['REMOTE_ADDR']]);
$log->error('Payment failed', ['order_id' => 'ABC', 'exception' => $e]);
?>

建立「統一捕獲、結構化日誌、分級響應、異步寫入、平台接入」的異常處理體系,是 PHP 專案從玩具級邁向企業級的關鍵基礎設施。不要等到線上事故無法定位時才後悔,從專案第一天就接入 Monolog + Sentry,養成「所有錯誤最終都在日誌系統可查」的習慣。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類型基類可捕獲典型場景
E_ERRORError否 (致命)語法錯誤、記憶體耗盡、調用不存在方法
E_WARNING-是 (錯誤處理器)檔案不存在、除零、類型不匹配
E_NOTICE-是未定義變數、陣列鍵不存在
E_DEPRECATED-是棄用功能使用
ExceptionThrowable是 (try-catch)業務邏輯拋出、庫拋出
ErrorThrowable是 (try-catch)TypeError、ArgumentCountError、ParseError