如何重写Laravel异常处理类详解


Posted in PHP onDecember 20, 2020

现在开发前后端分离变得越来越流行了,后端只提供接口返回json格式的数据,即使是错误信息也要以json格式来返回,然而目前无论是Laravel框架还是ThinkPHP框架,都只提供了返回json数据的方法,对异常的处理并不是以json格式来返回给我们,所以这里就需要我们自己来改写。

首先我们在app/Exceptions目录新建一个ExceptionHandler.php继承自Handler.php

namespace App\Exceptions;


class ExceptionHandler extends Handler
{

}

然后我们在bootstrap/app.php中,使用我们自定义的异常处理类ExceptionHandler替换掉默认的Handler类

//改为我们自定义的ExceptionHandler类
$app->singleton(
 Illuminate\Contracts\Debug\ExceptionHandler::class,
 App\Exceptions\ExceptionHandler::class
);

接下来我们就开始重写渲染方法

在render方法里,我们根据.env文件中的APP_DEBUG来判断,如果是调试模式,我们还是按照默认方式来渲染错误,如果是非调试模式,我们就返回JSON格式的信息

namespace App\Exceptions;

use Exception;

class ExceptionHandler extends Handler
{
 public function render($request, Exception $exception)
 {
 if (env('APP_DEBUG')) {
  return parent::render($request, $exception);
 }
 return response()->json([
  'code' => $exception->getCode(),
  'msg' => $exception->getMessage()
 ]);
 }
}

这样我们就可以根据APP_DEBUG的值设置是否返回JSON格式的数据了,现在我们把.env的APP_DEBUG的值设为false来测试一下,然后我们故意把代码写错,通过postman或浏览器来访问接口

Route::get('/', function () {
 //这是一段缺少了分号的代码,会报异常
 echo 'Hello World!'
});

如何重写Laravel异常处理类详解

在APP_DEBUG=true的情况下还仍然是默认渲染,方便我们查找错误排错

异常类默认会把异常以日志的形式记录在storage/logs目录下,并且以laravel-日期(YYYY-MM-DD)命名的形式,.log为后缀保存错误日志

如何重写Laravel异常处理类详解

我们打开这个日志文件查看记录的错误信息,我们可以发现错误信息记录的非常详细,除了错误说明之外,还记录了调用栈,如下图所示

如何重写Laravel异常处理类详解

基本上红框里的信息就够我们排错了,不需要像现在这样记录的这么详细,所以要想不记录调用栈,我们可以重写report方法

首先我们看一下框架的report方法,代码在(src/Illuminate/Foundation/Exceptions/Handler.php),我用红框框起来的代码就是调用栈信息,我们在重写这个方法时只需要完全拷贝这个方法里的所有代码到我们自定义的report方法里,然后把红框里的代码去掉即可

如何重写Laravel异常处理类详解

我们在我们自定义的异常处理类ExceptionHandler.php中重写report方法

public function report(Exception $exception)
{
 if ($this->shouldntReport($exception)) {
 return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
 return $this->container->call($reportCallable);
 }

 try {
 $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
 throw $exception;
 }

 $logger->error(
 $exception->getMessage()
 );
}

然后我们再重新请求一下接口再去查看错误日志的记录,可以发现确实没有记录调用栈信息了,但是下面的信息还是不够,我们没法根据下面的信息判断错误发生在哪一个文件和哪一行,如果能在记录错误信息的时候同时记录发生错误的文件和行就更好了,所以借着修改report方法

如何重写Laravel异常处理类详解

public function report(Exception $exception)
{
 if ($this->shouldntReport($exception)) {
 return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
 return $this->container->call($reportCallable);
 }

 try {
 $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
 throw $exception;
 }

 $logger->error(
 $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
 );
}

在代码里我通过exception的getFile()、getLine()方法加上了文件和行数,保存代码再次访问接口,查看错误日志文件我们可以看到发生错误的文件和行数已经记录下来了,有了这些信息基本我们就可以找到错误

如何重写Laravel异常处理类详解

截止到这里实现最初的需求我们的ExceptionHandler.php只需要有这些代码

namespace App\Exceptions;


