攔截PHP各種異常和錯誤,發生致命錯誤時進行報警,萬事防患於未然

来源:http://www.cnblogs.com/painsOnline/archive/2016/01/19/5141346.html
-Advertisement-
Play Games

腳本無故中斷,程式平常正常運行但是某天無故出現錯誤,線下測試一切正常線上總是運行不起來... 你是否遇到過上面揪心的問題,你是否很難找出原因,你是否很難對嚴重問題進行預警... 那麼,所有的答案都在這裡


在日常開發中,大多數人的做法是在開發環境時開啟調試模式,在產品環境關閉調試模式。在開發的時候可以查看各種錯誤、異常,但是線上上就把錯誤顯示的關閉。

上面的情形看似很科學,有人解釋為這樣很安全,別人看不到錯誤,以免泄露重要信息...

但是你有沒有遇到這種情況,線下好好的,一上線卻運行不起來也找不到原因...

一個腳本,跑了好長一段時間,一直沒有問題,有一天突然中斷了,然後了也沒有任何記錄都不造啥原因...

線上一個付款,別人明明付了款,但是我們卻沒有記錄到,自己親自去實驗,卻是好的...

 

種種以上,都是因為大家關閉了錯誤信息,並且未將錯誤、異常記錄到日誌,導致那些隨機發生的錯誤很難追蹤。這樣矛盾就來了,即不要顯示錯誤,又要追蹤錯誤,這如何實現了?

以上問題都可以通過PHP的錯誤、異常機制及其內建函數'set_exception_handler','set_error_handler','register_shutdown_function' 來實現

 

'set_exception_handler' 函數 用於攔截各種未捕獲的異常,然後將這些交給用戶自定義的方式進行處理

'set_error_handler' 函數可以攔截各種錯誤,然後交給用戶自定義的方式進行處理

'register_shutdown_function' 函數是在PHP腳本結束時調用的函數,配合'error_get_last'可以獲取最後的致命性錯誤

 

這個思路大體就是把錯誤、異常、致命性錯誤攔截下來,交給我們自定義的方法進行處理,我們辨別這些錯誤、異常是否致命,如果是則記錄的資料庫或者文件系統,然後使用腳本不停的掃描這些日誌,發現嚴重錯誤立即發送郵件或發送簡訊進行報警

 

首先我們定義錯誤攔截類,該類用於將錯誤、異常攔截下來,用我們自己定義的處理方式進行處理,該類放在文件名為'errorHandler.class.php'中,代碼如下

/**
 * 文件名稱:baseErrorHandler.class.php
 * 摘    要:錯誤攔截器父類
 */
require 'errorHandlerException.class.php';//異常類
class errorHandler
{
    public $argvs = array();

    public     $memoryReserveSize = 262144;//備用記憶體大小

    private $_memoryReserve;//備用記憶體

    /**
     * 方      法:註冊自定義錯誤、異常攔截器
     * 參      數:void
     * 返      回:void
     */
    public function register()
    {
        ini_set('display_errors', 0);

        set_exception_handler(array($this, 'handleException'));//截獲未捕獲的異常

        set_error_handler(array($this, 'handleError'));//截獲各種錯誤 此處切不可掉換位置

        //留下備用記憶體 供後面攔截致命錯誤使用
        $this->memoryReserveSize > 0 && $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);

