thinkphp 源码分析(四)—— 错误和异常处理 以及 log 日志

0x01 前言

本来是这样的,继续是smile 师傅的那篇文章,文章中提到了可以用包含日志, 但是一开始我输入:

http://127.0.0.1/public/index.php/index/index/inde 

这种居然没有产生日志文件,一开始以为thinkphp 会按照日志级别来处理,也就是说我设置了 info 的话,error 级别的日志也会记录。后来发现thinkphp 中config.php 中规定了记录哪些类型的日志才会记录这种类型的日志,不是按照级别高低来的。但是后来就算在config 中添加了error ,也不会产生日志。
但是后来这样:

http://127.0.0.1/public/index.php?s=index/think\app/invokefunction&function=call_user_&vars[0]=system&vars[1][]=whoami

(这里调用了一个不存在的方法call_user_() )
会记录到日志中

所以跟一跟源码来看看造成这种情况的原因。

0x02 日志记录

Log::save() 方法:
在这里插入图片描述
thinkphp 官方文档也说了,在请求结束的时候会自动调用Log::save()
在这里插入图片描述
可以看到152行到154行也确实如此,判断Log::$log 中有没有数据,没有的话,直接return .
然后是看是否Log::driver 为null ,是null 的话,说明还没有初始化Log,这个时候调用Log::init($config=[])来初始化Log ,包括初始化Log::driver ,以及把$config 赋值到 Log::$config 中
在这里插入图片描述
这里可以看到如果App::$debug 模式是开启的话,就会record 一个记录
我们看到Log::record 其实就是,把$msg $type ,写到Log::$log 中
在这里插入图片描述
然后接着Log::save() 函数,可以看到163 行到 175行,其实日志记录并不是按照优先级高的就也会记录优先级低的,而是一个个匹配,config 中写的是啥type就记录啥type . 将要写的日志记录到$log 变量中。
当然如果config 中的type 为[]的话,就会默认读取全部日志。

在这里插入图片描述

然后179行,调用Log::$driver->save($log,true) 方法来真正写入日志: 这里分析 think\log\driver::File 类中的save
在这里插入图片描述
首先49行,获取日志文件名,可以看到88 行会判断是否开启single 文件名,然后95行,会判断max_files ,95行到103 还是挺有趣的,利用了glob协议函数,来得到文件数目。
在这里插入图片描述
接着可以看到75 调用了 Log::write() 来进行文件写入,跟进Log::write() 函数,发现其实是通过error_log() 函数来写入日志的:
在这里插入图片描述
error_log() 函数第三个参数为3 的时候,会把$message 写入到目标文件中。
在这里插入图片描述

0x03 错误和异常处理

先提一个和dispatch 有关的:
对于dispatch() 中类名没有找到的话,
在这里插入图片描述
可以在index/Error 中自定义控制器,来处理(这个Error,来自于 e m p t y , empty , empty,emtpy 是从$config[‘empty_controller’]) 中得到的。

然后正式看看thinkphp 中的异常处理: Error::appException() 所有异常的入口
在这里插入图片描述
先看46行,handler:
在这里插入图片描述
可以看到handle 可以在config.php 中配置,默认配置是空 (可以看到121行说明可以自定义一handle class ,需要继承\think\excption\Handle 类)官方文档中也提到可以自定义一个handle ,继承Hanle 类,然后需要实现render 方法: https://www.kancloud.cn/manual/thinkphp5/126075
在这里插入图片描述
那么就会使用默认的Hanle 类,也就是 \think\exception\Handle 类: [这里的26行可以看到设置了**$ignoreReport** 为 HttpException,这也就是为什么控制器没找到的错误不会记录到日志中]
在这里插入图片描述

接着看上面第二个图中,47行调用$handle->report() 函数:
在这里插入图片描述
42行,调用了Handler->isIgnoreReport() ,跟进
在这里插入图片描述

发现,如果这个$exception 是Hanle::ignoreReport 的类的实例的话,就会返回ture ,也就是不会记录日志。
这也就是为什么 index/inde 不会记录到日志中,而call_user_array_fun_ 会记录到日志中,因为前者是HttpException ,而后者是其他的PHP 内部错误,而且没有错误处理啥的,前面的在Handle 中设置为了IgnoreReport .

再看看怎么抛出异常:
比如App::module() 方法中,模块,控制器,action 没找到都会throw HttpException 对象,这也就是为什么没有记录到日志中的原因
模块不存在的话,也会throw 一个HttpException 对象:

在这里插入图片描述
对于控制器不存在,先抛出ClassNotFoundException ,然后catch 之后,也变成throw 一个 HttpException

在这里插入图片描述
在这里插入图片描述
操作不存在的话,也会throw 一个HtppException 对象
在这里插入图片描述

而对于反射中调用不存在的方法名的时候,会抛出 ReflectionException 错误
在这里插入图片描述

在这里插入图片描述
还有一个问题,为什么发生错误或者异常的时候,会进入think\Error 的appError() 和appException() 函数呢,然后程序结束的时候会调用appShutdown() 函数呢,原来在base.php 中调用了Error::register() 函数来注册错误和异常处理机制;
在这里插入图片描述