use Exception;
use Illuminate\Support\Reflector;
use Psr\Log\LoggerInterface;

class ExceptionHandler extends Handler
{

 public function render($request, Exception $exception)
 {
 if (env('APP_DEBUG')) {
  return parent::render($request, $exception);
 }
 return response()->json([
  'code' => $exception->getCode(),
  'msg' => $exception->getMessage()
 ]);
 }

 public function report(Exception $exception)
 {
 if ($this->shouldntReport($exception)) {
  return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
  return $this->container->call($reportCallable);
 }

 try {
  $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
  throw $exception;
 }

 $logger->error(
  $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
 );
 }
}

然后还不够,我们发现刚刚我们把服务器端的错误信息以JSON格式返回给客户端了,这是不允许的,我们应该只把一些客户端错误返回给客户端,比如密码不足六位、身份证不合法诸如此类,而服务端出现错误时我们只返回给客户端一个模糊的信息即可,比如“服务器错误”,把真实的服务器错误信息记录在日志里面方便开发人员排查错误

所以我们需要定义一个客户端异常专门用户返回客户端错误,使用如下命令在app/Exceptions目录下生成一个ClientException.php文件

php artisan make:exception ClientException

修改为构造方法为如下代码

namespace App\Exceptions;

use Exception;

class ClientException extends Exception
{
 public function __construct($code, $msg)
 {
 parent::__construct($msg, $code);
 }
}

接着我们继续修改ExceptionHandler.php

namespace App\Exceptions;


use Exception;
use Illuminate\Support\Reflector;
use Psr\Log\LoggerInterface;

class ExceptionHandler extends Handler
{
 /**
 * @var int 错误码
 */
 protected $code;
 /**
 * @var string 错误信息
 */
 protected $message;

 protected $dontReport = [
 ClientException::class
 ];

 public function render($request, Exception $exception)
 {
 if ($exception instanceof ClientException) {
  $this->code = $exception->getCode();
  $this->message = $exception->getMessage();
 } else {
  if (env('APP_DEBUG')) {
  return parent::render($request, $exception);
  }
  
  $this->code = 500;
  $this->message = '服务器错误';
 }
 
 return response()->json([
  'code' => $this->code,
  'msg' => $this->message
 ]);
 }

 public function report(Exception $exception)
 {
 if ($this->shouldntReport($exception)) {
  return;
 }

 if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
  return $this->container->call($reportCallable);
 }

 try {
  $logger = $this->container->make(LoggerInterface::class);
 } catch (Exception $ex) {
  throw $exception;
 }

 $logger->error(
  $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
 );
 }
}

对于上面的修改做一下说明,laravel的$dontReport属性的异常类都不会被上报,因为客户端错误信息我们不需要记录,所以将其添加到$dontReport属性里,并且在render方法里把异常大概分为了两大类,一大类就是客户端异常,另一大类就是服务器异常,我们把服务器异常统一code为500,错误信息为服务器错误,将真实的错误信息记录在了错误日志里,避免把服务器信息暴露给了客户端。

现在我们来测试我们重写异常的结果

假如我们想返回客户端异常,比如没有权限,这类客户端异常在错误日志里都不会产生记录,我们本身也不需要记录

Route::get('/', function () {
 throw new \App\Exceptions\ClientException(403, '你没有权限');
});

如何重写Laravel异常处理类详解

对于服务器端的错误,如少些了分号,客户端就只会知道服务器的某个接口出了问题,但是不清楚具体问题是什么

Route::get('/', function () {
 echo 'Hello World!'
});

如何重写Laravel异常处理类详解

但是真实的错误信息会记录在错误日志里,我们仍旧可以通过错误日志来修改我们服务端的错误

如何重写Laravel异常处理类详解

我们还可以在render方法中加入告警代码,如果是服务端错误就给管理员发送邮件。

至此,我们的重写Laravel异常处理类就算完成啦,希望对正在准备使用Laravel做前后端分离项目的你有所帮助。

