现在开发前后端分离变得越来越流行了,后端只提供接口返回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!'
});

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

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

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

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

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

我们在我们自定义的异常处理类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方法

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()方法加上了文件和行数,保存代码再次访问接口,查看错误日志文件我们可以看到发生错误的文件和行数已经记录下来了,有了这些信息基本我们就可以找到错误

截止到这里实现最初的需求我们的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, '你没有权限');
});

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

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

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

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

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

重写Laravel异常处理类的更多相关文章

  1. 扩展PHP内置的异常处理类

    在try代码块中,需要使用throw语句抛出一个异常对象,才能跳到转到catch代码块中执行,并在catch代码块中捕获并使用这个异常类的对象.虽然在PHP中提供的内置异常处理类Exception,已 ...

  2. Laravel异常处理

    Laravel异常处理 标签(空格分隔): php 自定义异常类 <?php namespace App\Exceptions; use Throwable; use Exception; cl ...

  3. php 异常处理类

    PHP具有很多异常处理类,其中Exception是所有异常处理的基类. Exception具有几个基本属性与方法,其中包括了: message 异常消息内容code 异常代码file 抛出异常的文件名 ...

  4. 【转载】 C++多继承中重写不同基类中相同原型的虚函数

    本篇随笔为转载,原文地址:C++多继承中重写不同基类中相同原型的虚函数. 在C++多继承体系当中,在派生类中可以重写不同基类中的虚函数.下面就是一个例子: class CBaseA { public: ...

  5. 面向对象编程(九)——面向对象三大特性之继承以及重写、Object类的介绍

    面向对象三大特性 面向对象三大特征:继承 :封装/隐藏 :多态(为了适应需求的多种变化,使代码变得更加通用!) 封装:主要实现了隐藏细节,对用户提供访问接口,无需关心方法的具体实现. 继承:很好的实现 ...

  6. Spring MVC自定义统一异常处理类,并且在控制台中输出错误日志

    在使用SimpleMappingExceptionResolver实现统一异常处理后(参考Spring MVC的异常统一处理方法), 发现出现异常时,log4j无法在控制台输出错误日志.因此需要自定义 ...

  7. or1200处理器的异常处理类指令介绍

    下面内容摘自<步步惊芯--软核处理器内部设计分析>一书 我们在计算机体系结构的学习中知道:中断实质上包含由外部事件引起的硬中断(又称外中断)和由内部预先安排的特定指令或内部异常引起的软中断 ...

  8. springboot统一异常处理类及注解参数为数组的写法

    统一异常处理类 package com.wdcloud.categoryserver.common.exception; import com.wdcloud.categoryserver.commo ...

  9. php异常处理类

    <?php header('content-type:text/html;charset=UTF-8'); // 创建email异常处理类 class emailException extend ...

随机推荐

  1. ServiceStage-华为微服务开发与管理平台

    前言 在上一篇文章一年前,我来到国企搞IT 中,和小伙伴分享了我在国企这一年当中的所见,所闻,所想,很高兴能够获得很多同道中人的共鸣.过去一年,我的很大一部分工作都投入到公司技术平台的建设中.Jira ...

  2. 来看看面试必问的HashMap,一次彻底帮你搞定HashMap源码

    HashMap结构 数组+链表+红黑树 链表大于8转红黑树,红黑树节点数小于6退回链表. 存放的key-value的Node节点 static class Node<K,V> implem ...

  3. MathType如何输入微分上的点

    作为被老师们青睐的公式编辑器,MathType可以帮助插入各种数学符号和编辑数学公式,从而提高数学试卷的编写效率.但是作为新手,在编辑公式的时候难免有困难,比如就有人问:如何输入微分上的点?其实也是有 ...

  4. Vegas媒体生成器是什么,有什么作用

    在专业视频剪辑软件-Vegas的界面中,有一个媒体生成器的界面,此界面包含HitFilm Light Flares,Pro Type Titler,测试图案,纯色,棋盘格,色彩渐变,噪声纹理,致谢字幕 ...

  5. 聊聊 elasticsearch 之分词器配置 (IK+pinyin)

    系统:windows 10 elasticsearch版本:5.6.9 es分词的选择 使用es是考虑服务的性能调优,通过读写分离的方式降低频繁访问数据库的压力,至于分词的选择考虑主要是根据目前比较流 ...

  6. vulnhub: DC 9

    信息收集: root@kali:/opt/test# nmap -A -v 192.168.76.137 Starting Nmap 7.80 ( https://nmap.org ) at 2020 ...

  7. python计算时间差

    前言 之前写代码都是看打印的初始和结束时间然后自己算间隔时间,感觉总是不方便,这不符合python的优雅简洁,于是去寻找简便之道. 方法 time模块计算时间差 import time s_time ...

  8. Centos7安装vscode

    CentOS7 安装vscode                              最近在Linux环境下写几个程序时发现用vim时总出现一点问题,配置了vim也还是不太习惯,因此就安装了vs ...

  9. git原理-本地仓库认识

    项目人员使用git,几乎70%的工作都是在本地仓库完成的.由此可见本地仓库的重要性. 下面我们就通过一些基本的命令讲下git的本地仓库的结构,存储流程,数据类型,如何存储...... 仓库结构 大家都 ...

  10. 【python】Matplotlib作图中有多个Y轴

    在作图过程中,需要绘制多个变量,但是每个变量的数量级不同,在一个坐标轴下作图导致曲线变化很难观察,这时就用到多个坐标轴.本文除了涉及多个坐标轴还包括Axisartist相关作图指令.做图中label为 ...