最近公司项目,前后端分离,为了统一rest分格的API,对异常处理统一规范。

如果是调试模式,走thinkphp框架的原本流程
如果是关闭调试,处理异常统一显示500错误信息,避免暴露敏感信息和友好提示并且统一json格式返回。

整体思路差不多,最近的项目5.0和3.2版本的都有,但是TP5.0和3.2版本还是有点小区别,所以都贴上代码

TP3.2 版本

注意:3.2版本,没有exception_handle这个配置项,所以需要自己配置“自定义的异常处理函数”
设置使用的函数set_exception_handler,不了解的同学自己查看手册。

因为我自己的接口都会继承BaseController,所以直接把设置写在这个类的构造函数中。

class BaseController extends Controller
{
    public function __construct()
    {
        parent::__construct();
        set_exception_handler('Lib\Exception\ExceptionHandler::render');
    }
}
<?php
namespace Lib\Exception;

use Exception;
use think\exception\Handle;
use Think\Log;
use Think\Think;

class ExceptionHandler
{
    private static $code;
    private static  $msg;
    private static  $errorCode;

    public static function render(Exception $e)
    {
        // 如果是自定义异常
        if ($e instanceof BaseApiException){
            self::$code      = $e->code;
            self::$msg       = $e->msg;
            self::$errorCode = $e->errorCode;
        }else{
            // 非调试模式,不要暴露具体错误,只写日志
            if (APP_DEBUG == false){
                self::$code      = 500;
                self::$msg       = '服务器内部错误';
                self::$errorCode = 999;
                self::recordErrorLog($e);
            }else{
                Think::appException($e);
            }
        }

        $result = [
            'msg' => self::$msg,
            'error_code' => self::$errorCode,
            'request_url' => __SELF__
        ];
        // 3.2版本没发现什么返回好用的函数,这里ajaxReturn是自己写的函数,第二个参数是http code
        ajaxReturn($result, self::$code);
    }

    private static function recordErrorLog(Exception $e)
    {
        Log::write($e->getMessage(), 'error', 'File');
    }
}

对比5.0版本的代码,3.2版本记录日志前并没有初始化,因为3.2版本日志记录功能太简单了。

3.2版本指定的日志级别记录,是通过全局配置的C('LOG_LEVEL')

Log.init 方法并没有提供level参数来指定记录的级别。

源代码文件 Think\Log.php

static function record($message,$level=self::ERR,$record=false) {
     if($record || false !== strpos(C('LOG_LEVEL'),$level)) {
          self::$log[] =   "{$level}: {$message}\r\n";
     }
}

TP5.0版本

5.0版本,官方宣传 —— 为API开发而设计的高性能框架,的确这个口号正如他的表现一样。
自定义的异常处理函数,只要在配置文件配置即可

// 异常处理handle类 留空使用 \think\exception\Handle
'exception_handle'       => 'app\lib\exception\ExceptionHandler',
<?php
namespace app\lib\exception;


use Exception;
use think\exception\Handle;
use think\Log;
use think\Request;

class ExceptionHandler extends  Handle
{
    private $code;
    private $msg;
    private $errorCode;

    public function render(Exception $e)
    {
        if ($e instanceof BaseException){
            // 如果是自定义异常
            $this->code = $e->code;
            $this->msg  = $e->msg;
            $this->errorCode = $e->errorCode;
        }else{
            if (config('app_debug')){
                return parent::render($e);
            }else{
                $this->code = 500;
                $this->msg  = '服务器内部错误';
                $this->errorCode = 999;
                $this->recordErrorLog($e);
            }
        }

        $request = Request::instance();

        $result = [
            'msg' => $this->msg,
            'error_code' => $this->errorCode,
            'request_url' => $request->url()
        ];

        // 5.0版本自带的json函数,更方便
        return json($result, $this->code);
    }

    private function recordErrorLog(Exception $e){
        Log::init([
            // 日志记录方式,内置 file socket 支持扩展
            'type'  => 'file',
            // 日志保存目录
            'path'  => LOG_PATH,
            // 日志记录级别, 这里只有error级别以上的才会记录
            'level' => ['error'],
        ]);
        Log::record($e->getMessage(), 'error');
    }
}

相比3.2版本,新版日志记录功能更强大,可以指定错误级别来记录错误日志。

源代码文件 Think\Log.php

<?php
    /**
     * 保存调试信息
     * @access public
     * @return bool
     */
    public static function save()
    {
        ......
        if (empty(self::$config['level'])) {
            // 获取全部日志
            $log = self::$log;
            if (!App::$debug && isset($log['debug'])) {
                unset($log['debug']);
            }
        } else {
            // 记录允许级别
            $log = [];
            foreach (self::$config['level'] as $level) {
                if (isset(self::$log[$level])) {
                    $log[$level] = self::$log[$level];
                }
            }
        }
        ......
    }