        register_shutdown_function(array($this, 'handleFatalError'));//截獲致命性錯誤
    }

    /**
     * 方      法:取消自定義錯誤、異常攔截器
     * 參      數:void
     * 返      回:void
     */
    public function unregister()
    {
        restore_error_handler();
        restore_exception_handler();
    }

    /**
     * 方      法:處理截獲的未捕獲的異常
     * 參      數:Exception $exception
     * 返      回:void
     */
    public function handleException($exception)
    {
        $this->unregister();
        try
        {
            $this->logException($exception);
            exit(1);
        }
        catch(Exception $e)
        {
            exit(1);
        }
    }

    /**
     * 方      法:處理截獲的錯誤
     * 參      數:int     $code 錯誤代碼
     * 參      數:string $message 錯誤信息
     * 參      數:string $file 錯誤文件
     * 參      數:int     $line 錯誤的行數
     * 返      回:boolean
     */
    public function handleError($code, $message, $file, $line)
    {
        //該處思想是將錯誤變成異常拋出 統一交給異常處理函數進行處理
        if((error_reporting() & $code) && !in_array($code, array(E_NOTICE, E_WARNING, E_USER_NOTICE, E_USER_WARNING, E_DEPRECATED)))
        {//此處只記錄嚴重的錯誤 對於各種WARNING NOTICE不作處理
            $exception = new errorHandlerException($message, $code, $code, $file, $line);
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
            array_shift($trace);//trace的第一個元素為當前對象 移除
            foreach($trace as $frame) 
            {
                if($frame['function'] == '__toString') 
                {//如果錯誤出現在 __toString 方法中 不拋出任何異常
                    $this->handleException($exception);
                    exit(1);
                }
            }
            throw $exception;
        }
        return false;
    }

    /**
     * 方      法:截獲致命性錯誤
     * 參      數:void
     * 返      回:void
     */
    public function handleFatalError()
    {
        unset($this->_memoryReserve);//釋放記憶體供下麵處理程式使用

        $error = error_get_last();//最後一條錯誤信息
        if(errorHandlerException::isFatalError($error))
        {//如果是致命錯誤進行處理
            $exception = new errorHandlerException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
            $this->logException($exception);
            exit(1);
        }
    }

    /**
     * 方      法:獲取伺服器IP
     * 參      數:void
     * 返      回:string
     */
    final public function getServerIp()
    {
        $serverIp = '';
        if(isset($_SERVER['SERVER_ADDR']))
        {
            $serverIp = $_SERVER['SERVER_ADDR'];
        }
        elseif(isset($_SERVER['LOCAL_ADDR']))
        {
            $serverIp = $_SERVER['LOCAL_ADDR'];
        }
        elseif(isset($_SERVER['HOSTNAME']))
        {
            $serverIp = gethostbyname($_SERVER['HOSTNAME']);
        }
        else
        {
            $serverIp = getenv('SERVER_ADDR');
        }        
        
        return $serverIp; 
    }

    /**
     * 方      法:獲取當前URI信息
     * 參      數:void
     * 返      回:string $url
     */
    public function getCurrentUri()
    {
        $uri = '';
        if($_SERVER ["REMOTE_ADDR"])
        {//瀏覽器瀏覽模式
            $uri = 'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
        }
        else
        {//命令行模式
            $params = $this->argvs;
            $uri = $params[0];
            array_shift($params);
            for($i = 0, $len = count($params); $i < $len; $i++)
            {
                $uri .= ' ' . $params[$i];
            }
        }
        return $uri;
    }

    /**
     * 方      法:記錄異常信息
     * 參      數:errorHandlerException $e 錯誤異常
     * 返      回:boolean 是否保存成功
     */
    final public function logException($e)
    {
        $error = array(
                        'add_time'     =>     time(),
                        'title'     =>     errorHandlerException::getName($e->getCode()),//這裡獲取用戶友好型名稱
                        'message'     =>     array(),
                        'server_ip' =>     $this->getServerIp(),
                        'code'         =>     errorHandlerException::getLocalCode($e->getCode()),//這裡為各種錯誤定義一個編號以便查找
                        'file'         =>  $e->getFile(),
                        'line'         =>     $e->getLine(),
                        'url'        =>  $this->getCurrentUri(),
                    );
        do
        {
            //$e->getFile() . ':' . $e->getLine() . ' ' . $e->getMessage() . '(' . $e->getCode() . ')'
            $message = (string)$e;
            $error['message'][] = $message;
        } while($e = $e->getPrevious());
        $error['message'] = implode("\r\n", $error['message']);
        $this->logError($error);
    }

    /**
     * 方      法:記錄異常信息
     * 參      數:array $error = array(
     *                                    'time' => int, 
     *                                    'title' => 'string', 
     *                                    'message' => 'string', 
     *                                    'code' => int,
     *                                    'server_ip' => 'string'
     *                                     'file'     =>  'string',
     *                                    'line' => int,
     *                                    'url' => 'string',
     *                                );
     * 返      回:boolean 是否保存成功
     */
    public function logError($error)
    {
        /*這裡去實現如何將錯誤信息記錄到日誌*/
    }
}

上述代碼中,有個'errorHandlerException'類,該類放在文件'errorHandlerException.class.php'中,該類用於將錯誤轉換為異常,以便記錄錯誤發生的文件、行號、錯誤代碼、錯誤信息等信息,同時其方法'isFatalError'用於辨別該錯誤是否是致命性錯誤。這裡我們為了方便管理,將錯誤進行編號並命名。該類的代碼如下

/**
 * 文件名稱:errorHandlerException.class.php
 * 摘    要:自定義錯誤異常類 該類繼承至PHP內置的錯誤異常類
 */
