您的位置:首页 > 编程语言 > PHP开发

PHP自动捕获Exception,Fatal error等错误和异常

2017-05-16 22:08 543 查看
PHP代码运行时,常常会出现Parse error,Fatal error、Exception导致程序异常终止。对于Parse error,因为它一般出现在脚本的启动阶段,所以相对而言比较容易发现和定位问题。而对于Fatal error、Exception这种可能出现在程序执行过程中的任何阶段的报错,就相对比较麻烦,尤其是对于一些需要持续运行的服务程序,比如数据统计脚本之类,在某个无人职守的夜晚默默crash掉,常常令人很无语。而且不同的环境,PHP的log可能不一定完整,这时定位问题就比较麻烦,从而降低了系统整体的稳定性和可用性。

而如果我们能在程序出现Fatal error、Exception时进行自动捕获,并且将其写到日志中,或者进行报警,就能大大减轻运维工作量从而提升系统可用性。我们今天就针对这个问题做一下探讨。

如果你足够熟悉PHP手册,你会知道PHP有几个系统函数专门提供给用户进行异常捕获,即set_exception_handler()set_error_handler() 。来,我们简单测试一下。

<?php
//errorTest.php

set_exception_handler("exceptionDeal");

function exceptionDeal($exception)
{
echo $exception->getMessage().PHP_EOL;
}

throw new Exception("I 'm a Exception", 1);

执行结果如下图:



我们可以看到程序成功捕获到了Exception,接下来我们再试试set_error_handler() 

<?php

set_error_handler("errorDeal");

function errorDeal($errno, $errstr, $errfile, $errline)
{
$message = <<<EOF
"errno":$errno
"errstr":$errstr
"errfile":$errfile
"errline":$errline
EOF;

echo $message.PHP_EOL;
}

echo $test;//该变量未定义,这时会报一个notice级别的error

执行结果如下图:



这时我们看到notice级别的error也被捕获到了,看起来貌似用这两个函数就能解决问题咯~~来,下面我们试一下捕获Fatal error

<?php
set_error_handler("errorDeal");

function errorDeal($errno, $errstr, $errfile, $errline)
{
$message = <<<EOF
"errno":$errno
"errstr":$errstr
"errfile":$errfile
"errline":$errline
EOF;

echo $message.PHP_EOL;
}

test();//调用不存在的函数,这时会报一个Fatal error

结果:



这时程序居然没有捕获到错误而直接抛出Fatal error后退出。。。WTF  


来,我们先看看这个函数的定义:

mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )


我们发现这个函数对于报错回调的错误类型是有限制的,再看看文档里的说明:以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数
E_STRICT。而且如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册。

这时再仔细研究手册,会发现PHP还提供了两个函数register_shutdown_function()error_get_last()。

其中register_shutdown_function()函数可实现当程序执行完成后执行的函数,其功能为可实现程序执行完成的后续操作。error_get_last()以数组的形式返回最后发生的错误

。该数组包含4个k-v:

[type] - 错误类型

[message] - 错误消息

[file] - 发生错误所在的文件

[line] - 发生错误所在的行

下面我们继续通过demo来看:

<?php

register_shutdown_function("error_handler");

function error_handler()
{
echo "I 'm a Fatal error!";
}

function test(){}
function test(){}// 函数重复定义,这时会报一个Fatal error


结果:



register_shutdown_function()并没有运行成功,其原因是该函数只会在run-time阶段执行,而由于代码中的函数重复定义是在parse-time阶段就会中断的错误,故register_shutdown_function()并没有被调用。

我们稍微修改一下代码:

<?php

function error_handler()
{
echo "I 'm a Fatal error!";
}

if(true)
{
function test(){}
}
function test(){}// 函数重复定义,这时会报一个Fatal error


结果:



我们看到这时register_shutdown_function()被成功调用了,这是因为我们通过加入一个if-else流控制,实现了一个类似于闭包的机制,与外面的test名称不冲突,这时跳过了parse-time阶段进入run-time阶段抛出错误。除此之外,我们还需要注意:

1,register_shutdown_function()函数可重复调用,但执行的顺序与注册的顺序相同

2,如果在调用register_shutdown_function()函数之前有exit()函数调用,register_shutdown_function()函数将不能执行

3,PHP4后支持注册函数参数传递

4,在某些服务端,如Apache,当前目录在register_shutdown_function()函数中能够改变

5,register_shutdown_function()函数执行在headers发送之后

那么有没有什么比较通用的解决方案,可以让所有报错都被捕获呢?

来,我们看下面这个demo:

errorHandler.php

<?php
//errorHandler.php

register_shutdown_function( "fatal_handler" );
set_error_handler("error_handler");

define('E_FATAL',  E_ERROR | E_USER_ERROR |  E_CORE_ERROR |
E_COMPILE_ERROR | E_RECOVERABLE_ERROR| E_PARSE );

//获取fatal error
function fatal_handler()
{
$error = error_get_last();
if($error && ($error["type"]===($error["type"] & E_FATAL))) {
$errno   = $error["type"];
$errfile = $error["file"];
$errline = $error["line"];
$errstr  = $error["message"];
error_handler($errno,$errstr,$errfile,$errline);
}
}

//获取所有的error
function error_handler($errno,$errstr,$errfile,$errline)
{

$str=<<<EOF
"errno":$errno
"errstr":$errstr
"errfile":$errfile
"errline":$errline
EOF;
//获取到错误可以自己处理,比如记Log、报警等等
echo $str;
}


errorTest.php

<?php

//errorTest.php

function test1()
{

}

function test1()
{

}


errorInclude.php

include_once('errorHandler.php');
include_once('errorTest.php');


我们直接执行errorInclude.php
结果:



我们发现虽然依旧有函数重复定义的问题,但是register_shutdown_function()却成功得到了调用,这是因为错误没有发生在运行文件errorInclude.php,而是发生在运行文件include的文件中。故也是成功进入了run-time阶段才报错。

所以呢,我们可以在日常的开发中,结合实际情况把有可能会出error的程序按照上面的方式进行封装,就可以自动截获所有的error啦。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