diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php index 17cc76e7..7ea5fd7d 100644 --- a/src/Monolog/Handler/RavenHandler.php +++ b/src/Monolog/Handler/RavenHandler.php @@ -181,7 +181,7 @@ class RavenHandler extends AbstractProcessingHandler } if (isset($record['context']['exception']) && $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'], [], $options); diff --git a/src/Monolog/Handler/WhatFailureGroupHandler.php b/src/Monolog/Handler/WhatFailureGroupHandler.php index 42c2336a..c2a58d5d 100644 --- a/src/Monolog/Handler/WhatFailureGroupHandler.php +++ b/src/Monolog/Handler/WhatFailureGroupHandler.php @@ -46,6 +46,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/src/Monolog/Logger.php b/src/Monolog/Logger.php index 11b94620..4579f842 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -15,6 +15,7 @@ use DateTimeZone; use Monolog\Handler\HandlerInterface; use Psr\Log\LoggerInterface; use Psr\Log\InvalidArgumentException; +use Throwable; /** * Monolog log channel @@ -135,6 +136,11 @@ class Logger implements LoggerInterface */ protected $timezone; + /** + * @var callable + */ + protected $exceptionHandler; + /** * @param string $name The logging channel, a simple descriptive name that is attached to all log records * @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc. @@ -304,22 +310,26 @@ class Logger implements LoggerInterface 'extra' => [], ]; - foreach ($this->processors as $processor) { - $record = call_user_func($processor, $record); - } - - // advance the array pointer to the first handler that will handle this record - reset($this->handlers); - while ($handlerKey !== key($this->handlers)) { - next($this->handlers); - } - - 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); + // advance the array pointer to the first handler that will handle this record + reset($this->handlers); + while ($handlerKey !== key($this->handlers)) { + next($this->handlers); + } + + while ($handler = current($this->handlers)) { + if (true === $handler->handle($record)) { + break; + } + + next($this->handlers); + } + } catch (Throwable $e) { + $this->handleException($e, $record); } return true; @@ -392,6 +402,23 @@ class Logger implements LoggerInterface return false; } + /** + * Set a custom exception handler that will be called if adding a new record fails + * + * The callable will receive an exception object and the record that failed to be logged + */ + public function setExceptionHandler(?callable $callback): self + { + $this->exceptionHandler = $callback; + + return $this; + } + + public function getExceptionHandler(): ?callable + { + return $this->exceptionHandler; + } + /** * Adds a log record at an arbitrary level. * @@ -533,4 +560,17 @@ class Logger implements LoggerInterface { return $this->timezone; } + + /** + * Delegates exception management to the custom exception handler, + * or throws the exception if no custom handler is set. + */ + protected function handleException(Throwable $e, array $record) + { + if (!$this->exceptionHandler) { + throw $e; + } + + call_user_func($this->exceptionHandler, $e, $record); + } } diff --git a/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php b/tests/Monolog/Handler/WhatFailureGroupHandlerTest.php index 60f51755..475889e2 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 */ diff --git a/tests/Monolog/LoggerTest.php b/tests/Monolog/LoggerTest.php index 428daa0a..69ea4010 100644 --- a/tests/Monolog/LoggerTest.php +++ b/tests/Monolog/LoggerTest.php @@ -576,4 +576,63 @@ class LoggerTest extends \PHPUnit\Framework\TestCase 'without microseconds' => [false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame', 'Y-m-d\TH:i:sP'], ]; } + + /** + * @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::handleException + * @expectedException Exception + */ + public function testDefaultHandleException() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock(); + $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->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock(); + $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'); + } }