這篇文章主要對(duì)php中的錯(cuò)誤處理進(jìn)行簡(jiǎn)單的記錄
php一開(kāi)始被設(shè)計(jì)為一門面向過(guò)程的語(yǔ)言,所以異常處理沒(méi)有使用像Java一樣的 try / catch
機(jī)制值依,出錯(cuò)時(shí)直接顯示到頁(yè)面上圃泡,或者記錄到web服務(wù)器的錯(cuò)誤日志中,并且php的錯(cuò)誤分成了很多的級(jí)別愿险,例如E_ERROR颇蜡、E_WARNING、E_PARSE辆亏、E_NOTICE等等风秤,對(duì)于像E_ERROR、E_PARSE這樣的嚴(yán)重錯(cuò)誤扮叨,php會(huì)直接終止腳本的運(yùn)行缤弦。線下開(kāi)發(fā)時(shí),我們一般會(huì)設(shè)置error_reporting(-1)
和ini_set('display_errors', 'on')
來(lái)報(bào)告并顯示所有異常彻磁,但是線上運(yùn)行的項(xiàng)目碍沐,出于安全的考慮,我們一般會(huì)隱藏錯(cuò)誤日志的頁(yè)面輸出衷蜓,如果php的配置文件中開(kāi)啟了log_error
,那么相關(guān)的錯(cuò)誤日志可以在PHP的錯(cuò)誤日志中查詢累提,但是如果我們是多項(xiàng)目呢,通過(guò)PHP錯(cuò)誤日志來(lái)排錯(cuò)磁浇,顯然不科學(xué)斋陪,那么我們?nèi)绾蜗窨蚣苤幸粯觼?lái)優(yōu)雅的捕捉異常呢?相信有一定研發(fā)經(jīng)驗(yàn)的同學(xué)扯夭,一定聽(tīng)說(shuō)過(guò)或者用過(guò)大名鼎鼎的laravel框架鳍贾,那么它是如何處理異常的呢?通過(guò)閱讀源碼交洗,有興趣可以在Illuminate\Foundation\Bootstrap\HandleExceptions.php
查看具體實(shí)現(xiàn)。這里只對(duì)其中的核心部分進(jìn)行分享橡淑,其中主要有三個(gè)PHP內(nèi)置函數(shù)的調(diào)用:set_error_handler构拳、set_exception_handler和register_shutdown_function
set_error_handler函數(shù)
set_error_handler
來(lái)注冊(cè)自己的錯(cuò)誤處理方法來(lái)代替php的標(biāo)準(zhǔn)錯(cuò)誤處理方式(輸出到頁(yè)面或者記錄到日志),但是一些嚴(yán)重錯(cuò)誤是無(wú)法通過(guò)這種方式來(lái)處理的,具體我們來(lái)看手冊(cè)對(duì)該方法的介紹
mixed set_error_handler ( callable error_types = E_ALL | E_STRICT ] )
設(shè)置一個(gè)用戶的函數(shù)(error_handler)來(lái)處理腳本中出現(xiàn)的錯(cuò)誤置森。
本函數(shù)可以用你自己定義的方式來(lái)處理運(yùn)行中的錯(cuò)誤斗埂, 例如,在應(yīng)用程序中嚴(yán)重錯(cuò)誤發(fā)生時(shí)凫海,或者在特定條件下觸發(fā)了一個(gè)錯(cuò)誤(使用 trigger_error())呛凶,你需要對(duì)數(shù)據(jù)/文件做清理回收。
重要的是要記住 error_types 里指定的錯(cuò)誤類型都會(huì)繞過(guò) PHP 標(biāo)準(zhǔn)錯(cuò)誤處理程序行贪, 除非回調(diào)函數(shù)返回了 FALSE漾稀。 error_reporting() 設(shè)置將不會(huì)起到作用而你的錯(cuò)誤處理函數(shù)繼續(xù)會(huì)被調(diào)用 —— 不過(guò)你仍然可以獲取 error_reporting 的當(dāng)前值,并做適當(dāng)處理建瘫。 需要特別注意的是帶 @ error-control operator 前綴的語(yǔ)句發(fā)生錯(cuò)誤時(shí)崭捍,這個(gè)值會(huì)是 0。
同時(shí)注意啰脚,在需要時(shí)你有責(zé)任使用 die()殷蛇。 如果錯(cuò)誤處理程序返回了,腳本將會(huì)繼續(xù)執(zhí)行發(fā)生錯(cuò)誤的后一行橄浓。
以下級(jí)別的錯(cuò)誤不能由用戶定義的函數(shù)來(lái)處理: E_ERROR粒梦、 E_PARSE、 E_CORE_ERROR荸实、 E_CORE_WARNING谍倦、 E_COMPILE_ERROR、 E_COMPILE_WARNING泪勒,和在 調(diào)用 set_error_handler() 函數(shù)所在文件中產(chǎn)生的大多數(shù) E_STRICT昼蛀。
set_exception_handler函數(shù)
set_exception_handler
注冊(cè)異常處理函數(shù),這樣當(dāng)有未被catch的異常產(chǎn)生時(shí)圆存,系統(tǒng)會(huì)為我們自動(dòng)調(diào)用注冊(cè)的處理函數(shù)來(lái)處理叼旋。相關(guān)詳細(xì)描述請(qǐng)查看官網(wǎng)文檔set_exception_handler函數(shù)文檔
register_shutdown_function函數(shù)
register_shutdown_function
注冊(cè)一個(gè)會(huì)在php中止時(shí)執(zhí)行的函數(shù)注冊(cè)一個(gè) callback ,它會(huì)在腳本執(zhí)行完成或者 exit()后被調(diào)用沦辙。關(guān)詳細(xì)描述請(qǐng)查看官網(wǎng)文檔register_shutdown_function函數(shù)文檔
最后直接上干貨
<?php
class myErrorException
{
private $fileRootName;
private $fileName;
public function __construct(string $fileName = '/tmp/php/', bool $bugType = false)
{
$this->fileRootName = $fileName . date('Y-m-d') . '.log';
if (!$bugType) {
$this->checkDir();
$this->errorHandle();
}
}
private function checkDir(){
$this->fileName = $this->fileRootName . date('Y-m-d') . '.log';
if (!file_exists($this->fileName) && !is_dir($this->fileRootName)) {
@mkdir($this->fileRootName, '0777', true);
}
}
/**
* 注冊(cè)全局的錯(cuò)誤處理函數(shù)
*/
private function errorHandle()
{
// 注冊(cè)自己的錯(cuò)誤處理方法來(lái)代替php的標(biāo)準(zhǔn)錯(cuò)誤處理方式(輸出到頁(yè)面或者記錄到日志)夫植,但是一些嚴(yán)重錯(cuò)誤是無(wú)法通過(guò)這種方式來(lái)處理的
set_error_handler([$this, '_handleError']);
// 注冊(cè)異常處理函數(shù),這樣當(dāng)有未被catch的異常產(chǎn)生時(shí)油讯,系統(tǒng)會(huì)為我們自動(dòng)調(diào)用注冊(cè)的處理函數(shù)來(lái)處理详民。
set_exception_handler([$this, '_handleException']);
// 注冊(cè)捕捉致命錯(cuò)誤
register_shutdown_function([$this, '_handleShutdown']);
}
/**
* 錯(cuò)誤處理
* @param int $level
* @param string $message
* @param string $file
* @param int $line
* @param array $context
*/
public function _handleError(int $level, string $message, string $file = '', int $line = 0, array $context = [])
{
if (error_reporting() & $level) {
$this->_logHandle($message, $level, $file, $line);
}
}
/**
* 未捕捉的異常
* @param \Throwable $e
*/
public function _handleException(\Throwable $e)
{
if (!$e instanceof \Error) {
$this->_logHandle($e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
} else {
$this->_logHandle($e->getMessage(), $e->getCode(), $e->getFile(), $e->getLine());
}
}
/**
* 致命錯(cuò)誤
*/
public function _handleShutdown()
{
$error = error_get_last();
if (!is_null($error)) {
$this->_logHandle($error['message'], $error['type'], $error['file'], $error['line']);
}
}
/**
* 日志記錄格式
* @param $message
* @param $level
* @param $file
* @param $line
*/
private function _logHandle($message, $level, $file, $line)
{
$logData = [
'message' => $message,
'level' => $level,
'file' => $file,
'line' => $line
];
$logStr = date('Y-m-d H:i:s') . " error => " . json_encode($logData);
file_put_contents($this->fileRootName, json_encode($logStr));
throw new \Error($message, 500);
}
}
$logObj = new myErrorException();
new testBugA();
die('test');
此時(shí)上面的PHP代碼網(wǎng)頁(yè)中顯示:
然后服務(wù)器日志中顯示:
du -h / --max-depth=2 | sort -nr | head -5