Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 542|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-6-9 12:42:49 | 显示全部楼层 |阅读模式

    在日常开发中,大多数人的做法是在开发环境时开启调试模式,在产品环境关闭调试模式。在开发的时候可以查看各种错误、异常,但是在线上就把错误显示的关闭。

    上面的情形看似很科学,有人解释为这样很安全,别人看不到错误,以免泄露重要信息...

    但是你有没有遇到这种情况,线下好好的,一上线却运行不起来也找不到原因...

    一个脚本,跑了好长一段时间,一直没有问题,有一天突然中断了,然后了也没有任何记录都不造啥原因...

    线上一个付款,别人明明付了款,但是我们却没有记录到,自己亲自去实验,却是好的...

     

    种种以上,都是因为大家关闭了错误信息,并且未将错误、异常记录到日志,导致那些随机发生的错误很难追踪。这样矛盾就来了,即不要显示错误,又要追踪错误,这如何实现了?

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

     

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

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-17 15:03 , Processed in 0.068565 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表