Skip to content

Commit

Permalink
[3.0] 全局错误处理增强 (#651)
Browse files Browse the repository at this point in the history
* Update: 支持设置全局错误&异常处理器

* Update: 更新文档

* Update: 更新文档
  • Loading branch information
NHZEX authored Nov 20, 2023
1 parent 8199a59 commit 3a9aeb7
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 10 deletions.
2 changes: 1 addition & 1 deletion doc/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
* [事件监听](components/event/index.md)
* [事件列表](core/events.md)
* [中间件](core/middleware.md)
* [错误转为异常捕获](core/handleError.md)
* [全局异常处理](core/handleError.md)
* [内部进程间通讯](core/processCommunication.md)
* [Server 对象](core/server.md)
* [长连接分布式解决方案](core/long-connection-distributed.md)
Expand Down
57 changes: 55 additions & 2 deletions doc/core/handleError.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 错误转为异常捕获
# 全局异常处理

[toc]

imi 框架底层支持将错误转为异常,可以通过 `try...catch` 来捕获。
## 异常处理器配置

`config.php` 中的 `beans` 配置

Expand All @@ -16,10 +16,63 @@ imi 框架底层支持将错误转为异常,可以通过 `try...catch` 来捕
'catchLevel' => E_ALL | E_STRICT,
// 抛出异常的错误级别,除此之外全部记录日志,此为默认值
'exceptionLevel' => E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_WARNING | E_CORE_WARNING | E_COMPILE_WARNING | E_USER_WARNING,
/**
* 异常事件处理器数组
* @var array<class-string<Imi\Log\IErrorEventHandler>>
*/
'errorEventHandlers' => [];
],
]
```

## 错误转为异常捕获

imi 框架底层支持将错误转为异常(通过`catchLevel`选项控制),可以通过 `try...catch` 来捕获。

> 错误级别参考:<https://www.php.net/manual/zh/errorfunc.constants.php>
抛出的异常类为 `\ErrorException`

## 全局异常处理器

支持通过`errorEventHandlers`数组声明多个全局异常事件处理器,每个异常处理器必须继承`Imi\Log\AbsErrorEventHandler`,并实现`handleError`,`handleException`方法。
多个异常处理器将按顺序执行,可调用方法`stopPropagation`取消后续异常处理器执行并阻止系统默认的异常处理。

> 请务必确保异常处理器内不要再次抛出异常,做好异常捕获安全处理。
### Demo

```php
# 当 catchLevel 设置为 E_ALL 时,添加以下处理器配合处理错误通知

<?php

declare(strict_types=1);

namespace Imi\App;

use Imi\Log\AbstractErrorEventHandler;
use Imi\Log\Log;
use Psr\Log\LogLevel;

class ErrorEventHandler extends AbstractErrorEventHandler
{
public function handleError(int $errNo, string $errStr, string $errFile, int $errLine): void
{
if (str_contains($errFile, '/phpunit/src/'))
{
// 当前错误与用户代码无关错误且不影响程序正常执行,阻止其抛出异常并打印常规日志
$this->stopPropagation();

Log::log(LogLevel::INFO, $errStr);
}
}

public function handleException(\Throwable $throwable): void
{
// 可以处理更多异常状况...
}
}

```

26 changes: 26 additions & 0 deletions src/Log/AbstractErrorEventHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Imi\Log;

abstract class AbstractErrorEventHandler implements IErrorEventHandler
{
private bool $stopPropagation = false;

/**
* 是否取消系统内部的错误域异常处理并停止后续处理器执行.
*/
public function isPropagationStopped(): bool
{
return $this->stopPropagation;
}

/**
* 取消系统内部的错误域异常处理并停止后续处理器执行.
*/
public function stopPropagation(bool $stop = true): void
{
$this->stopPropagation = $stop;
}
}
44 changes: 37 additions & 7 deletions src/Log/ErrorLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class ErrorLog
*/
protected int $exceptionLevel = \E_ERROR | \E_PARSE | \E_CORE_ERROR | \E_COMPILE_ERROR | \E_USER_ERROR | \E_RECOVERABLE_ERROR | \E_WARNING | \E_CORE_WARNING | \E_COMPILE_WARNING | \E_USER_WARNING;

/**
* 异常事件处理器.
*
* @var array<class-string<IErrorEventHandler>>
*/
protected array $errorEventHandlers = [];

/**
* 注册错误监听.
*/
Expand All @@ -36,26 +43,49 @@ public function register(): void
register_shutdown_function([$this, 'onShutdown']);
// @phpstan-ignore-next-line
set_error_handler($this->onError(...), $this->catchLevel);
set_exception_handler(static fn (\Throwable $th) => Log::error($th));
set_exception_handler($this->onException(...));
}

/**
* 错误.
*/
public function onError(int $errno, string $errstr, string $errfile, int $errline): void
{
foreach ($this->errorEventHandlers as $class)
{
$handler = new $class();
$handler->handleError($errno, $errstr, $errfile, $errline);
if ($handler->isPropagationStopped())
{
return;
}
}
if ($this->exceptionLevel & $errno)
{
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}
$method = match ($errno)
$level = match ($errno)
{
\E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR => 'error',
\E_WARNING, \E_CORE_WARNING, \E_COMPILE_WARNING, \E_USER_WARNING => 'warning',
\E_NOTICE, \E_USER_NOTICE => 'notice',
default => 'info',
\E_ERROR, \E_PARSE, \E_CORE_ERROR, \E_COMPILE_ERROR, \E_USER_ERROR, \E_RECOVERABLE_ERROR => \Psr\Log\LogLevel::ERROR,
\E_WARNING, \E_CORE_WARNING, \E_COMPILE_WARNING, \E_USER_WARNING => \Psr\Log\LogLevel::WARNING,
\E_NOTICE, \E_USER_NOTICE => \Psr\Log\LogLevel::NOTICE,
default => \Psr\Log\LogLevel::INFO,
};
Log::$method($errstr);
Log::log($level, $errstr);
}

public function onException(\Throwable $throwable): void
{
foreach ($this->errorEventHandlers as $class)
{
$handler = new $class();
$handler->handleException($throwable);
if ($handler->isPropagationStopped())
{
return;
}
}
Log::error($throwable);
}

/**
Expand Down
19 changes: 19 additions & 0 deletions src/Log/IErrorEventHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Imi\Log;

interface IErrorEventHandler
{
public function isPropagationStopped(): bool;

/**
* 取消系统内部的错误域异常处理并停止后续处理器执行.
*/
public function stopPropagation(bool $stop = true): void;

public function handleError(int $errNo, string $errStr, string $errFile, int $errLine): void;

public function handleException(\Throwable $throwable): void;
}

0 comments on commit 3a9aeb7

Please sign in to comment.