現(xiàn)在開(kāi)發(fā)前后端分離變得越來(lái)越流行了,后端只提供接口返回json格式的數(shù)據(jù),即使是錯(cuò)誤信息也要以json格式來(lái)返回,然而目前無(wú)論是Laravel框架還是ThinkPHP框架,都只提供了返回json數(shù)據(jù)的方法,對(duì)異常的處理并不是以json格式來(lái)返回給我們,所以這里就需要我們自己來(lái)改寫(xiě)。
首先我們?cè)赼pp/Exceptions目錄新建一個(gè)ExceptionHandler.php繼承自Handler.php
namespace App\Exceptions;
class ExceptionHandler extends Handler
{
}
然后我們?cè)赽ootstrap/app.php中,使用我們自定義的異常處理類ExceptionHandler替換掉默認(rèn)的Handler類
//改為我們自定義的ExceptionHandler類
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\ExceptionHandler::class
);
接下來(lái)我們就開(kāi)始重寫(xiě)渲染方法
在render方法里,我們根據(jù).env文件中的APP_DEBUG來(lái)判斷,如果是調(diào)試模式,我們還是按照默認(rèn)方式來(lái)渲染錯(cuò)誤,如果是非調(diào)試模式,我們就返回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()
]);
}
}
這樣我們就可以根據(jù)APP_DEBUG的值設(shè)置是否返回JSON格式的數(shù)據(jù)了,現(xiàn)在我們把.env的APP_DEBUG的值設(shè)為false來(lái)測(cè)試一下,然后我們故意把代碼寫(xiě)錯(cuò),通過(guò)postman或?yàn)g覽器來(lái)訪問(wèn)接口
Route::get('/', function () {
//這是一段缺少了分號(hào)的代碼,會(huì)報(bào)異常
echo 'Hello World!'
});
![](/d/20211017/8e77e0d7c7a2a1b361f432386036c681.gif)
在APP_DEBUG=true的情況下還仍然是默認(rèn)渲染,方便我們查找錯(cuò)誤排錯(cuò)
異常類默認(rèn)會(huì)把異常以日志的形式記錄在storage/logs目錄下,并且以laravel-日期(YYYY-MM-DD)命名的形式,.log為后綴保存錯(cuò)誤日志
![](/d/20211017/9dde83074e3cda14b758eab79f1d6585.gif)
我們打開(kāi)這個(gè)日志文件查看記錄的錯(cuò)誤信息,我們可以發(fā)現(xiàn)錯(cuò)誤信息記錄的非常詳細(xì),除了錯(cuò)誤說(shuō)明之外,還記錄了調(diào)用棧,如下圖所示
![](/d/20211017/7ba0e37261d0606a7c7f3073a1e226d1.gif)
基本上紅框里的信息就夠我們排錯(cuò)了,不需要像現(xiàn)在這樣記錄的這么詳細(xì),所以要想不記錄調(diào)用棧,我們可以重寫(xiě)report方法
首先我們看一下框架的report方法,代碼在(src/Illuminate/Foundation/Exceptions/Handler.php),我用紅框框起來(lái)的代碼就是調(diào)用棧信息,我們?cè)谥貙?xiě)這個(gè)方法時(shí)只需要完全拷貝這個(gè)方法里的所有代碼到我們自定義的report方法里,然后把紅框里的代碼去掉即可
![](/d/20211017/71af61b354d28b27c7f0ed3be829ffb2.gif)
我們?cè)谖覀冏远x的異常處理類ExceptionHandler.php中重寫(xiě)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()
);
}
然后我們?cè)僦匦抡?qǐng)求一下接口再去查看錯(cuò)誤日志的記錄,可以發(fā)現(xiàn)確實(shí)沒(méi)有記錄調(diào)用棧信息了,但是下面的信息還是不夠,我們沒(méi)法根據(jù)下面的信息判斷錯(cuò)誤發(fā)生在哪一個(gè)文件和哪一行,如果能在記錄錯(cuò)誤信息的時(shí)候同時(shí)記錄發(fā)生錯(cuò)誤的文件和行就更好了,所以借著修改report方法
![](/d/20211017/4a1bfda2a06d7f7af82b547088eb7181.gif)
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()
);
}
在代碼里我通過(guò)exception的getFile()、getLine()方法加上了文件和行數(shù),保存代碼再次訪問(wèn)接口,查看錯(cuò)誤日志文件我們可以看到發(fā)生錯(cuò)誤的文件和行數(shù)已經(jīng)記錄下來(lái)了,有了這些信息基本我們就可以找到錯(cuò)誤
![](/d/20211017/2cc326a6d2c0f0333a6480070f0cac5a.gif)
截止到這里實(shí)現(xiàn)最初的需求我們的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()
);
}
}
然后還不夠,我們發(fā)現(xiàn)剛剛我們把服務(wù)器端的錯(cuò)誤信息以JSON格式返回給客戶端了,這是不允許的,我們應(yīng)該只把一些客戶端錯(cuò)誤返回給客戶端,比如密碼不足六位、身份證不合法諸如此類,而服務(wù)端出現(xiàn)錯(cuò)誤時(shí)我們只返回給客戶端一個(gè)模糊的信息即可,比如“服務(wù)器錯(cuò)誤”,把真實(shí)的服務(wù)器錯(cuò)誤信息記錄在日志里面方便開(kāi)發(fā)人員排查錯(cuò)誤
所以我們需要定義一個(gè)客戶端異常專門(mén)用戶返回客戶端錯(cuò)誤,使用如下命令在app/Exceptions目錄下生成一個(gè)ClientException.php文件
php artisan make:exception ClientException
修改為構(gòu)造方法為如下代碼
namespace App\Exceptions;
use Exception;
class ClientException extends Exception
{
public function __construct($code, $msg)
{
parent::__construct($msg, $code);
}
}
接著我們繼續(xù)修改ExceptionHandler.php
namespace App\Exceptions;
use Exception;
use Illuminate\Support\Reflector;
use Psr\Log\LoggerInterface;
class ExceptionHandler extends Handler
{
/**
* @var int 錯(cuò)誤碼
*/
protected $code;
/**
* @var string 錯(cuò)誤信息
*/
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 = '服務(wù)器錯(cuò)誤';
}
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()
);
}
}
對(duì)于上面的修改做一下說(shuō)明,laravel的$dontReport屬性的異常類都不會(huì)被上報(bào),因?yàn)榭蛻舳隋e(cuò)誤信息我們不需要記錄,所以將其添加到$dontReport屬性里,并且在render方法里把異常大概分為了兩大類,一大類就是客戶端異常,另一大類就是服務(wù)器異常,我們把服務(wù)器異常統(tǒng)一code為500,錯(cuò)誤信息為服務(wù)器錯(cuò)誤,將真實(shí)的錯(cuò)誤信息記錄在了錯(cuò)誤日志里,避免把服務(wù)器信息暴露給了客戶端。
現(xiàn)在我們來(lái)測(cè)試我們重寫(xiě)異常的結(jié)果
假如我們想返回客戶端異常,比如沒(méi)有權(quán)限,這類客戶端異常在錯(cuò)誤日志里都不會(huì)產(chǎn)生記錄,我們本身也不需要記錄
Route::get('/', function () {
throw new \App\Exceptions\ClientException(403, '你沒(méi)有權(quán)限');
});
![](/d/20211017/66ea2680c99a98dcdb55f742b876e5c5.gif)
對(duì)于服務(wù)器端的錯(cuò)誤,如少些了分號(hào),客戶端就只會(huì)知道服務(wù)器的某個(gè)接口出了問(wèn)題,但是不清楚具體問(wèn)題是什么
Route::get('/', function () {
echo 'Hello World!'
});
![](/d/20211017/81ece636380ce00a06b1455fc5d3b611.gif)
但是真實(shí)的錯(cuò)誤信息會(huì)記錄在錯(cuò)誤日志里,我們?nèi)耘f可以通過(guò)錯(cuò)誤日志來(lái)修改我們服務(wù)端的錯(cuò)誤
![](/d/20211017/9254753d11019b6a7cceea0e69e9f457.gif)
我們還可以在render方法中加入告警代碼,如果是服務(wù)端錯(cuò)誤就給管理員發(fā)送郵件。
至此,我們的重寫(xiě)Laravel異常處理類就算完成啦,希望對(duì)正在準(zhǔn)備使用Laravel做前后端分離項(xiàng)目的你有所幫助。
到此這篇關(guān)于如何重寫(xiě)Laravel異常處理類的文章就介紹到這了,更多相關(guān)重寫(xiě)Laravel異常處理類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- Laravel如何實(shí)現(xiàn)適合Api的異常處理響應(yīng)格式
- Laravel 修改驗(yàn)證異常的響應(yīng)格式實(shí)例代碼詳解
- laravel框架使用FormRequest進(jìn)行表單驗(yàn)證,驗(yàn)證異常返回JSON操作示例
- Laravel 解決composer相關(guān)操作提示php相關(guān)異常的問(wèn)題
- Laravel 5.5 異常處理 & 錯(cuò)誤日志的解決
- 解決laravel 表單提交-POST 異常的問(wèn)題
- laravel框架 api自定義全局異常處理方法
- Laravel核心解讀之異常處理的實(shí)踐過(guò)程
- 淺談Laravel中使用Slack進(jìn)行異常通知