如何重写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 相关文章推荐
PHP脚本的10个技巧(2)
Oct 09 PHP
PHP+javascript液晶时钟
Oct 09 PHP
php实现的MySQL通用查询程序
Mar 11 PHP
PHP缓存技术的多种方法小结
Aug 14 PHP
解析yahoo邮件用phpmailer发送的实例
Jun 24 PHP
thinkphp备份数据库的方法分享
Jan 04 PHP
php判断访问IP的方法
Jun 19 PHP
PHP文件上传问题汇总(文件大小检测、大文件上传处理)
Dec 24 PHP
PHP利用超级全局变量$_POST来接收表单数据的实例
Nov 05 PHP
php格式文件打开的四种方法
Feb 24 PHP
php简单检测404页面的方法示例
Aug 23 PHP
php array_map()函数实例用法
Mar 03 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中数组元素升序、降序以及重新排序的函数
2013/06/20 PHP
CI(CodeIgniter)框架中的增删改查操作
2014/06/10 PHP
Destoon模板制作简明教程
2014/06/20 PHP
浅析php创建者模式
2014/11/25 PHP
Windows Server 2008 R2和2012中PHP连接MySQL过慢的解决方法
2016/07/02 PHP
js格式化时间和js格式化时间戳示例
2014/02/10 Javascript
详解JavaScript中常用的函数类型
2015/11/18 Javascript
深入理解vue路由的使用
2017/03/24 Javascript
JS实现动态添加DOM节点和事件的方法示例
2017/04/28 Javascript
微信小程序使用audio组件播放音乐功能示例【附源码下载】
2017/12/08 Javascript
js实现各浏览器全屏代码实例
2018/07/03 Javascript
浅谈ElementUI中switch回调函数change的参数问题
2018/08/24 Javascript
从vue源码解析Vue.set()和this.$set()
2018/08/30 Javascript
jquery实现动态创建form并提交的方法示例
2019/05/27 jQuery
解决Layui 表格自适应高度的问题
2019/11/15 Javascript
微信小程序实现自定义动画弹框/提示框的方法实例
2020/11/06 Javascript
python+matplotlib实现礼盒柱状图实例代码
2018/01/16 Python
python tkinter界面居中显示的方法
2018/10/11 Python
在Python中定义一个常量的方法
2018/11/10 Python
Python判断变量名是否合法的方法示例
2019/01/28 Python
Python爬虫之UserAgent的使用实例
2019/02/21 Python
Python爬取爱奇艺电影信息代码实例
2019/11/26 Python
pycharm 实现光标快速移动到括号外或行尾的操作
2021/02/05 Python
快速一键生成Python爬虫请求头
2021/03/04 Python
澳大利亚吉他在线:Artist Guitars
2017/03/30 全球购物
Foot Locker澳洲官网:美国运动服和鞋类零售商
2019/10/11 全球购物
外贸学院会计专业应届生求职信
2013/11/14 职场文书
表决心的诗句大全
2014/03/11 职场文书
认购协议书范本
2014/04/22 职场文书
初中生期末评语大全
2014/04/24 职场文书
主题党日活动总结
2014/07/08 职场文书
2014年行政部工作总结
2014/11/19 职场文书
2015年医德医风工作总结
2015/04/02 职场文书
讲座开场白台词和结束语
2015/05/29 职场文书
Python中的tkinter库简单案例详解
2022/01/22 Python
如何解决goland,idea全局搜索快捷键失效问题
2022/04/03 Golang