class errorHandlerException extends ErrorException
{
    public static $localCode = array(
                                        E_COMPILE_ERROR => 4001,
                                        E_COMPILE_WARNING => 4002,
                                        E_CORE_ERROR => 4003,
                                        E_CORE_WARNING => 4004,
                                        E_DEPRECATED => 4005,
                                        E_ERROR => 4006,
                                        E_NOTICE => 4007,
                                        E_PARSE => 4008,
                                        E_RECOVERABLE_ERROR => 4009,
                                        E_STRICT => 4010,
                                        E_USER_DEPRECATED => 4011,
                                        E_USER_ERROR => 4012,
                                        E_USER_NOTICE => 4013,
                                        E_USER_WARNING => 4014,
                                        E_WARNING => 4015,
                                        4016 => 4016,
                                    );

    public static $localName = array(
                                        E_COMPILE_ERROR => 'PHP Compile Error',
                                        E_COMPILE_WARNING => 'PHP Compile Warning',
                                        E_CORE_ERROR => 'PHP Core Error',
                                        E_CORE_WARNING => 'PHP Core Warning',
                                        E_DEPRECATED => 'PHP Deprecated Warning',
                                        E_ERROR => 'PHP Fatal Error',
                                        E_NOTICE => 'PHP Notice',
                                        E_PARSE => 'PHP Parse Error',
                                        E_RECOVERABLE_ERROR => 'PHP Recoverable Error',
                                        E_STRICT => 'PHP Strict Warning',
                                        E_USER_DEPRECATED => 'PHP User Deprecated Warning',
                                        E_USER_ERROR => 'PHP User Error',
                                        E_USER_NOTICE => 'PHP User Notice',
                                        E_USER_WARNING => 'PHP User Warning',
                                        E_WARNING => 'PHP Warning',
                                        4016 => 'Customer`s Error',
                                    );

    /**
     * 方      法:構造函數
     * 摘      要:相關知識請查看 http://php.net/manual/en/errorexception.construct.php
     *   
     * 參      數:string        $message     異常信息(可選)
     *              int         $code         異常代碼(可選)
     *              int         $severity
     *              string     $filename     異常文件(可選)
     *              int         $line         異常的行數(可選)
     *           Exception  $previous   上一個異常(可選)
     *
     * 返      回:void
     */
    public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $line = __LINE__, Exception $previous = null)
    {
        parent::__construct($message, $code, $severity, $filename, $line, $previous);
    }

    /**
     * 方      法:是否是致命性錯誤
     * 參      數:array $error
     * 返      回:boolean
     */
    public static function isFatalError($error)
    {
        $fatalErrors = array(
                                E_ERROR, 
                                E_PARSE, 
                                E_CORE_ERROR,
                                E_CORE_WARNING, 
                                E_COMPILE_ERROR, 
                                E_COMPILE_WARNING
                            );
        return isset($error['type']) && in_array($error['type'], $fatalErrors);
    }

    /**
     * 方      法:根據原始的錯誤代碼得到本地的錯誤代碼
     * 參      數:int $code
     * 返      回:int $localCode
     */
    public static function getLocalCode($code)
    {
        return isset(self::$localCode[$code]) ? self::$localCode[$code] : self::$localCode[4016];
    }

    /**
     * 方      法:根據原始的錯誤代碼獲取用戶友好型名稱
     * 參      數:int 
     * 返      回:string $name
     */
    public static function getName($code)
    {
        return isset(self::$localName[$code]) ? self::$localName[$code] : self::$localName[4016];
    }

在錯誤攔截類中,需要用戶自己定義實現錯誤記錄的方法('logException'),這個地方需要註意,有些錯誤可能在一段時間內不斷發生,因此只需記錄一次即可,你可以使用錯誤代碼、文件、行號、錯誤詳情 生成一個MD5值用於記錄該錯誤是否已經被記錄,如果在規定時間內(一個小時)已經被記錄過則不需要再進行記錄

 

然後我們定義一個文件,用於實例化以上類,捕獲各種錯誤、異常,該文件命名為'registerErrorHandler.php', 內如如下

/*
* 使用方法介紹:
* 在入口處引入該文件即可,然後可以在該文件中定義調試模式常量'DEBUG_ERROR'
*
* <?php
*   
*    require 'registerErrorHandler.php';
*   
* ?>
*/

