攔截PHP各種異常和錯誤,發生致命錯誤時進行報警(一)

2021-03-05 PHP老楊

在日常開發中,大多數人的做法是在開發環境時開啟調試模式,在產品環境關閉調試模式。在開發的時候可以查看各種錯誤、異常,但是在線上就把錯誤顯示的關閉。
上面的情形看似很科學,有人解釋為這樣很安全,別人看不到錯誤,以免洩露重要信息...
但是你有沒有遇到這種情況,線下好好的,一上線卻運行不起來也找不到原因...
一個腳本,跑了好長一段時間,一直沒有問題,有一天突然中斷了,然後了也沒有任何記錄都不造啥原因...
線上一個付款,別人明明付了款,但是我們卻沒有記錄到,自己親自去實驗,卻是好的...
種種以上,都是因為大家關閉了錯誤信息,並且未將錯誤、異常記錄到日誌,導致那些隨機發生的錯誤很難追蹤。這樣矛盾就來了,即不要顯示錯誤,又要追蹤錯誤,這如何實現了?
以上問題都可以通過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)
    {
        /*這裡去實現如何將錯誤信息記錄到日誌*/
    }
}

相關焦點

  • php7異常與錯誤處理和自定義異常
    生效時產生該錯誤DivisionByZeroError (分母為零) 運算過程中例如除法,分母為0除了這幾種情況,其餘全部為異常 異常處理在以前的 php5.X 中 並且不能被 try-catch 捕捉得到,到了 php 7.x 中,定義了一個 Throwable
  • 再談PHP錯誤與異常處理
    本文章分5個部分介紹我的異常處理的理解:一、異常與錯誤的概述二、ERROR的級別三、PHP異常處理中的黑科技四、巧妙的捕獲錯誤和異常五、自定義異常處理和異常嵌套六、PHP7中的異常處理一、異常與錯誤的概述  PHP中什麼是異常:  程序在運行中出現不符合預期的情況,允許發生
  • PHP 中的錯誤和異常處理
    就我自己這些年寫程序的現狀看,我基本上就沒有真正明白什麼是異常處理,經常把異常和錯誤處理混為一談,關於代碼中的那些寫法,不是寫錯了,就是寫的太特麼爛了。恰好最近在寫一些類時用到異常處理了,順便就把這個整理下,但是這個僅代表我個人的一些理解和使用,也可能是錯誤的,還請謹慎閱讀。概述錯誤處理定義錯誤是指導致系統不能按照用戶意圖工作的一切原因、事件。
  • 乾貨分享:PHP的錯誤機制總結
    截至到php5.5,一共有16個錯誤級別注意:嘗試下面的代碼的時候請確保打開error_log:E_ERROR這種錯誤是致命錯誤,會在頁面顯示Fatal Error,當出現這種錯誤的時候,程序就無法繼續執行下去了錯誤示例:注意,如果有未被捕獲的異常,也是會觸發這個級別的。
  • (安全篇)PHP 的錯誤機制詳解
    截至到php5.5,一共有16個錯誤級別注意:嘗試下面的代碼的時候請確保打開error_log:error_reporting(E_ALL);ini_set('display_errors', 'On');E_ERROR這種錯誤是致命錯誤,會在頁面顯示Fatal Error, 當出現這種錯誤的時候,程序就無法繼續執行下去了錯誤示例:
  • Python基礎教程(一) - 錯誤和異常
    程式設計師的一生中,錯誤幾乎每天都在發生。在過去的一個時期,錯誤要麼對程序是致命的,要麼產生一堆無意義的輸出。所以,人們需要一個柔和的處理錯誤的方法,而不是終止執行。當然,這一切都是在異常和異常處理出現之前的事了。
  • Python基礎 | 新手學Python時常見的語法錯誤和異常
    然後各種艱難的複查發現可能是循環語句缺少冒號啊、用了中文的標點符號啊、引號/括號等少了一個或者無法匹配啊、函數方法或變量名拼寫錯誤啊等等。在Python編程中有兩種可區分的報錯:語法錯誤 和 異常。語法錯誤又稱解析錯誤,是我們在剛接觸學習Python 時最容易遇到的錯誤,區區別於異常而言,語法錯誤非程序執行時的邏輯錯誤;即使語句或表達式在語法上是正確的,但在嘗試執行時,它仍可能會引發錯誤,而這個在執行時檢測到的邏輯錯誤被稱為異常。
  • Note 013:這次來認識一下「錯誤」和「異常」
    」和「異常」就很關鍵了。SystemExit 解釋器請求退出KeyboardInterrupt 用戶中斷執行(通常是輸入^C)Exception 常規錯誤的基類StopIteration 迭代器沒有更多的值GeneratorExit 生成器(generator)發生異常來通知退出StandardError 所有的內建標準異常的基類ArithmeticError 所有數值計算錯誤的基類
  • php程序訪問經常出現500錯誤原因與解決方法總結
    PHP程序語法錯誤導致場景一:我們項目設置有報警監控(定時每隔10分鐘訪問一下網站的一個固定連結),曾經有段時間,每天都會收到兩三次報500錯誤的郵件,但當自己再手動訪問時卻訪問正常…這應該是最常見的錯誤了,語法錯誤也能很快復現,只要把報錯信息暴露出來即可根據問題立馬解決。
  • Python系列特別篇-錯誤和異常
    即便是語法、縮進都是OK的,也不代表程序就能夠在運行時不會引發錯誤。在執行時檢測到的錯誤被稱為 異常,異常並不致命,但我們仍然需要學會處理它。異常有不同的類型,而其類型名稱將會作為錯誤信息的一部分列印出來:上述示例中的異常類型依次是:ZeroDivisionError,NameError 和TypeError。內置異常列印的字符串是其名稱。錯誤信息的前一部分以堆棧回溯的形式顯示發生異常時的上下文。通常它包含列出原始碼行的堆棧回溯;但是它不會顯示從標準輸入中讀取的行。
  • PHP日誌擴展 SeasLog-1.4.2 發布,支持錯誤與異常
    php內置error_log、syslog函數功能強大且性能極好,但由於各種缺陷(error_log無錯誤級別、無固定格式,syslog不分模塊、與系統日誌混合),靈活度降低了很多,不能滿足應用需求。 好消息是,有不少第三方的log類庫彌補了上述缺陷,如log4php、plog、Analog等(當然也有很多應用在項目中自己開發的log類)。
  • python教程之九錯誤和異常處理
    Python 有兩種錯誤很容易辨認:語法錯誤和異常。語法錯誤語法分析器指出了出錯的一行,並且在最先找到的錯誤的位置標記了顏色,如下所示。 異常Python 程序的語法是正確的,在運行它的時候,也有可能發生錯誤。運行期檢測到的錯誤被稱為異常。
  • nginx File not found 錯誤
    比如我的網站doucument_root下沒有test.php,訪問這個文件時通過抓包可以看到返回的內容。很多人不想用戶直接看到這個默認的404錯誤信息,想自定義404錯誤.給出解決辦法前我們來先分析下如何避免出現這類404錯誤,然後再說真的遇到這種情況(比如用戶輸入一個錯誤不存在的路徑)時該怎麼辦,才能顯示自定義的404錯誤頁。
  • Python-異常與錯誤
    那就讓我們進入本章的學習吧Python 有兩種錯誤很容易辨認:語法錯誤和異常。Python assert(斷言)用於判斷一個表達式,在表達式條件為 false 的時候觸發異常。語法分析器指出了出錯的一行,並且在最先找到的錯誤的位置標記了一個小小的箭頭。異常即便 Python 程序的語法是正確的,在運行它的時候,也有可能發生錯誤。運行期檢測到的錯誤被稱為異常。
  • JavaScript 錯誤和異常處理
    在編程中有三種類型的錯誤:1.句法錯誤 2.運行時錯誤 3. 邏輯錯誤。句法錯誤句法錯誤,也叫解析錯誤,發生在傳統程式語言的編譯時和JavaScript的解釋時。例如,下面會引起一個句法錯誤因為缺少結束的括號。window.print(;運行時錯誤運行時錯誤,也叫異常,會出現在執行過程中(在編譯/解釋完成之後)。
  • php中try catch捕獲異常實例詳解
    具體方法分析如下:php中try catch可以幫助我們捕獲程序代碼的異常了,這樣我們可以很好的處理一些不必要的錯誤了,感興趣的朋友可以一起來看看。PHP中try{}catch{}語句概述PHP5添加了類似於其它語言的異常處理模塊。在 PHP 代碼中所產生的異常可被 throw語句拋出並被 catch 語句捕獲。
  • 第16天:Python 錯誤和異常
    作為 Python 初學者,在剛學習 Python 編程時,經常會看到一些報錯信息,這些報錯信息就是我們接下來要講的錯誤和異常。錯誤提示信息會告訴我們異常發生的上下文,並以調用棧的形式顯示具體信息,提示信息的最後一行開頭會顯示錯誤類型名稱,上例中,錯誤類型為'TypeError',表示類型異常。什麼是異常異常是一個事件,該事件會在程序執行過程中發生,從而影響程序的正常執行。當 Python遇到無法處理的程序時,就會引發一個異常。
  • Python小課堂|錯誤和異常
    作為 Python 初學者,在剛學習 Python 編程時,經常會看到一些報錯信息,在前面我們沒有提及,這章節我們會專門介紹。Python 有兩種錯誤很容易辨認:語法錯誤和異常。Python assert(斷言)用於判斷一個表達式,在表達式條件為 false 的時候觸發異常。
  • Python學習第50課-處理錯誤和異常
    在工作當中會經常出現意料不到的錯誤和異常,就需要我們對可能出現的錯誤和異常進行預判,然後加上捕獲和處理錯誤異常的代碼,否則,程序在運行過程中,遇到錯誤和異常就會crash崩潰,無法繼續向下執行。●Python的錯誤種類:①語法錯誤,或稱解析錯誤。
  • 【筆記】整理了Git遇到錯誤時如何解決的一些坑!
    抖音號:startphp用短視頻和大家分享PHP學習方法,學習技巧與經驗分享,功能實例歡迎關注抖音號:startphp在工作中,你總是與代碼打交道,上傳代碼的時候,相信你不是用svn,就是用Git版本控制器,下面是最近這幾天整理下來經常會遇到的