Laravel Exception结合自定义Log服务的使用

第一部分:laravel关于错误和异常的部分源码

第二部分:自定义异常的使用(结合serviceprovider monolog elasticsearch)

过程中涉及到的重要函数请自行查看手册

error_reporting set_error_handler set_exception_handler register_shutdown_function error_get_last

laravel v6.18.40

源码部分

我们来到http kernel文件,处理请求部分

可以看到执行我们业务逻辑的sendRequestThroughRouter方法被try_catch包裹的严严实实

public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride(); $response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e); $response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e)); $response = $this->renderException($request, $e);
} $this->app['events']->dispatch(
new RequestHandled($request, $response)
); return $response;
}

捕获到异常后 框架做了哪些工作呢? reportException 记录了异常 renderException 响应了异常

/**
* Report the exception to the exception handler.
*
* @param \Exception $e
* @return void
*/
protected function reportException(Exception $e)
{
# 从容器中解析ExceptionHandler, 绑定位于bootstrap/app.php中
# 执行的是App\Exceptions\Handler的report方法
$this->app[ExceptionHandler::class]->report($e);
} # 跳转到App\Exceptions\Handler的report方法
public function report(Exception $exception)
{
# 继续跳转到父类的report方法
parent::report($exception);
} # 只看核心代码
Illuminate\Foundation\Exceptions\Handler::report()
/**
* Report or log an exception.
*
* @param \Exception $e
* @return void
*
* @throws \Exception
*/
public function report(Exception $e)
{
if ($this->shouldntReport($e)) {
return;
} # 判断传递进来的异常是否存在report方法,有就执行
if (is_callable($reportCallable = [$e, 'report'])) {
// 值得注意的是,此步骤通过容器调用
// 这意味着我们可以在自定义的异常类中肆无忌惮的向report方法中诸如依赖了!!!
// 后面自定义异常类的使用会提及
return $this->container->call($reportCallable);
} # 从容器中解析log服务
# Illuminate\Log\LogManager实例
try {
$logger = $this->container->make(LoggerInterface::class);
} catch (Exception $ex) {
throw $e;
} # 记录日志 基于monolog 后面自定义日志服务会讲解monolog的使用
$logger->error(
$e->getMessage(),
array_merge(
$this->exceptionContext($e),
$this->context(),
['exception' => $e]
)
);
} # renderException方法请自行查看

通过上面的代码,我们自然而然的认为这样就可以捕获应用中产生的所有异常了,其实不然

下面我们来看框架引导阶段为处理错误和异常做的工作

逻辑同样位于框架的boot阶段

下面给出简要介绍

Illuminate\Foundation\Bootstrap\HandleExceptions::bootstrap()
public function bootstrap(Application $app)
{
self::$reservedMemory = str_repeat('x', 10240); $this->app = $app; // 尽可能显示所有错误, 甚至包括将来 PHP 可能加入的新的错误级别和常量
// -1 和 E_ALL | E_STRICT 的区别为是否向后兼容
error_reporting(-1); // 设置执行逻辑部分出现错误的回调
// 默认错误级别为 E_ALL | E_STRICT
// 网上说此函数只有warning和notice级别的错误能够触发的说法不够准确
// 个人拙见:可以触发回调的错误级别为运行时产生的错误
// 直接中断脚本执行的错误不能触发此回调 因为回调还未注册
// 为了更大范围的抓取错误,需要配合register_shutdown_function 和 error_get_last 处理
set_error_handler([$this, 'handleError']); // 设置捕获执行逻辑部分未捕获的异常回调
set_exception_handler([$this, 'handleException']); // 设置php脚本结束前最后执行的回调
register_shutdown_function([$this, 'handleShutdown']); if (! $app->environment('testing')) {
ini_set('display_errors', 'Off');
}
} # 将php错误转化为异常抛出
/**
* Convert PHP errors to ErrorException instances.
* @throws \ErrorException
*/
public function handleError($level, $message, $file = '', $line = 0, $context = [])
{
if (error_reporting() & $level) {
throw new ErrorException($message, 0, $level, $file, $line);
}
} # 注释已经非常清晰 致命错误异常不能按照普通异常处理 在此处直接记录和返回响应
/**
* Handle an uncaught exception from the application.
*
* Note: Most exceptions can be handled via the try / catch block in
* the HTTP and Console kernels. But, fatal error exceptions must
* be handled differently since they are not normal exceptions.
*
* @param \Throwable $e
* @return void
*/
public function handleException($e)
{
if (! $e instanceof Exception) {
$e = new FatalThrowableError($e);
} try {
self::$reservedMemory = null; $this->getExceptionHandler()->report($e);
} catch (Exception $e) {
//
} if ($this->app->runningInConsole()) {
$this->renderForConsole($e);
} else {
$this->renderHttpResponse($e);
}
} /**
* Handle the PHP shutdown event.
*
* @return void
*/
public function handleShutdown()
{
// 生成的异常类是symfony封装的异常类
// 例:可以在任意路由中来上一句不加分号的代码 看看测试效果
if (! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
$this->handleException($this->fatalExceptionFromError($error, 0));
}
}
使用部分

