如何重写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中的CMS的涵义
Mar 11 PHP
PHP中文件上传的一个问题
Sep 04 PHP
PHP 字符串长度判断效率更高的方法
Mar 02 PHP
Codeigniter的dom类用法实例
Jun 26 PHP
php实现的农历算法实例
Aug 11 PHP
PHP MVC框架路由学习笔记
Mar 02 PHP
ThinkPHP实现生成和校验验证码功能
Apr 28 PHP
iis 7下安装laravel 5.4环境的方法教程
Jun 14 PHP
PHP删除字符串中非字母数字字符方法总结
Jan 20 PHP
PHP实现关键字搜索后描红功能示例
Jul 03 PHP
laravel-admin 管理平台获取当前登陆用户信息的例子
Oct 08 PHP
PHP连接MySQL数据库的三种方式实例分析【mysql、mysqli、pdo】
Nov 04 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
Syphon 秘笈
2021/03/03 冲泡冲煮
php 随机生成10位字符代码
2009/03/26 PHP
PHP实现表单提交数据的验证处理功能【防SQL注入和XSS攻击等】
2017/07/21 PHP
javascript英文日期(有时间)选择器
2007/05/02 Javascript
javascript 清除输入框中的数据
2009/04/13 Javascript
dojo随手记 gird组件引用
2011/02/24 Javascript
jQuery前台数据获取实现代码
2011/03/16 Javascript
js change,propertychange,input事件小议
2011/12/20 Javascript
jquery如何实现在加载完iframe的内容后再进行操作
2013/09/10 Javascript
JS获取图片高度宽度的方法分享
2015/04/17 Javascript
jQuery实现的自动加载页面功能示例
2016/09/04 Javascript
浅谈js使用in和hasOwnProperty获取对象属性的区别
2017/04/27 Javascript
Angular X中使用ngrx的方法详解(附源码)
2017/07/10 Javascript
three.js 入门案例详解
2018/01/23 Javascript
Angular学习笔记之集成三方UI框架、控件的示例
2018/03/23 Javascript
Vue2.0点击切换类名改变样式的方法
2018/08/22 Javascript
Vue 前端实现登陆拦截及axios 拦截器的使用
2019/07/17 Javascript
js对象数组和对象的使用实例详解
2019/08/27 Javascript
JS实现随机抽取三人
2019/11/06 Javascript
Node如何后台数据库使用增删改查功能
2019/11/21 Javascript
[03:02]2014DOTA2西雅图邀请赛 让队员自己告诉你DK NAVI备战情况
2014/07/08 DOTA
Python自定义线程池实现方法分析
2018/02/07 Python
TensorFlow2.0矩阵与向量的加减乘实例
2020/02/07 Python
如何解决pycharm调试报错的问题
2020/08/06 Python
初婚未育证明
2014/01/15 职场文书
总经理岗位职责范本
2014/02/02 职场文书
电子专业毕业生自荐信
2014/05/25 职场文书
工作求职信
2014/07/04 职场文书
基层党员干部四风问题整改方向和措施
2014/09/25 职场文书
财务人员个人工作总结
2015/02/27 职场文书
求职信内容一般写什么?
2015/03/20 职场文书
2015年农村党员干部主题教育活动总结
2015/03/25 职场文书
男人帮观后感
2015/06/18 职场文书
html实现弹窗的实例
2021/06/09 HTML / CSS
Spring Data JPA使用JPQL与原生SQL进行查询的操作
2021/06/15 Java/Android
【海涛解说】史上最给力比赛,挑战DOTA极限
2022/04/01 DOTA