拦截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)
    {
        /*这里去实现如何将错误信息记录到日志*/
    }
}

上述代码中,有个‘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_WARNING 、 E_COMPILE_ERROR 、 E_COMPILE_WARNING, 这个可以在官方文档中看到,但是本处无妨,因为以上错误是解析、编译错误,这些都没有通过,你是不可能发布上线的

以上代码经过严格测试,并且已经应用在线上环境,大家可以根据自己需要进行更改使用

时间: 2024-07-31 21:07:52

拦截PHP各种异常和错误,发生致命错误时进行报警,万事防患于未然的相关文章

SQL2008、SQL2013 执行Transact-SQL 语句或者批处理时发生了异常。错误5120

附加数据库的时候遇到问题,问题描述如下: 附加数据库 对于 服务器"服务器名"失败.(Microsoft.SqlServer.Smo) 执行Transact-SQL 语句或者批处理时发生了异常.错误5120 原因权限不够.以管理员身份运行dbms.或者找到数据文件,右键--属性---安全--给用户组添加点权限.

Retrofit+RxJava 优雅的处理服务器返回异常、错误

开始本博客之前,请先阅读: Retrofit请求数据对错误以及网络异常的处理 异常&错误 实际开发经常有这种情况,比如登录请求,接口返回的 信息包括请求返回的状态:失败还是成功,错误码,User对象等等.如果网络等原因引起的登录失败可以归结为异常,如果是用户信息输入错误导致的登录失败算是错误. 假如服务器返回的是统一数据格式: /** * 标准数据格式 * @param <T> */ public class Response<T> { public int state;

VC 调试技术与异常(错误)处理 VC 调试技术与异常(错误)处理

调试技术与异常(错误)处理 (1)   转载自 52PK游戏论坛 跟踪与中间过程输出 也许一个开发人员一半以上的时间都是在面对错误,所以好的调试/查错方法(工具)会减轻我们工作的负担,也可以让枯燥的DEBUG过程得以缩短. VC开 发环境所提供的调试环境是很优秀的,我们可以运用单步运行,设置断点的方法来查找问题所在.但是这种跟踪是非常耗时的,所以我们需要采用一些策略来让我们 更容易的发现错误并对错误进行定位,所幸的是VC在这方面提供了强大的支持.在本节中我们先看看如何利用设置断点和利用TRACE

Android Retrofit+RxJava 优雅的处理服务器返回异常、错误

开始本博客之前,请先阅读: Retrofit请求数据对错误以及网络异常的处理 异常&错误 实际开发经常有这种情况,比如登录请求,接口返回的 信息包括请求返回的状态:失败还是成功,错误码,User对象等等.如果网络等原因引起的登录失败可以归结为异常,如果是用户信息输入错误导致的登录失败算是错误. 假如服务器返回的是统一数据格式: /** * 标准数据格式 * @param <T> */ public class Response<T> { public int state;

14.PowerShell--抛出异常,错误处理

PowerShell – 错误处理 1.  What-if 参数 (试运行,模拟操作) 简介:PowerShell 不会执行任何对系统有影响的操作,只会告诉你如果没有模拟运行,可能产生什么影响和后果. 实例: PS C:\>Stop-Process  -name calc -whatif What if: Performing theoperation "Stop-Process" on target "calc (119000)". 2.  -confirm

delphi高手突破之异常及错误处理

什么是异常?为什么要用它? 所谓“异常”是指一个异常类的对象.Delphi的VCL中,所有异常类都派生于Exception类.该类声明了异常的一般行为.性质.最重要的是,它有一个Message属性可以报告异常发生的原因. 但需要强调的是,异常用来标志错误发生,却并不因为错误发生而产生异常.产生异常仅仅是因为遇到了raise,在任何时候,即使没有错误发生,raise都将会导致异常的发生.异常的发生,仅仅是因为raise,而非其他!采用抛出异常以处理意外情况,则可以保证程序主流程中的所有代码可用,而

异常、错误

一.异常.错误的概念 异常是不正常的事件,不是错误 eg: 10/0,文件不存在等 错误是很难处理的,比如内存溢出等,不能够通过异常处理机制来解决. 异常是程序中发生的不正常事件流,通过处理程序依然可以运行下去.但是错误是无法控制的,程序肯定要中断. 二.异常的种类 异常分为运行期异常和编译期异常两种 运行期异常:程序运行时抛除的异常,所有RuntimeException的子类都是运行期异常 数学异常 空指针异常 数组下标越界   ......... 编译期异常(Checked Exceptio

PHP异常与错误处理机制

先区别一下php中错误 与 异常的概念吧 PHP错误:是属于php程序自身的问题,一般是由非法的语法,环境问题导致的,使得编译器无法通过检查,甚至无法运行的情况.平时遇到的warming.notice都是错误,只是级别不同而已. PHP异常:一般是业务逻辑上出现的不合预期.与正常流程不同的状况,不是语法错误. PHP异常处理机制借鉴了java  c++等,但是PHP的异常处理机制是不健全的.异常处理机制目的是将 程序正常执行的代码  与 出现异常如何处理的代码分离. PHP是无法自动捕获异常的(

Axis 调用.net WebServic接口出现:验证消息的安全性时错误发生

解决方法:call.setSOAPVersion(org.apache.axis.soap.SOAPConstants.SOAP12_CONSTANTS); 參考:http://www.blogjava.net/andy199999/ 异常内容: AxisFault faultCode: {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}InvalidSecurity faultS