自定义异常的使用

方式一:直接抛出一个异常 在中判断 进行自定义的处理

1 创建一个自定义异常类
php artisan make:exception CustomException 2 在业务逻辑中抛出异常
Route::get('ex', function () {
throw new CustomException('thrown for caught');
}); 3 扩展App\Exceptions\Handler类
public function report(Exception $exception)
{
if ($exception instanceof CustomException) {
Log::channel('daily')->error($exception->getMessage(), ['type' => 'myEx']);
}
parent::report($exception);
}
# 当然你也可以扩展render方法 4 访问路由
# 查看logs下的文件

以上方法显然不够优雅,当异常变多的时候,需要配合大量的instanceof判断,并且可能会记录两份相同内容的日志

所以还可以使用第二种方式进行自定义异常的使用,利用框架自动调用report和render方法的特性,实现记录和渲染异常响应

方式二:自定义一个exception 然后让框架自动调用report方法 不进行render

1 创建自定义并编辑自定义异常
php artisan make:exception MyException
class MyException extends Exception
{
public function report()
{
Log::channel('daily')->error($this->getMessage(), array_merge($this->exceptionContext(), $this->context()));
} // 其实是不必要的 这两个方法可以在Handler中进行重写,请自行查看Handler的父类,根据需要进行扩展重写
public function exceptionContext()
{
// return [$this->getTraceAsString()];
return [];
} // 其实是不必要的 这两个方法可以在Handler中进行重写
public function context()
{
// return ['type' => 'myEx'];
return ['exception' => $this];
} public function render()
{
return 'whatever you like';
}
} 2 抛出异常
Route::get('myex', function () {
throw new MyException('thrown for caught');
}); 3 执行并查看日志文件 是不是发现和laravel原生的异常记录长得很像呢

方式三:使用自定义的日志服务记录异常

上面提到异常实例的report是通过容器调用的,这意味着我们可以注入我们自定义的日志服务

这里使用神器monolog,因为laravel的日志系统基于monolog,框架已经包含了此库。如果使用其他框架请先确保安装monolog

这里使用elasticsearch作为日志handler之一,所以请确保安装了composer require elasticsearch/elasticsearch

使用monolog作为自定义日志服务实现的原因是因为monolog本身具有替换性和通用性,其他框架稍加改动也可以使用

