Browse Source

Added ignore option to Handler

main 2.1.2
Dustin Wilson 6 months ago
parent
commit
6d23922b81
  1. 2
      README.md
  2. 94
      lib/Catcher/Handler.php
  3. 11
      lib/Catcher/InvalidArgumentException.php
  4. 15
      tests/cases/TestHandler.php
  5. 29
      tests/cases/TestJSONHandler.php
  6. 29
      tests/cases/TestPlainTextHandler.php

2
README.md

@ -190,6 +190,7 @@ abstract class Handler {
protected bool $_forceExit = false;
protected bool $_forceOutputNow = false;
protected int $_httpCode = 500;
protected ?array $_ignore = null;
protected ?LoggerInterface $_logger = null;
protected bool $_logWhenSilent = true;
protected bool $_outputBacktrace = false;
@ -241,6 +242,7 @@ _forceBreak_: When set this will force the stack loop to break after the handler
_forceExit_: When set this will force an exit after all handlers have been run. Defaults to _false_.
_forceOutputNow_: When set this will force output of the handler immediately. Defaults to _false_.
_httpCode_: The HTTP code to be sent; possible values are 200, 400-599. Defaults to _500_.
_ignore_: An array of class strings or error codes to ignore. Defaults to _null_ (no ignoring).
_logger_: The PSR-3 compatible logger in which to log to. Defaults to _null_ (no logging).
_logWhenSilent_: When set to true the handler will still send logs when silent. Defaults to _true_.
_outputBacktrace_: When true will output a stack trace. Defaults to _false_.

94
lib/Catcher/Handler.php

@ -50,6 +50,11 @@ abstract class Handler {
protected bool $_forceOutputNow = false;
/** The HTTP code to be sent; possible values: 200, 400-599 */
protected int $_httpCode = 500;
/**
* An array of class strings or error codes to ignore
* @var int[]|string[]
*/
protected array $_ignore = [];
/** The PSR-3 compatible logger in which to log to; defaults to null (no logging) */
protected ?LoggerInterface $_logger = null;
/** When set to true the handler will still send logs when silent */
@ -117,30 +122,45 @@ abstract class Handler {
}
public function handle(ThrowableController $controller): array {
$output = $this->buildOutputArray($controller);
if ($this->_outputBacktrace) {
$output['frames'] = $controller->getFrames(argFrameLimit: $this->_backtraceArgFrameLimit);
}
if ($this->_outputTime && $this->_timeFormat !== '') {
$output['time'] = new \DateTimeImmutable();
$ignore = false;
if (count($this->_ignore) > 0) {
$throwable = $controller->getThrowable();
foreach ($this->_ignore as $i) {
if (($throwable instanceof Error && is_int($i) && $throwable->getCode() === $i) || (is_string($i) && $throwable instanceof $i)) {
$ignore = true;
break;
}
}
}
$code = 0;
if ($this->_bubbles) {
$code = self::BUBBLES;
}
if ($this->_forceExit) {
$code |= self::EXIT;
}
if ($this->_logger !== null && (!$this->_silent || ($this->_silent && $this->_logWhenSilent))) {
$code |= self::LOG;
if (!$ignore) {
if ($this->_forceExit) {
$code |= self::EXIT;
}
if ($this->_logger !== null && (!$this->_silent || ($this->_silent && $this->_logWhenSilent))) {
$code |= self::LOG;
}
if ($this->_forceOutputNow) {
$code |= self::NOW;
}
if (!$this->_silent) {
$code |= self::OUTPUT;
}
} else {
return [ 'code' => $code ];
}
if ($this->_forceOutputNow) {
$code |= self::NOW;
$output = $this->buildOutputArray($controller);
if ($this->_outputBacktrace) {
$output['frames'] = $controller->getFrames(argFrameLimit: $this->_backtraceArgFrameLimit);
}
if (!$this->_silent) {
$code |= self::OUTPUT;
if ($this->_outputTime && $this->_timeFormat !== '') {
$output['time'] = new \DateTimeImmutable();
}
$output['code'] = $code;
@ -205,20 +225,33 @@ abstract class Handler {
return;
}
if (
$name === 'httpCode' &&
is_int($value) &&
$value !== 200 &&
max(400, min($value, 418)) !== $value &&
max(421, min($value, 429)) !== $value &&
$value !== 431 &&
$value !== 451 &&
max(500, min($value, 511)) !== $value &&
// Cloudflare extensions
max(520, min($value, 527)) !== $value &&
$value !== 530
) {
throw new RangeException('Option "httpCode" can only be a valid HTTP 200, 4XX, or 5XX code');
switch ($name) {
case 'httpCode':
if (
is_int($value) &&
$value !== 200 &&
max(400, min($value, 418)) !== $value &&
max(421, min($value, 429)) !== $value &&
$value !== 431 &&
$value !== 451 &&
max(500, min($value, 511)) !== $value &&
// Cloudflare extensions
max(520, min($value, 527)) !== $value &&
$value !== 530
) {
throw new RangeException('Option "httpCode" can only be a valid HTTP 200, 4XX, or 5XX code');
}
break;
case 'ignore':
if (is_array($value)) {
foreach ($value as $v) {
if (!is_int($v) && !is_string($v)) {
throw new InvalidArgumentException('Option "ignore" can only be an array of integers and/or strings');
}
}
}
break;
}
$name = "_$name";
@ -254,6 +287,7 @@ 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']);

11
lib/Catcher/InvalidArgumentException.php

@ -0,0 +1,11 @@
<?php
/**
* @license MIT
* Copyright 2022 Dustin Wilson, et al.
* See LICENSE and AUTHORS files for details
*/
declare(strict_types=1);
namespace MensBeam\Catcher;
class InvalidArgumentException extends \InvalidArgumentException {}

15
tests/cases/TestHandler.php

@ -10,6 +10,7 @@ namespace MensBeam\Catcher\Test;
use MensBeam\Catcher\{
Error as CatcherError,
Handler,
InvalidArgumentException,
RangeException,
ThrowableController
};
@ -62,15 +63,12 @@ class TestHandler extends ErrorHandlingTestCase {
$r = new \ReflectionProperty($this->handler::class, 'outputBuffer');
$r->setAccessible(true);
$this->assertEquals(1, count($r->getValue($this->handler)));
$this->assertNull($this->handler->getLastOutputThrowable());
$h = $this->handler;
$h();
$this->assertEquals(0, count($r->getValue($h)));
$this->assertSame(\Exception::class, $h->getLastOutputThrowable()['class']);
$this->assertEquals(0, count($r->getValue($this->handler)));
$h();
$this->assertEquals(0, count($r->getValue($h)));
$this->assertSame(\Exception::class, $h->getLastOutputThrowable()['class']);
$this->assertEquals(0, count($r->getValue($this->handler)));
}
/** @dataProvider provideLogTests */
@ -160,6 +158,12 @@ class TestHandler extends ErrorHandlingTestCase {
$l = new FailLogger();
$l->error('Ook!');
}
],
[
InvalidArgumentException::class,
function (Handler $h): void {
$h->setOption('ignore', [ \M_PI ]);
}
]
];
@ -260,6 +264,7 @@ class TestHandler extends ErrorHandlingTestCase {
[ 'httpCode', 200 ],
[ 'httpCode', 400 ],
[ 'httpCode', 502 ],
[ 'ignore', [ \Exception::class, \E_USER_ERROR ] ],
[ 'logger', Phake::mock(LoggerInterface::class) ],
[ 'logWhenSilent', false ],
[ 'outputBacktrace', true ],

29
tests/cases/TestJSONHandler.php

@ -17,7 +17,10 @@ use Psr\Log\LoggerInterface,
Phake;
/** @covers \MensBeam\Catcher\JSONHandler */
/**
* @covers \MensBeam\Catcher\JSONHandler
* @covers \MensBeam\Catcher\Handler
*/
class TestJSONHandler extends \PHPUnit\Framework\TestCase {
protected ?Handler $handler = null;
@ -32,7 +35,7 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
}
/** @dataProvider provideInvocationTests */
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, int $line): void {
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, ?array $ignore, int $line): void {
$this->handler->setOption('outputToStderr', false);
if (!$silent) {
@ -42,6 +45,9 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
$l = Phake::mock(LoggerInterface::class);
$this->handler->setOption('logger', $l);
}
if ($ignore !== null) {
$this->handler->setOption('ignore', $ignore);
}
$o = $this->handler->handle(new ThrowableController($throwable));
$c = $o['class'] ?? null;
@ -51,17 +57,20 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
$h();
$u = ob_get_clean();
if (!$silent) {
if (!$silent && $ignore === null) {
$u = json_decode($u, true);
$this->assertEquals($c, $u['class']);
$this->assertEquals(__FILE__, $u['file']);
$this->assertEquals($line, $u['line']);
} else {
if ($ignore !== null) {
$this->assertNull($h->getLastOutputThrowable());
}
$this->assertSame('', $u);
}
if ($log) {
Phake::verify($l, Phake::times(1))->$logMethodName;
Phake::verify($l, Phake::times((int)(count($ignore ?? []) === 0)))->$logMethodName;
}
}
@ -83,10 +92,14 @@ class TestJSONHandler extends \PHPUnit\Framework\TestCase {
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' ]
[ new \Exception('Ook!'), false, true, 'critical', null ],
[ new \Exception('Ook!'), false, true, 'critical', [ \Exception::class ] ],
[ new \Error('Ook!'), true, false, null, null ],
[ new \Error('Ook!'), true, false, null, [ \Error::class ] ],
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', null ],
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', [ \E_ERROR ] ],
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', null ],
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', [ \Exception::class ] ]
];
$l = count($options);

29
tests/cases/TestPlainTextHandler.php

@ -17,7 +17,10 @@ use Psr\Log\LoggerInterface,
Phake;
/** @covers \MensBeam\Catcher\PlainTextHandler */
/**
* @covers \MensBeam\Catcher\PlainTextHandler
* @covers \MensBeam\Catcher\Handler
*/
class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
protected ?Handler $handler = null;
@ -32,7 +35,7 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
}
/** @dataProvider provideInvocationTests */
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, int $line): void {
public function testInvocation(\Throwable $throwable, bool $silent, bool $log, ?string $logMethodName, ?array $ignore, int $line): void {
$this->handler->setOption('outputToStderr', false);
if (!$silent) {
@ -42,6 +45,9 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
$l = Phake::mock(LoggerInterface::class);
$this->handler->setOption('logger', $l);
}
if ($ignore !== null) {
$this->handler->setOption('ignore', $ignore);
}
$o = $this->handler->handle(new ThrowableController($throwable));
@ -56,14 +62,17 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
$u = ob_get_clean();
$u = substr($u, 0, strpos($u, \PHP_EOL) ?: 0);
if (!$silent) {
if (!$silent && $ignore === null) {
$this->assertMatchesRegularExpression(sprintf('/^\[[\d:]+\] %s: Ook\! in file %s on line %s$/', preg_quote($c, '/'), preg_quote(__FILE__, '/'), $line), $u);
} else {
if ($ignore !== null) {
$this->assertNull($h->getLastOutputThrowable());
}
$this->assertSame('', $u);
}
if ($log) {
Phake::verify($l, Phake::times(1))->$logMethodName;
Phake::verify($l, Phake::times((int)(count($ignore ?? []) === 0)))->$logMethodName;
}
}
@ -85,10 +94,14 @@ class TestPlainTextHandler extends \PHPUnit\Framework\TestCase {
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' ]
[ new \Exception('Ook!'), false, true, 'critical', null ],
[ new \Exception('Ook!'), false, true, 'critical', [ \Exception::class ] ],
[ new \Error('Ook!'), true, false, null, null ],
[ new \Error('Ook!'), true, false, null, [ \Error::class ] ],
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', null ],
[ new Error('Ook!', \E_ERROR, __FILE__, __LINE__), false, true, 'error', [ \E_ERROR ] ],
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', null ],
[ new \Exception(message: 'Ook!', previous: new \Error(message: 'Eek!', previous: new \ParseError('Ack!'))), true, true, 'critical', [ \Exception::class ] ]
];
$l = count($options);

Loading…
Cancel
Save