diff --git a/doc/02-handlers-formatters-processors.md b/doc/02-handlers-formatters-processors.md index 7734132c..a61bd584 100644 --- a/doc/02-handlers-formatters-processors.md +++ b/doc/02-handlers-formatters-processors.md @@ -108,6 +108,11 @@ exceptions raised by each child handler. This allows you to ignore issues where a remote tcp connection may have died but you do not want your entire application to crash and may wish to continue to log to other handlers. +- [_FallbackGroupHandler_](../src/Monolog/Handler/FallbackGroupHandler.php): This handler extends the _GroupHandler_ ignoring + exceptions raised by each child handler, until one has handled without throwing. + This allows you to ignore issues where a remote tcp connection may have died + but you do not want your entire application to crash and may wish to continue + to attempt log to other handlers, until one does not throw. - [_BufferHandler_](../src/Monolog/Handler/BufferHandler.php): This handler will buffer all the log records it receives until `close()` is called at which point it will call `handleBatch()` on the handler it wraps with all the log messages at once. This is very useful to diff --git a/src/Monolog/Handler/FallbackGroupHandler.php b/src/Monolog/Handler/FallbackGroupHandler.php new file mode 100644 index 00000000..728c4ed0 --- /dev/null +++ b/src/Monolog/Handler/FallbackGroupHandler.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Throwable; + +class FallbackGroupHandler extends GroupHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record): bool + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + foreach ($this->handlers as $handler) { + try { + $handler->handle($record); + break; + } catch (Throwable $e) { + // What throwable? + } + } + return false === $this->bubble; + } + /** + * {@inheritdoc} + */ + public function handleBatch(array $records): void + { + if ($this->processors) { + $processed = []; + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + $processed[] = $record; + } + $records = $processed; + } + + foreach ($this->handlers as $handler) { + try { + $handler->handleBatch($records); + break; + } catch (Throwable $e) { + // What throwable? + } + } + } +} diff --git a/tests/Monolog/Handler/ExceptionTestHandler.php b/tests/Monolog/Handler/ExceptionTestHandler.php new file mode 100644 index 00000000..f5a682ef --- /dev/null +++ b/tests/Monolog/Handler/ExceptionTestHandler.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Exception; + +class ExceptionTestHandler extends TestHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record): bool + { + throw new Exception("ExceptionTestHandler::handle"); + + parent::handle($record); + } +} diff --git a/tests/Monolog/Handler/FallbackGroupHandlerTest.php b/tests/Monolog/Handler/FallbackGroupHandlerTest.php new file mode 100644 index 00000000..f92a5afe --- /dev/null +++ b/tests/Monolog/Handler/FallbackGroupHandlerTest.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Test\TestCase; + +class FallbackGroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FallbackGroupHandler::__construct + * @covers Monolog\Handler\FallbackGroupHandler::handle + */ + public function testHandle() + { + $testHandlerOne = new TestHandler(); + $testHandlerTwo = new TestHandler(); + $testHandlers = [$testHandlerOne, $testHandlerTwo]; + $handler = new FallbackGroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + + $this->assertCount(2, $testHandlerOne->getRecords()); + $this->assertCount(0, $testHandlerTwo->getRecords()); + } + + /** + * @covers Monolog\Handler\FallbackGroupHandler::__construct + * @covers Monolog\Handler\FallbackGroupHandler::handle + */ + public function testHandleExceptionThrown() + { + $testHandlerOne = new ExceptionTestHandler(); + $testHandlerTwo = new TestHandler(); + $testHandlers = [$testHandlerOne, $testHandlerTwo]; + $handler = new FallbackGroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + + $this->assertCount(0, $testHandlerOne->getRecords()); + $this->assertCount(2, $testHandlerTwo->getRecords()); + } + + /** + * @covers Monolog\Handler\FallbackGroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlerOne = new TestHandler(); + $testHandlerTwo = new TestHandler(); + $testHandlers = [$testHandlerOne, $testHandlerTwo]; + $handler = new FallbackGroupHandler($testHandlers); + $handler->handleBatch([$this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)]); + $this->assertCount(2, $testHandlerOne->getRecords()); + $this->assertCount(0, $testHandlerTwo->getRecords()); + } + + /** + * @covers Monolog\Handler\FallbackGroupHandler::handleBatch + */ + public function testHandleBatchExceptionThrown() + { + $testHandlerOne = new ExceptionTestHandler(); + $testHandlerTwo = new TestHandler(); + $testHandlers = [$testHandlerOne, $testHandlerTwo]; + $handler = new FallbackGroupHandler($testHandlers); + $handler->handleBatch([$this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)]); + $this->assertCount(0, $testHandlerOne->getRecords()); + $this->assertCount(2, $testHandlerTwo->getRecords()); + } + + /** + * @covers Monolog\Handler\FallbackGroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = [new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)]; + $handler = new FallbackGroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FallbackGroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FallbackGroupHandler([$test]); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } + + /** + * @covers Monolog\Handler\FallbackGroupHandler::handleBatch + */ + public function testHandleBatchUsesProcessors() + { + $testHandlerOne = new ExceptionTestHandler(); + $testHandlerTwo = new TestHandler(); + $testHandlers = [$testHandlerOne, $testHandlerTwo]; + $handler = new FallbackGroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->pushProcessor(function ($record) { + $record['extra']['foo2'] = true; + + return $record; + }); + $handler->handleBatch([$this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)]); + $this->assertEmpty($testHandlerOne->getRecords()); + $this->assertTrue($testHandlerTwo->hasDebugRecords()); + $this->assertTrue($testHandlerTwo->hasInfoRecords()); + $this->assertCount(2, $testHandlerTwo->getRecords()); + $records = $testHandlerTwo->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + $this->assertTrue($records[0]['extra']['foo2']); + $this->assertTrue($records[1]['extra']['foo2']); + } +} diff --git a/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php index 6aacdde5..24365659 100644 --- a/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php +++ b/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php @@ -136,16 +136,3 @@ class WhatFailureGroupHandlerTest extends TestCase $this->assertTrue($records[0]['extra']['foo']); } } - -class ExceptionTestHandler extends TestHandler -{ - /** - * {@inheritdoc} - */ - public function handle(array $record): bool - { - parent::handle($record); - - throw new \Exception("ExceptionTestHandler::handle"); - } -}