/**
* 調試錯誤模式:
* 0                =>            非調試模式,不顯示異常、錯誤信息但記錄異常、錯誤信息
* 1                =>            調試模式,顯示異常、錯誤信息但不記錄異常、錯誤信息
*/
define('DEBUG_ERROR', 0);
require 'errorHandler.class.php';

class registerErrorHandler
{
    /**
     * 方      法:註冊異常、錯誤攔截
     * 參      數:void
     * 返      回:void
     */
    public static function register()
    {
        global $argv;
        if(DEBUG_ERROR)
        {//如果開啟調試模式
            ini_set('display_errors', 1);
            return;
        }

        //如果不開啟調試模式
        ini_set('error_reporting', -1);
        ini_set('display_errors', 0);
        $handler = new errorHandler();
        $handler->argvs = $argv;//此處主要相容命令行模式下獲取參數
        $handler->register();
    }    
}
registerErrorHandler::register();

剩下的就是需要你在你的入口文件引入該文件,定義調試模式,然後實現你自己記錄錯誤的方法即可

需要註意的是,有些錯誤在你進行註冊之前已經發生並且導致腳本中斷是無法記錄下來的,因為此時'registerErrorHandler::register()' 尚未執行已經中斷了

還有就是'set_error_handler'這個函數不能捕獲下麵類型的錯誤 E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNINGE_COMPILE_ERROR、 E_COMPILE_WARNING, 這個可以在官方文檔中看到,但是本處無妨,因為以上錯誤是解析、編譯錯誤,這些都沒有通過,你是不可能發佈上線的

 

以上代碼經過嚴格測試,並且已經應用線上上環境,大家可以根據自己需要進行更改使用

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 話說是這樣的,這兩天開發一個簡訊發送功能,客戶給了一個 Web Service 地址(沒有文檔),讓我調用就可以發送了,我在VS 2013添加了服務引用,一切正常,可是執行代理方法時,怎麼都報錯RPC Message receiveExtMTPushRequest1 in operation rec...
  • 訂單管理是ERP系統中一個重要模塊,客戶下訂單,ERP通過訂單來為客戶進行配送。訂單模塊主要包括訂單創建,訂單修改,訂單審核,訂單取消,訂單分配,訂單列印,訂單揀貨,訂單出庫。在隨後的幾節里我們看看這些每個模塊是怎麼設計運行的。 1.訂單創建 訂單創建主要功能是下單,下單的時候輸入收貨人信息,...
  • Qt 是目前最先進、最完整的跨平臺C++開發工具。它不僅完全實現了一次編寫,所有平臺無差別運行,更提供了幾乎所有開發過程中需要用到的工具。如今,Qt已被運用於超過70個行業、數千家企業,支持數百萬設備及應用。
  • 要想將 TextBlock 里的文本自動換行的話,只需要設置 TextWrapping 屬性為 Wrap 即可。但是 TextWrapping 是儘可能根據空白字元來換行的,因此,就有可能出現下麵這種狀況:每一行的尾部會出現長短不一的空白。在 UI 設計上,有一點建議,那就是同一級的內容是要對齊的。...
  • 使用PHP的array_unique()函數允許你傳遞一個數組,然後移除重覆的值,返回一個擁有唯一值的數組。這個函數大多數情況下都能工作得很好。但是,如果你嘗試在一個大的數組裡使用array_unique()函數,它會運行地慢一些。 有一個比較好而且更快的函數array_flip()來替代使用ar...
  • Ruby對象數組的排序作者剛剛接觸Ruby,因之前總認為腳本語言語法不規範,對腳本語言有些偏見,如不是項目需要並不會去學習PYTHON、RUBY等語言。現在項目中需要實現對象數組排序的任務,對於昨天開始看ruby的我來說壓力山大啊!【汗】但是經過一番查詢資料,終於初步實現了自己想要的結果,現將自己做...
  • 在百度知道上看到一個排列組合的一個提問,一想還是比較容易的主要採用取餘的方法. 這裡用C寫了一段代碼測試了一下.代碼比較水,主要看後面的數學分析. 想想還是高中生 的時候比較厲害,啥都會,啥都算的快. 現在打游戲都力不從心了. 同學們四年游戲專業一定要好好學.不留遺憾.
  • 原文鏈接這篇文章介紹了Python中list是如何實現的。在Python中list特別有用。讓我們來看下list的內部是如何實現的。來看下麵簡單的程式,在list中添加一些整數並將他們列印出來。>>> L = []>>> L.append(1)>>> L.append(2)>>> L.append(...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...