如何重写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 相关文章推荐
Adodb的十个实例(清晰版)
Dec 31 PHP
php设计模式 DAO(数据访问对象模式)
Jun 26 PHP
360通用php防护代码(使用操作详解)
Jun 18 PHP
php像数组一样存取和修改字符串字符
Mar 21 PHP
CI框架在CLI下执行占用内存过大问题的解决方法
Jun 17 PHP
PHP实现绘制3D扇形统计图及图片缩放实例
Oct 01 PHP
php判断访问IP的方法
Jun 19 PHP
浅谈php7的重大新特性
Oct 23 PHP
php官方微信接口大全(微信支付、微信红包、微信摇一摇、微信小店)
Dec 21 PHP
laravel 实现登陆后返回登陆前的页面方法
Oct 03 PHP
PHP7生产环境队列Beanstalkd用法详解
May 19 PHP
浅谈Laravel中使用Slack进行异常通知
May 29 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
PHP 批量删除 sql语句
2009/06/05 PHP
一个简单php扩展介绍与开发教程
2010/08/19 PHP
YII Framework教程之异常处理详解
2016/03/14 PHP
基于php数组中的索引数组和关联数组详解
2018/03/12 PHP
Prototype使用指南之form.js
2007/01/10 Javascript
JavaScript Sort 表格排序
2009/10/31 Javascript
javascript 获取元素位置的快速方法 getBoundingClientRect()
2009/11/26 Javascript
jQuery对象和DOM对象的相互转化实现代码
2010/03/02 Javascript
JQuery 操作select标签实现代码
2010/05/14 Javascript
javascript setAttribute, getAttribute 在不同浏览器上的不同表现
2010/08/05 Javascript
基于KMP算法JavaScript的实现方法分析
2013/05/03 Javascript
浅谈Javascript中匀速运动的停止条件
2014/12/19 Javascript
js中取得变量绝对值的方法
2015/01/03 Javascript
JavaScript实现点击按钮直接打印
2016/01/06 Javascript
Bootstrap作品展示站点实战项目2
2016/10/14 Javascript
Vue2.0表单校验组件vee-validate的使用详解
2017/05/02 Javascript
详解VUE-地区选择器(V-Distpicker)组件使用心得
2018/05/07 Javascript
微信小程序如何连接Java后台
2019/08/08 Javascript
小程序实现左滑删除的效果的实例代码
2020/10/19 Javascript
[49:43]VG vs FNATIC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
Python两个内置函数 locals 和globals(学习笔记)
2016/08/28 Python
python网络编程调用recv函数完整接收数据的三种方法
2017/03/31 Python
python rsa实现数据加密和解密、签名加密和验签功能
2019/09/18 Python
Python matplotlib读取excel数据并用for循环画多个子图subplot操作
2020/07/14 Python
scrapy结合selenium解析动态页面的实现
2020/09/28 Python
NARS化妆品官方商店:美国彩妆品牌
2017/08/26 全球购物
MAC Cosmetics官方网站:魅可专业艺术彩妆
2019/04/10 全球购物
餐饮收银员岗位职责
2014/02/07 职场文书
灰雀教学反思
2014/04/28 职场文书
2014年客房部工作总结
2014/11/22 职场文书
茶楼服务员岗位职责
2015/02/09 职场文书
2015年教师自我评价范文
2015/03/04 职场文书
2015年教研组工作总结
2015/05/04 职场文书
Django 实现jwt认证的示例
2021/04/30 Python
python保存大型 .mat 数据文件报错超出 IO 限制的操作
2021/05/10 Python
win11怎么消除图标小盾牌?win11消除图标小盾牌解决方法
2022/08/05 数码科技