diff --git a/src/Monolog/Formatter/JsonFormatter.php b/src/Monolog/Formatter/JsonFormatter.php index 1a8fe0b4..22185d41 100644 --- a/src/Monolog/Formatter/JsonFormatter.php +++ b/src/Monolog/Formatter/JsonFormatter.php @@ -11,7 +11,7 @@ namespace Monolog\Formatter; -use Exception; +use Throwable; /** * Encodes whatever record data is passed to it as json @@ -156,7 +156,7 @@ class JsonFormatter extends NormalizerFormatter return $normalized; } - if ($data instanceof Exception) { + if ($data instanceof Throwable) { return $this->normalizeException($data); } @@ -171,7 +171,7 @@ class JsonFormatter extends NormalizerFormatter * * @return array */ - protected function normalizeException(\Throwable $e) + protected function normalizeException(Throwable $e) { $data = [ 'class' => get_class($e), diff --git a/src/Monolog/Handler/RavenHandler.php b/src/Monolog/Handler/RavenHandler.php index db64f803..08d1e7a6 100644 --- a/src/Monolog/Handler/RavenHandler.php +++ b/src/Monolog/Handler/RavenHandler.php @@ -179,7 +179,7 @@ class RavenHandler extends AbstractProcessingHandler $options['release'] = $this->release; } - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) { $options['extra']['message'] = $record['formatted']; $this->ravenClient->captureException($record['context']['exception'], $options); } else { diff --git a/tests/Monolog/Formatter/JsonFormatterTest.php b/tests/Monolog/Formatter/JsonFormatterTest.php index 392647fd..1068fb9d 100644 --- a/tests/Monolog/Formatter/JsonFormatterTest.php +++ b/tests/Monolog/Formatter/JsonFormatterTest.php @@ -80,43 +80,99 @@ class JsonFormatterTest extends TestCase { $formatter = new JsonFormatter(); $exception = new \RuntimeException('Foo'); - $message = $formatter->format([ - 'level_name' => 'CRITICAL', - 'channel' => 'core', - 'context' => ['exception' => $exception], - 'datetime' => new \DateTimeImmutable(), - 'extra' => [], - 'message' => 'foobar', - ]); + $formattedException = $this->formatException($exception); - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - $path = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1); - } else { - $path = substr(json_encode($exception->getFile()), 1, -1); - } - $this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$path.':'.$exception->getLine().'"}},"datetime":'.json_encode(new \DateTimeImmutable()).',"extra":[],"message":"foobar"}'."\n", $message); + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); } public function testDefFormatWithPreviousException() { $formatter = new JsonFormatter(); $exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?')); - $message = $formatter->format([ + $formattedPrevException = $this->formatException($exception->getPrevious()); + $formattedException = $this->formatException($exception, $formattedPrevException); + + $message = $this->formatRecordWithExceptionInContext($formatter, $exception); + + $this->assertContextContainsFormattedException($formattedException, $message); + } + + public function testDefFormatWithThrowable() + { + $formatter = new JsonFormatter(); + $throwable = new \Error('Foo'); + $formattedThrowable = $this->formatException($throwable); + + $message = $this->formatRecordWithExceptionInContext($formatter, $throwable); + + $this->assertContextContainsFormattedException($formattedThrowable, $message); + } + + /** + * @param string $expected + * @param string $actual + * + * @internal param string $exception + */ + private function assertContextContainsFormattedException($expected, $actual) + { + $this->assertEquals( + '{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n", + $actual + ); + } + + /** + * @param JsonFormatter $formatter + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception) + { + $message = $formatter->format(array( 'level_name' => 'CRITICAL', 'channel' => 'core', - 'context' => ['exception' => $exception], - 'datetime' => new \DateTimeImmutable(), - 'extra' => [], + 'context' => array('exception' => $exception), + 'datetime' => null, + 'extra' => array(), 'message' => 'foobar', - ]); + )); + return $message; + } - if (version_compare(PHP_VERSION, '5.4.0', '>=')) { - $pathPrevious = substr(json_encode($exception->getPrevious()->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1); - $pathException = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1); - } else { - $pathPrevious = substr(json_encode($exception->getPrevious()->getFile()), 1, -1); - $pathException = substr(json_encode($exception->getFile()), 1, -1); - } - $this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$pathException.':'.$exception->getLine().'","previous":{"class":"LogicException","message":"'.$exception->getPrevious()->getMessage().'","code":'.$exception->getPrevious()->getCode().',"file":"'.$pathPrevious.':'.$exception->getPrevious()->getLine().'"}}},"datetime":'.json_encode(new \DateTimeImmutable()).',"extra":[],"message":"foobar"}'."\n", $message); + /** + * @param \Exception|\Throwable $exception + * + * @return string + */ + private function formatExceptionFilePathWithLine($exception) + { + $options = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + $path = substr(json_encode($exception->getFile(), $options), 1, -1); + + return $path . ':' . $exception->getLine(); + } + + /** + * @param \Exception|\Throwable $exception + * + * @param null|string $previous + * + * @return string + */ + private function formatException($exception, $previous = null) + { + $formattedException = + '{"class":"' . get_class($exception) . + '","message":"' . $exception->getMessage() . + '","code":' . $exception->getCode() . + ',"file":"' . $this->formatExceptionFilePathWithLine($exception) . + ($previous ? '","previous":' . $previous : '"') . + '}'; + + return $formattedException; } }