到此这篇关于如何重写Laravel异常处理类的文章就介绍到这了,更多相关重写Laravel异常处理类内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
dede全站URL静态化改造[070414更正]
Apr 17 PHP
php auth_http类库进行身份效验
Mar 19 PHP
收藏的PHP常用函数 推荐收藏保存
Feb 21 PHP
php中使用Curl、socket、file_get_contents三种方法POST提交数据
Aug 12 PHP
PHP中计算字符串相似度的函数代码
Dec 29 PHP
php将url地址转化为完整的a标签链接代码(php为url地址添加a标签)
Jan 17 PHP
ThinkPHP框架安全实现分析
Mar 14 PHP
PHPWind9.0手动屏蔽验证码解决后台关闭验证码但是依然显示的问题
Aug 12 PHP
php封装的图片(缩略图)处理类完整实例
Oct 19 PHP
Zend Framework处理Json数据方法详解
Dec 09 PHP
在 Laravel 项目中使用 webpack-encore的方法
Jul 21 PHP
使用swoole 定时器变更超时未支付订单状态的解决方案
Jul 24 PHP
ThinkPHP6.0如何利用自定义验证规则规范的实现登陆
Dec 16 #PHP
6个常见的 PHP 安全性攻击实例和阻止方法
Dec 16 #PHP
TP5多入口设置实例讲解
Dec 15 #PHP
Mac系统下搭建Nginx+php-fpm实例讲解
Dec 15 #PHP
php在linux环境中如何使用redis详解
Dec 15 #PHP
PHP文件操作简单介绍及函数汇总
Dec 11 #PHP
PHP SESSION跨页面传递失败解决方案
Dec 11 #PHP
You might like
Apache服务器无法使用的解决方法
2013/05/08 PHP
PHP IE中下载附件问题解决方法
2014/01/07 PHP
叫你如何修改Nginx与PHP的文件上传大小限制
2014/09/10 PHP
PHP文件上传判断file是否己选择上传文件的方法
2014/11/10 PHP
织梦sitemap地图实时推送给百度的教程
2015/08/03 PHP
Javascript优化技巧(文件瘦身篇)
2008/01/28 Javascript
js tab 选项卡
2009/04/26 Javascript
javascript 写的一个简单的timer
2009/07/30 Javascript
jquery 插件学习(六)
2012/08/06 Javascript
浮动的div自适应居中显示的js代码
2013/12/23 Javascript
javascript匀速动画和缓冲动画详解
2016/10/20 Javascript
JS获取一个表单字段中多条数据并转化为json格式
2017/10/17 Javascript
node.js的Express服务器基本使用教程
2019/01/09 Javascript
vue中格式化时间过滤器代码实例
2019/04/17 Javascript
Vue2(三)实现子菜单展开收缩,带动画效果实现方法
2019/04/28 Javascript
React Hooks 实现和由来以及解决的问题详解
2020/01/17 Javascript
JS创建自定义对象的六种方法总结
2020/12/15 Javascript
[05:09]2016国际邀请赛中国区预选赛淘汰赛首日精彩回顾
2016/06/29 DOTA
[00:44]华丽开场!DOTA2勇士令状带来全新对阵画面
2019/05/15 DOTA
深入分析在Python模块顶层运行的代码引起的一个Bug
2014/07/04 Python
python基于multiprocessing的多进程创建方法
2015/06/04 Python
Python 模块EasyGui详细介绍
2017/02/19 Python
python中csv文件的若干读写方法小结
2018/07/04 Python
pycharm恢复默认设置或者是替换pycharm的解释器实例
2018/10/29 Python
PyQt5 实现字体大小自适应分辨率的方法
2019/06/18 Python
解决Jupyter因卸载重装导致的问题修复
2020/04/10 Python
Python urllib2运行过程原理解析
2020/06/04 Python
Python的Tqdm模块实现进度条配置
2021/02/24 Python
Html5百叶窗效果的示例代码
2017/12/11 HTML / CSS
寻找完美的房车租赁:RVShare
2019/02/23 全球购物
通用求职信范文模板分享
2013/12/27 职场文书
销售团队口号大全
2014/06/06 职场文书
安全标语大全
2014/06/10 职场文书
传承焦裕禄精神思想汇报2014
2014/09/10 职场文书
演讲稿之开卷有益
2019/08/07 职场文书
分析Netty直接内存原理及应用
2021/06/14 Java/Android