现在开发前后端分离变得越来越流行了,后端只提供接口返回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. 面试阿里,腾讯,字节跳动90%都会被问到的Spring中的循环依赖

    前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...

  3. 下载器Folx扩展程序支持哪些浏览器

    Folx使用多线程的下载方式大大提升了下载的速度,可以完全替代浏览器自带的下载工具,使下载文件的管理更加简单高效.但是,必须给浏览器安装Folx扩展程序,才能使用Folx下载页面链接. Folx在偏好 ...

  4. 对于order by子句

    order by子句指定排序顺序 select username from user order by username; 依据username的字母顺序对于查找出来的username进行排序,默认是 ...

  5. 将input 的文本框改为不可编辑状态

    <input type="text" id = "textid" name="名称" value="值" size ...

  6. git学习与应用

    git是什么 Git是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目:同类的有svn,如果知道svn是干嘛的(上篇),那么git是啥就不多解释了. Git 与常用的版本控制工具 C ...

  7. Java基础教程——垃圾回收机制

    垃圾回收机制 Garbage Collection,GC 垃圾回收是Java的重要功能之一. |--堆内存:垃圾回收机制只回收堆内存中对象,不回收数据库连接.IO等物理资源. |--失去使用价值,即为 ...

  8. 【证书】curl 和 java 请求报证书错误

    1. 说明: 以下:例子的域名因为工作环境的问题,被我拿自己的博客域名替代了,所以无法进行模拟测试,请珍重,哈哈! 2. 环境: centos:7.5 java jdk:1.8.0_74 3. cur ...

  9. vs2019 Com组件初探-通过IDispatch接口调用Com

    vs2019 Com组件初探-简单的COM编写以及实现跨语言调用 上一篇实现了如何编写基于IDipatch接口的COM以及vbs如何调用编写的COM 本次主要是实现VBS的CreateObject函数 ...

  10. LeetCode 029 Divide Two Integers

    题目要求:Divide Two Integers Divide two integers without using multiplication, division and mod operator ...