From 207c91699e8e50a4837b92e17e9919a2bf0573db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Par=C3=A9?= Date: Tue, 27 Jun 2017 23:29:27 +0200 Subject: [PATCH 1/4] Custom exception handler (#500) Add custom exception handler to let the user change the default behavior when Monolog raise an exception while logging a record. --- src/Monolog/Logger.php | 65 ++++++++++++++++++++++++++++----- tests/Monolog/LoggerTest.php | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 8 deletions(-) diff --git a/src/Monolog/Logger.php b/src/Monolog/Logger.php index 49d00af1..8686c8aa 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -133,6 +133,11 @@ class Logger implements LoggerInterface */ protected $microsecondTimestamps = true; + /** + * @var callable + */ + protected $exceptionHandler; + /** * @param string $name The logging channel * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. @@ -329,16 +334,20 @@ class Logger implements LoggerInterface 'extra' => array(), ); - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - - while ($handler = current($this->handlers)) { - if (true === $handler->handle($record)) { - break; + try { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); } - next($this->handlers); + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + } catch (\Exception $ex) { + $this->handleException($ex, $record); } return true; @@ -501,6 +510,46 @@ class Logger implements LoggerInterface return false; } + /** + * Set a custom exception handler + * + * @param callable $callback + * @return $this + */ + public function setExceptionHandler($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Exception handler must be valid callable (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + $this->exceptionHandler = $callback; + + return $this; + } + + /** + * @return callable + */ + public function getExceptionHandler() + { + return $this->exceptionHandler; + } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + * + * @param Exception $ex + * @param array $record + */ + protected function handleException(\Exception $ex, $record) + { + if ($this->exceptionHandler) { + call_user_func($this->exceptionHandler, $ex, $record); + } else { + throw $ex; + } + } + /** * Adds a log record at an arbitrary level. * diff --git a/tests/Monolog/LoggerTest.php b/tests/Monolog/LoggerTest.php index 1ecc34a0..f938ea02 100644 --- a/tests/Monolog/LoggerTest.php +++ b/tests/Monolog/LoggerTest.php @@ -545,4 +545,73 @@ class LoggerTest extends \PHPUnit_Framework_TestCase 'without microseconds' => array(false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame'), ); } + + /** + * @covers Monolog\Logger::setExceptionHandler + */ + public function testSetExceptionHandler() + { + $logger = new Logger(__METHOD__); + $this->assertNull($logger->getExceptionHandler()); + $callback = function ($ex) { + }; + $logger->setExceptionHandler($callback); + $this->assertEquals($callback, $logger->getExceptionHandler()); + } + + /** + * @covers Monolog\Logger::setExceptionHandler + * @expectedException InvalidArgumentException + */ + public function testBadExceptionHandlerType() + { + $logger = new Logger(__METHOD__); + $logger->setExceptionHandler(false); + } + + /** + * @covers Monolog\Logger::handleException + * @expectedException Exception + */ + public function testDefaultHandleException() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->throwException(new \Exception('Some handler exception'))) + ; + $logger->pushHandler($handler); + $logger->info('test'); + } + + /** + * @covers Monolog\Logger::handleException + * @covers Monolog\Logger::addRecord + */ + public function testCustomHandleException() + { + $logger = new Logger(__METHOD__); + $that = $this; + $logger->setExceptionHandler(function ($e, $record) use ($that) { + $that->assertEquals($e->getMessage(), 'Some handler exception'); + $that->assertTrue(is_array($record)); + $that->assertEquals($record['message'], 'test'); + }); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->throwException(new \Exception('Some handler exception'))) + ; + $logger->pushHandler($handler); + $logger->info('test'); + } } From 22b92c7c00d495b4a9cb043bc592aa5a3f9065c8 Mon Sep 17 00:00:00 2001 From: Ben Dubuisson Date: Mon, 10 Jul 2017 19:18:17 +1200 Subject: [PATCH 2/4] Fixed message not being passed to client when capturing an exception The raven client expects the message to be at the first level of the data array when passing an exception. see https://github.com/getsentry/sentry-php/blob/master/lib/Raven/Client.php#L795 --- src/Monolog/Handler/RavenHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php index eb5dda08..34ff0091 100644 --- a/src/Monolog/Handler/RavenHandler.php +++ b/src/Monolog/Handler/RavenHandler.php @@ -180,7 +180,7 @@ class RavenHandler extends AbstractProcessingHandler } if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { - $options['extra']['message'] = $record['formatted']; + $options['message'] = $record['formatted']; $this->ravenClient->captureException($record['context']['exception'], $options); } else { $this->ravenClient->captureMessage($record['formatted'], array(), $options); From 3035d4a251c989b4795635b33d4cc4e1bb662b7e Mon Sep 17 00:00:00 2001 From: Chris Wilkinson Date: Wed, 12 Jul 2017 07:45:50 +0100 Subject: [PATCH 3/4] Fix WhatFailureGroupHandler::handleBatch when the handler has processors --- .../Handler/WhatFailureGroupHandler.php | 10 ++++++++ .../Handler/WhatFailureGroupHandlerTest.php | 23 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Monolog/Handler/WhatFailureGroupHandler.php b/src/Monolog/Handler/WhatFailureGroupHandler.php index 2732ba3d..6bc4671c 100644 --- a/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -48,6 +48,16 @@ class WhatFailureGroupHandler extends GroupHandler */ public function handleBatch(array $records) { + if ($this->processors) { + $processed = array(); + foreach ($records as $record) { + foreach ($this->processors as $processor) { + $processed[] = call_user_func($processor, $record); + } + } + $records = $processed; + } + foreach ($this->handlers as $handler) { try { $handler->handleBatch($records); diff --git a/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php index 8d37a1fc..0594a232 100644 --- a/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php +++ b/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php @@ -87,6 +87,29 @@ class WhatFailureGroupHandlerTest extends TestCase $this->assertTrue($records[0]['extra']['foo']); } + /** + * @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch + */ + public function testHandleBatchUsesProcessors() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new WhatFailureGroupHandler($testHandlers); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + $this->assertTrue($records[1]['extra']['foo']); + } + } + /** * @covers Monolog\Handler\WhatFailureGroupHandler::handle */ From 6d79e51f91de32c7ea9d85e2a1ad1c92ea34779e Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 8 Jun 2018 20:47:04 +0200 Subject: [PATCH 4/4] Tweaks to exception handler, refs #1012 --- src/Monolog/Logger.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Monolog/Logger.php b/src/Monolog/Logger.php index bb14a026..28e9cf98 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -15,6 +15,7 @@ use Monolog\Handler\HandlerInterface; use Monolog\Handler\StreamHandler; use Psr\Log\LoggerInterface; use Psr\Log\InvalidArgumentException; +use Exception; /** * Monolog log channel @@ -346,8 +347,8 @@ class Logger implements LoggerInterface next($this->handlers); } - } catch (\Exception $ex) { - $this->handleException($ex, $record); + } catch (Exception $e) { + $this->handleException($e, $record); } return true; @@ -537,17 +538,14 @@ class Logger implements LoggerInterface /** * Delegates exception management to the custom exception handler, * or throws the exception if no custom handler is set. - * - * @param Exception $ex - * @param array $record */ - protected function handleException(\Exception $ex, $record) + protected function handleException(Exception $e, array $record) { - if ($this->exceptionHandler) { - call_user_func($this->exceptionHandler, $ex, $record); - } else { - throw $ex; + if (!$this->exceptionHandler) { + throw $e; } + + call_user_func($this->exceptionHandler, $e, $record); } /**