From f2617be261a209be1c55eab06f12400e8a7f62ca Mon Sep 17 00:00:00 2001 From: Dustin Wilson Date: Fri, 3 Nov 2023 22:55:11 -0500 Subject: [PATCH] Added JSONHandler --- lib/Catcher/Handler.php | 1 - lib/Catcher/JSONHandler.php | 68 +++++++++++++++++ tests/cases/TestJSONHandler.php | 108 +++++++++++++++++++++++++++ tests/cases/TestPlainTextHandler.php | 14 +--- 4 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 lib/Catcher/JSONHandler.php create mode 100644 tests/cases/TestJSONHandler.php diff --git a/lib/Catcher/Handler.php b/lib/Catcher/Handler.php index a2334cc..255cf56 100644 --- a/lib/Catcher/Handler.php +++ b/lib/Catcher/Handler.php @@ -242,7 +242,6 @@ abstract class Handler { protected function cleanOutputThrowable(array $outputThrowable): array { unset($outputThrowable['controller']); - unset($outputThrowable['code']); if (isset($outputThrowable['previous'])) { $outputThrowable['previous'] = $this->cleanOutputThrowable($outputThrowable['previous']); diff --git a/lib/Catcher/JSONHandler.php b/lib/Catcher/JSONHandler.php new file mode 100644 index 0000000..dc1c819 --- /dev/null +++ b/lib/Catcher/JSONHandler.php @@ -0,0 +1,68 @@ +outputBuffer as $o) { + if (($o['code'] & self::OUTPUT) === 0) { + if ($o['code'] & self::LOG) { + $this->serializeOutputThrowable($o); + } + + continue; + } + + $this->print($this->serializeOutputThrowable($o)); + } + } + + + protected function serializeOutputThrowable(array $outputThrowable, bool $previous = false): array|string { + $controller = $outputThrowable['controller']; + $output = $this->cleanOutputThrowable($outputThrowable); + $jsonFlags = \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE; + if ($this->_prettyPrint) { + $jsonFlags |= \JSON_PRETTY_PRINT; + } + + if (isset($outputThrowable['previous'])) { + $output['previous'] = $this->serializeOutputThrowable($outputThrowable['previous'], true); + } + + if (!$previous) { + if (isset($outputThrowable['frames']) && is_array($outputThrowable['frames']) && count($outputThrowable['frames']) > 0) { + $output['frames'] = $outputThrowable['frames']; + } + + // The log message shouldn't have the timestamp added to it. + if ($outputThrowable['code'] & self::LOG) { + $o = $output; + unset($o['time']); + $this->log($controller->getThrowable(), json_encode($o, $jsonFlags)); + } + + $output = json_encode($output, $jsonFlags); + } + + return $output; + } +} \ No newline at end of file diff --git a/tests/cases/TestJSONHandler.php b/tests/cases/TestJSONHandler.php new file mode 100644 index 0000000..a9aef94 --- /dev/null +++ b/tests/cases/TestJSONHandler.php @@ -0,0 +1,108 @@ +handler = new JSONHandler([ + 'outputBacktrace' => true, + 'silent' => true + ]); + } + + ///** @dataProvider provideHandlingTests */ + /*public function testHandling(\Throwable $throwable, int $expectedCode, array $options = []): void { + foreach ($options as $k => $v) { + $this->handler->setOption($k, $v); + } + + $o = $this->handler->handle(new ThrowableController($throwable)); + $this->assertSame($throwable::class, $o['controller']->getThrowable()::class); + $this->assertEquals($expectedCode, $o['code']); + }*/ + + /** @dataProvider provideInvocationTests */ + public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, int $line): void { + $this->handler->setOption('outputToStderr', false); + + if (!$silent) { + $this->handler->setOption('silent', false); + } + if ($log) { + $l = Phake::mock(LoggerInterface::class); + $this->handler->setOption('logger', $l); + } + + $o = $this->handler->handle(new ThrowableController($throwable)); + $c = $o['class'] ?? null; + + $h = $this->handler; + ob_start(); + $h(); + $u = ob_get_clean(); + + if (!$silent) { + $u = json_decode($u, true); + $this->assertEquals($c, $u['class']); + $this->assertEquals(__FILE__, $u['file']); + $this->assertEquals($line, $u['line']); + } else { + $this->assertSame('', $u); + } + + if ($log) { + Phake::verify($l, Phake::times(1))->$logMethodName; + } + } + + + public static function provideHandlingTests(): iterable { + $options = [ + [ new \Exception('Ook!'), Handler::BUBBLES | Handler::EXIT, [ 'forceExit' => true ] ], + [ new \Error('Ook!'), Handler::BUBBLES ], + [ new Error('Ook!', \E_ERROR, '/dev/null', 42, new \Error('Eek!')), Handler::BUBBLES | Handler::NOW, [ 'forceOutputNow' => true ] ], + [ new \Exception('Ook!'), Handler::BUBBLES, [ 'logger' => Phake::mock(LoggerInterface::class), 'logWhenSilent' => false ] ], + [ new \Error('Ook!'), Handler::BUBBLES | Handler::LOG, [ 'forceOutputNow' => true, 'logger' => Phake::mock(LoggerInterface::class) ] ] + ]; + + foreach ($options as $o) { + $o[1] |= Handler::NOW; + yield $o; + } + } + + public static function provideInvocationTests(): iterable { + $options = [ + [ new \Exception('Ook!'), false, true, 'critical' ], + [ new \Error('Ook!'), true, false, null ], + [ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error' ], + [ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical' ] + ]; + + $l = count($options); + foreach ($options as $k => $o) { + yield [ ...$o, __LINE__ - 4 - $l + $k ]; + } + } +} \ No newline at end of file diff --git a/tests/cases/TestPlainTextHandler.php b/tests/cases/TestPlainTextHandler.php index 402c39e..05a4a55 100644 --- a/tests/cases/TestPlainTextHandler.php +++ b/tests/cases/TestPlainTextHandler.php @@ -11,8 +11,7 @@ use MensBeam\Catcher\{ Error, Handler, PlainTextHandler, - ThrowableController, - UnderflowException + ThrowableController }; use Psr\Log\LoggerInterface, Phake; @@ -32,17 +31,6 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase { ]); } - ///** @dataProvider provideHandlingTests */ - /*public function testHandling(\Throwable $throwable, int $expectedCode, array $options = []): void { - foreach ($options as $k => $v) { - $this->handler->setOption($k, $v); - } - - $o = $this->handler->handle(new ThrowableController($throwable)); - $this->assertSame($throwable::class, $o['controller']->getThrowable()::class); - $this->assertEquals($expectedCode, $o['code']); - }*/ - /** @dataProvider provideInvocationTests */ public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, int $line): void { $this->handler->setOption('outputToStderr', false);