异常报警方案与实施_2

157 阅读1分钟

异常实战

目的如下:

  • 1、将系统抛出的非严重错误屏蔽,并做warning警报处理。而且不做中断。

  • 2、将系统抛出的严重错误屏蔽,并提示内部错误和做error警报处理,程序中断。

  • 3、将手动抛出的异常提示用户,中断程序。

ThinkPHP5.1.19 错误处理分析

1、public/index.php // 入口

2、require DIR . '/../thinkphp/base.php' 19line

// 注册错误和异常处理机制
Error::register();//19行

3、Error::register()的内容,处理异常都是中断程序,然后写日志。thinkphp/library/think/Error.php 32line

    error_reporting(E_ALL);
    set_error_handler([__CLASS__, 'appError']);
    set_exception_handler([__CLASS__, 'appException']);
    register_shutdown_function([__CLASS__, 'appShutdown']);

    /**
     * Exception Handler
     * @access public
     * @param  \Exception|\Throwable $e
     */
    public static function appException($e) 
    {
        if (!$e instanceof \Exception) {
            $e = new ThrowableError($e);
        }

        self::getExceptionHandler()->report($e); // 这一调用了self::getExceptionHandler(),//**这里提供给用户设置处理异常的途径。**

        if (PHP_SAPI == 'cli') {
            self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
        } else {
            self::getExceptionHandler()->render($e)->send();
        }
    }

4、public/index.php 将注册在容器的thinkphp/library/think/App.php执行run

$app = Container::get('app')->run()->send();

5、thinkphp/library/think/App.php 375line的run()

    // 初始化应用
    $this->initialize(); //379

6、$this->initialize(); thinkphp/library/think/App.php 232line 设置异常处理类。

    // 注册异常处理类
    if ($this->config('app.exception_handle')) {
        Error::setExceptionHandler($this->config('app.exception_handle'));//设置用户的处理异常类
    }

1、目的一,将系统抛出的非严重错误屏蔽,并做warning警报处理。而且不做中断。

原先的异常处理是会中断的,不中断异常那么就要替换原先的错误处理,重写set-error-handler,要生效是放最后写,替换之前写的。而且要放在响应给客户端之前。

根据上面分析,要放在thinkphp/library/think/App.php的$this->initialize();后。

    public function run()
    {
        try {
            // 初始化应用
            $this->initialize();

            // 监听app_init
            $this->hook->listen('app_init'); // ****这里放在这里****

            if ($this->bindModule) {
                // 模块/控制器绑定
                $this->route->bind($this->bindModule);
            } elseif ($this->config('app.auto_bind_module')) {
                // 入口自动绑定
                $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME);
                if ($name && 'index' != $name && is_dir($this->appPath . $name)) {
                    $this->route->bind($name);
                }
            }

            // 监听app_dispatch
            $this->hook->listen('app_dispatch');

            $dispatch = $this->dispatch;

            if (empty($dispatch)) {
                // 路由检测
                $dispatch = $this->routeCheck()->init();
            }

            // 记录当前调度信息
            $this->request->dispatch($dispatch);

            // 记录路由和请求信息
            if ($this->appDebug) {
                $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true));
                $this->log('[ HEADER ] ' . var_export($this->request->header(), true));
                $this->log('[ PARAM ] ' . var_export($this->request->param(), true));
            }

            // 监听app_begin
            $this->hook->listen('app_begin');

            // 请求缓存检查
            $this->checkRequestCache(
                $this->config('request_cache'),
                $this->config('request_cache_expire'),
                $this->config('request_cache_except')
            );

            $data = null;
        } catch (HttpResponseException $exception) {
            $dispatch = null;
            $data     = $exception->getResponse();
        }

        $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) {
            return is_null($data) ? $dispatch->run() : $data;
        });

        $response = $this->middleware->dispatch($this->request);

        // 监听app_end
        $this->hook->listen('app_end', $response);

        return $response;
    }

这里提供能很多可以自定义的地方,我放在$this->hook->listen('app_init'); 这里,也就是 application/tags.php的app_init

    // 应用初始化
    'app_init' => [
        'app\common\init\ExceptionInit'
    ],

application\common\init\ExceptionInit.php的内容。

<?php

namespace app\common\init;

use think\Request;

class ExceptionInit
{
    /**
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function run()
    {
        if (!\think\facade\Env::get('APP_DEBUG')) {
            // set to the user defined error handler
            set_error_handler(error_handler(function ($log){
                trace($log, 'warning');
            }));//error_handler方法是自定义的处理方法,通过回调的方式处理异常结果,这里记录日志为warning类型。
        }
    }
}

error_handler方法内容

if (! function_exists('error_handler')) {
    function error_handler(\Closure $logCallback) {
        return function ($errno, $errstr, $errfile, $errline) use($logCallback){
            if (!(error_reporting() & $errno)) {
                // This error code is not included in error_reporting, so let it fall
                // through to the standard PHP error handler
                return false;
            }

            // $errstr may need to be escaped:
            $errstr = htmlspecialchars($errstr);

            switch ($errno) {
                case E_DEPRECATED:
                    $errLevel = "E_DEPRECATED:";
                    break;
                case E_NOTICE:
                    $errLevel = "E_NOTICE:";
                    break;
                case E_WARNING:
                    $errLevel = "E_WARNING:";
                    break;
                default:
                    $errLevel = "Unknown error type:";
                    break;
            }
            if(is_callable($logCallback)){
                $logCallback($errLevel . '[' . $errno . ']' . "{$errstr} {$errfile} {$errline}");
            }
            /* Don't execute PHP internal error handler */
            return true;
        };
    }
}

2、目标二、三。将系统抛出的严重错误屏蔽,并提示内部错误和做error警报处理,程序中断。将手动抛出的异常提示用户,中断程序。

严重错误和自定义抛出会走set_exception_handler。前面因为要不中断所以重写了set_error_handler,然后中断的继续用系统的处理方式,系统提供了异常处理的方式,config/app.php 的138line。

    // 异常处理handle类 留空使用 \think\exception\Handle  交给模块下的config
    'exception_handle'       => 'app\common\exception\ExceptionHandler'

application\common\exception\ExceptionHandler.php文件

<?php

namespace app\common\exception;

use app\common\service\DingdingService;
use OneHour\Core\Controller\AjaxResponseTrait;
use think\exception\Handle;
use think\exception\ThrowableError;

class ExceptionHandler extends Handle
{
    use AjaxResponseTrait;

    public function report(\Exception $exception)
    {
        if (!env('APP_DEBUG')) {
            //严重错误处理
            if ($exception instanceof ThrowableError) {
                $msg = $exception->getMessage() .
                    $exception->getFile() .
                    $exception->getLine() . ' ' .
                    $exception->getCode();
                DingdingService::getInstance()->errorSend($msg);//钉钉异常通知
                trace($msg, 'error');//日志记录
                $this->failResponse('服务器内部错误,请稍后再试')->send();//统一内部错误
            }
            //常规错误
            $this->failResponse($exception->getMessage())->send();
        }
    }
}