跟进 ,可以看到具体是通过调用了四个错误异常有关的函数来进行注册的
在这里插入图片描述
最后说一说php 中的错误和异常:
php 中任何自身的错误和非正常的代码都会被当作错误来处理,而不是以异常抛出。(这点和java 不一样)
https://segmentfault.com/a/1190000009504337
https://segmentfault.com/a/1190000009504337

所以框架中在set_error_handler()中相当于杀了个回马枪,再把错误信息以异常的形式抛出来,这样就可以实现错误以异常的形式抛出。大家要注意:这样做是有缺点的,会受到set_error_handler()函数捕获级别的限制。他能够捕获一部分错误,不能捕获系统级E_ERROR、E_PARSE等错误
在这里插入图片描述
对于set_error_handler() 函数会绕过正常的php error_reporting ,直接走用户定义的函数
在这里插入图片描述
另外对于 set_error_handler() 函数执行之后不会退出程序,但是set_excption_hanler() 函数执行后会退出程序

set_error_handler:
在这里插入图片描述
在这里插入图片描述

可以看到程序会继续进行,输出 1

set_exception_handler:
在这里插入图片描述
在这里插入图片描述
可以看到set_exception_hander 之后,程序并没有输出1 ,而是直接退出了。

此外,在看smile 师傅的wp 中,对于用error_reporting(0) 来 绕过thinkphp 报错不是很理解。
因为set_error_hanlder() 如果没设置第二个参数的话,会忽略error_reporting() 的值,都会走设置好的handler 函数。

而且thinkphp 官方文档中有一段话:
在这里插入图片描述
说可以通过error_reporting() 来设置报错级别,
在这里插入图片描述
仔细看了看源码之后,恍然大悟,其实64 行,会取出当前 error_reporting()的值,与上$errono ,如果不为0 ,就抛出一个exception ,托管给 Exception 去处理。那么加入托管给了Exception 处理,程序就会终止。而如果结果不会0 ,就走68行 report 日志,这样程序会继续进行不会终止的。
对于 error_reporting() 和 $errono 与的这个操作:
error_reporting() 返回的是当前的设置了level ,有如下级别:

在这里插入图片描述
看到其实这些常量 对应的二进制就是:
在这里插入图片描述
比如 error_reporting() 设置为 0 的话,显然不管$errno 是多少,都是0;
再比如error_reporting() 设置的是 ERROR_WARING 的话,对于的二进制就是10,如果$errno 是 2 的话,那么与上的结果还是 2 ,不为0 ,这个时候就会就会抛出异常。

0x04 后记

跟完源码后发现,之所以/index/index/inde 没有被记录到日志中有两个原因:
(1) config.php 添加了log level ,但是没有添加error level 。(所以想要记录错误日志,要么就是添加上error ,要么就是使用默认的 [] 。)
(2) 就算开启了error 的日志,因为/index/index/inde 这种方法名不存在(控制器,模块不存在也一样),在thinkphp 中都处理成了 抛出HttpException 异常,对于控制器不存在本身是会抛出ClassNotFoundException,但是thinkphp 中会 catch 这个异常,然后throw 一个 HttpException。而HttpException 在Error::hanle 类中设置在了 $ignoreReport 中.
在这里插入图片描述

42行可以看到调用 Error::report() 函数中,对于在$ignoreReport 数组中的Excption 不会进行日志记录。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
thinkphp6中,异常处理是通过异常类来实现的。当系统出现异常时,会抛出一个异常对象,我们可以通过捕获这个异常对象来进行异常处理。以下是一个简单的异常处理示例: 1. 在控制器中抛出一个异常: ```php throw new \think\Exception('这是一个异常'); ``` 2. 在应用的异常处理类中进行异常处理: ```php namespace app\exception; use think\exception\Handle; use think\Response; use Throwable; class Http extends Handle { public function render($request, Throwable $e): Response { // 根据不同异常类型进行处理 if ($e instanceof \think\Exception) { // 返回错误信息 return json(['msg' => $e->getMessage()], 500); } // 其他异常交给系统处理 return parent::render($request, $e); } } ``` 注意,上面的代码中,我们继承了think\exception\Handle类,并重写了它的render方法。render方法接受两个参数:$request表示当前请求对象,$e表示抛出的异常对象。在这个方法中,我们可以根据不同的异常类型进行处理,并返回一个Response对象。如果我们不需要对异常进行特殊处理,可以直接调用父类的render方法,让系统进行默认的异常处理。 3. 在应用的配置文件中配置异常处理类: ```php return [ // 异常处理类 'exception_handle' => 'app\exception\Http', ]; ``` 在上面的配置中,我们将异常处理类设置为app\exception\Http。这样,在应用出现异常时,系统就会自动调用这个类的render方法进行异常处理。 除了上面的方法,thinkphp6还提供了其他的异常处理方式,比如使用自定义的异常类、使用异常监听器等。你可以根据自己的需求选择合适的方式进行异常处理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值