1 创建契约
<?php namespace App\Contracts; interface ExceptionLog
{
// 记录异常
public function recordEx(\Exception $e);
} 2 创建日志服务 并简单介绍monolog使用
# 关于monolog的更多使用方法请查看官方文档 https://github.com/Seldaek/monolog
<?php namespace App\Services\Logs; use App\Contracts\ExceptionLog;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\ElasticsearchHandler;
use Elasticsearch\ClientBuilder; class MonoException implements ExceptionLog
{
protected $logger; public function __construct()
{
// 创建本地文件存储handlers
$streamHandler = new StreamHandler(storage_path('logs/exception.log'), Logger::DEBUG); // 创建本地日期切分存储handler
$rotateHandler = new RotatingFileHandler(storage_path('logs/exception.log'), Logger::DEBUG); // 创建es 客户端 为了减小难度 就不在这里注入elasticsearch客户端了 其实是我懒 开心撸码最重要
$esClient = ClientBuilder::create()->setHosts(config('es.hosts'))->build();
// es配置
$options = [
'index' => 'my_exception_index',
'type' => 'test_exception',
]; // 创建远程elasticsearch日志存储
$esHandler = new ElasticsearchHandler($esClient, $options); // 这里没有阻止handlers的堆栈冒泡,一条日志会逐个经过es、rotate、stream日志处理器
// 更多的日志存储handler请查看文档(为了性能考量,monolog甚至为你提供了异步方式记录日志) // 创建logger 虽然叫logger但是他并没有记录日志的能力
// 真正提供记录日志能力的是提前创建好的handlers
// monolog提供非常多开箱即用的handler 请查看文档
// 并没有设置processor等等 更多api请查看官方文档
$logger = new Logger('exception', compact('streamHandler', 'rotateHandler', 'esHandler'));
$this->logger = $logger;
} public function recordEx(\Exception $e)
{
$this->logger->error($e->getMessage());
}
} 3 创建服务提供者 因为我们创建的服务在异常中调用 所以使用单例和延迟绑定更加合适
php artisan make:provider LogServiceProvider
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider;
use App\Contracts\ExceptionLog;
use App\Services\Logs\MonoException;
use Illuminate\Contracts\Support\DeferrableProvider; class LogServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->singleton(ExceptionLog::class, function () {
return new MonoException();
});
} /**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
} public function provides()
{
return [ExceptionLog::class];
}
} 4 注册服务 config/app.php
...
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
// 注册自定义日志服务
App\Providers\LogServiceProvider::class, 4 创建自定义异常 并在其中使用自定义的日志服务
<?php namespace App\Exceptions; use Exception;
use App\Contracts\ExceptionLog; class CustomException extends Exception
{
// 注入我们的日志服务
public function report(ExceptionLog $logger)
{
$logger->recordEx($this);
} public function render()
{
return 'whatever you like';
}
} 5 测试异常的使用
Route::get('cex', function () {
throw new CustomException('thrown for caught!!!');
}); 6 检查es和storage/logs下是否存在我们的日志吧
# 我们实现的日志服务可以在应用的任何地方使用,这里只是使用在了异常记录的地方,希望大家能够理解

方式四:有的道友可能吐槽,laravel好好的日志服务不香吗?折腾啥啊?

好的,那就通过laravel自带的日志服务实现和上面同样的功能

config/logging.php
<?php use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Monolog\Handler\ElasticsearchHandler;
use Elasticsearch\ClientBuilder;
use Monolog\Formatter\ElasticsearchFormatter; ...
'stack' => [
'driver' => 'stack',
// 'channels' => ['single'],
'channels' => ['daily', 'es'],
'ignore_exceptions' => false,
], // 自定义es日志处理器
// 一定要设置es的formatter!!!
'es' => [
'driver' => 'monolog',
'level' => 'debug',
'handler' => ElasticsearchHandler::class,
'formatter' => ElasticsearchFormatter::class,
'formatter_with' => [
'index' => 'lara_exception',
'type' => 'lara_exception'
],
'handler_with' => [
'client' => ClientBuilder::create()->setHosts(config('es.hosts'))->build()
]
], # 这样就添加了es作为日志记录的驱动了
# 测试一下吧 比如访问这个路由 查看你的本地文件和es吧
Route::get('cex', function () {
// laravel会将其转换成一个symfony的致命异常
aaa
});

其他方式:想要维持一个高性能的、功能强大的日志服务的话,可以考虑添加一个异步的日志handler,其实monolog也已经提供了开箱即用的handler

更多用法请查看laravel组件的日志部分文档,感兴趣的道友可以自行查看laravel log和monolog的源码

今天没有下集预告,发现错误欢迎指正,感谢!!!

Laravel Exception结合自定义Log服务的使用的更多相关文章

  1. Laravel Exception处理逻辑解析

    Laravel Exception处理逻辑解析 vendor/laravel/framework/src/Illuminate/Foundation/Application.php app首先继承了c ...

  2. 阿里云容器服务--配置自定义路由服务应对DDOS攻击

    阿里云容器服务--配置自定义路由服务应对DDOS攻击 摘要: 容器服务中,除了slb之外,自定义路由服务(基于HAProxy)也可以作为DDOS攻击的一道防线,本文阐述了几种方法来应对普通规模的DDO ...

  3. PHP laravel+thrift+swoole打造微服务框架

    Laravel作为最受欢迎的php web框架一直广受广大互联网公司的喜爱. 笔者也参与过一些由laravel开发的项目.虽然laravel的性能广受诟病但是业界也有一些比较好的解决方案,比如堆机器, ...

  4. Laravel 学习笔记 —— 神奇的服务容器 [转]

    容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存放的不是文本.数值,而是对象.对象的描述(类.接口)或 ...

  5. Swift开发小技巧--自定义Log

    Swift中的自定义Log OC中有宏的定义,可以定义自己的Log,但是Swif中没有宏的定义,想要实现类似OC中的自定义Log,必须实现以下操作 1.在AppDelegate.swift文件中定义一 ...

  6. SharePoint 2013 中自定义WCF服务

    在使用SharePoint2013的时候,如果其他客户端 API 的组合不足,可以通过自定义 Web 服务扩展 SharePoint.默认情况下,SharePoint 2013 不仅支持创建自定义 A ...

  7. 自定义Attribute 服务端校验 客户端校验

    MVC 自定义Attribute 服务端校验 客户端校验/* GitHub stylesheet for MarkdownPad (http://markdownpad.com) *//* Autho ...

  8. 第四十二篇、自定义Log打印

    1.在Xcode 8出来之后,需要我们去关闭多余的日志信息打印 2.在开发的过程中,打印调试日志是一项比不可少的工程,但是在iOS 10中NSLog打印日志被屏蔽了,就不得不使用自定义Log 3.去掉 ...

  9. Swift中自定义Log打印方法

    系统如何调用super方法 系统默认只会在构造函数中,自动调用super.init()方法,而且是在所写方法的尾部进行调用. 在其他函数中,如何需要调用父类的默认实现,都需要手动去实现. 如果在构造函 ...

随机推荐

  1. Dubbo直连方式

    目录 一.dubbo概述 1. 基本架构 2. dubbo 支持的协议 二.直连方法 三.创建服务提供者 1. 思路 1. 创建maven web 2. pom.xml 3. 创建实体 4. 创建服务 ...

  2. 新建一个Vue项目

    node环境以及vue的安装可查看:https://www.cnblogs.com/renlywen/p/13522869.html 第一步:创建项目 终端输入: vue init webpack d ...

  3. Jupyter Notebook 入门指南

    https://www.jianshu.com/p/061c6e5c4b0d cmd输入 :jupyter notebook

  4. 小白式DPDK搭建方法(附相关错误得处理方法)

    搭建环境:VM VM搭建参考其他博客 步骤一 点击虚拟机设置,添加两个网卡(我这里都是使用的都是桥接模式) 步骤二.从网上下载dpdk的压缩包: 命令:wget http://fast.dpdk.or ...

  5. 仓库ERP管理系统(springboot)

    查看更多系统:系统大全,课程设计.毕业设计,请点击这里查看 01 系统概述 基于SpringBoot框架和SaaS模式,非常好用的ERP软件,目前专注进销存+财务功能.主要模块有零售管理.采购管理.销 ...

  6. 总结java创建文件夹的4种方法及其优缺点-JAVA IO基础总结第三篇

    本文是Java IO总结系列篇的第3篇,前篇的访问地址如下: 总结java中创建并写文件的5种方式-JAVA IO基础总结第一篇 总结java从文件中读取数据的6种方法-JAVA IO基础总结第二篇 ...

  7. printf size_t warning

    printf("print discoverList.size()=[%u]\n", discoverList.size()); src/ResultToDB.cpp:2768: ...

  8. Unity动画优化

    Unity动画优化 https://blog.csdn.net/TracyZly/article/details/79991593 Unity中Animator做UI动画的一些细节 https://b ...

  9. CodeForces - 505B-Mr. Kitayuta's Colorful Graph(暴力)

    Mr. Kitayuta has just bought an undirected graph consisting of n vertices and medges. The vertices o ...

  10. github travis-ci持续部署hexo博客

    引言 目前我的博客源码是在coding上的,因为有很方便的持续部署,但是coding目前还不提供push文件的开放API. 因为最近做了一个一键分发平台,将博客分发到简书.CSDN等等的平台,但是我的 ...