From a525fad96ae71cc38d3d3e4b122b452644993d8d Mon Sep 17 00:00:00 2001 From: Dustin Wilson Date: Mon, 26 Jun 2023 15:42:39 -0500 Subject: [PATCH] Added secondary error handling in Handler This should allow Catcher to print errors even if there are errors encountered when logging or exporting variables in stack traces --- lib/Catcher/Handler.php | 63 +++++++++++++++++++++++++++++++- lib/Catcher/PlainTextHandler.php | 2 +- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php index b10e33e..27d77ce 100644 --- a/lib/Catcher/Handler.php +++ b/lib/Catcher/Handler.php @@ -7,7 +7,8 @@ declare(strict_types=1); namespace MensBeam\Catcher; -use Psr\Log\LoggerInterface; +use MensBeam\Catcher, + Psr\Log\LoggerInterface; abstract class Handler { @@ -146,6 +147,49 @@ abstract class Handler { return $output; } + /** + * If an error is triggered while logging the error would be output by PHP's + * handler. This is used to attempt as best as possible to have the error be + * output by Catcher. + * + * @internal + */ + public function handleError(int $code, string $message, ?string $file = null, ?int $line = null): bool { + if ($code && $code & error_reporting()) { + // PHP's method for getting the current exception handler is stupid, + // but that's how it is... + $exceptionHandler = set_exception_handler(null); + set_exception_handler($exceptionHandler); + + // If the current exception handler happens to not be Catcher use PHP's handler + // instead; this shouldn't happen but is here just in case + if (!is_array($exceptionHandler) || !$exceptionHandler[0] instanceof Catcher) { + return false; + } + + $catcher = $exceptionHandler[0]; + $handlers = $catcher->getHandlers(); + $silent = false; + foreach ($handlers as $h) { + $h->setOption('logger', null); + $h->setOption('varExporter', null); + $silent = (!$silent) ? $h->getOption('silent') : $silent; + } + + // If all of the handlers are silent then use PHP's handler instead; this is + // because a valid use for Catcher is to have it be silent but instead have the + // logger print the errors to stderr/stdout; if there is an error in the logger + // then it wouldn't print. + if ($silent) { + return false; + } + + $catcher->handleThrowable(new Error($message, $code, $file, $line)); + } + + return true; + } + public function setOption(string $name, mixed $value): void { $class = get_class($this); if (!property_exists($class, "_$name")) { @@ -219,7 +263,12 @@ abstract class Handler { abstract protected function invokeCallback(): void; protected function log(\Throwable $throwable, string $message): void { + if ($this->_logger === null) { + return; + } + $context = [ 'exception' => $throwable ]; + set_error_handler([ $this, 'handleError' ]); if ($throwable instanceof \Error) { switch ($throwable->getCode()) { case \E_NOTICE: @@ -246,6 +295,7 @@ abstract class Handler { } else { $this->_logger->critical($message, $context); } + restore_error_handler(); } protected function print(string $string): void { @@ -257,4 +307,15 @@ abstract class Handler { echo $string; } + + + protected function varExporter(mixed $value): string|bool { + $exporter = $this->_varExporter; + + set_error_handler([ $this, 'handleError' ]); + $value = $exporter($value); + restore_error_handler(); + + return $value; + } } \ No newline at end of file diff --git a/lib/Catcher/PlainTextHandler.php b/lib/Catcher/PlainTextHandler.php index d217806..6d2158c 100644 --- a/lib/Catcher/PlainTextHandler.php +++ b/lib/Catcher/PlainTextHandler.php @@ -86,7 +86,7 @@ class PlainTextHandler extends Handler { if (!empty($frame['args']) && $this->_backtraceArgFrameLimit > $key) { $varExporter = $this->_varExporter; - $output .= preg_replace('/^/m', "$indent| ", $varExporter($frame['args'])) . \PHP_EOL; + $output .= preg_replace('/^/m', "$indent| ", $this->varExporter($frame['args'])) . \PHP_EOL; } }