diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index e3b5ebc6..ea5a2a65 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -11,9 +11,9 @@ namespace Monolog\Formatter; -use Throwable; use Monolog\DateTimeImmutable; use Monolog\Utils; +use Throwable; /** * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets @@ -28,6 +28,8 @@ class NormalizerFormatter implements FormatterInterface protected $maxNormalizeDepth = 9; protected $maxNormalizeItemCount = 1000; + private $jsonEncodeOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION; + /** * @param ?string $dateFormat The format of the timestamp: one supported by DateTime::format */ @@ -89,6 +91,20 @@ class NormalizerFormatter implements FormatterInterface return $this; } + /** + * Enables `json_encode` pretty print. + */ + public function setJsonPrettyPrint(bool $enable): self + { + if ($enable) { + $this->jsonEncodeOptions |= JSON_PRETTY_PRINT; + } else { + $this->jsonEncodeOptions ^= JSON_PRETTY_PRINT; + } + + return $this; + } + /** * @param mixed $data * @return int|bool|string|null|array @@ -247,7 +263,7 @@ class NormalizerFormatter implements FormatterInterface */ private function jsonEncode($data) { - return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION); + return json_encode($data, $this->jsonEncodeOptions); } /** diff --git a/tests/Monolog/Formatter/JsonFormatterTest.php b/tests/Monolog/Formatter/JsonFormatterTest.php index 7ccf5bc8..2208393c 100644 --- a/tests/Monolog/Formatter/JsonFormatterTest.php +++ b/tests/Monolog/Formatter/JsonFormatterTest.php @@ -46,6 +46,36 @@ class JsonFormatterTest extends TestCase $this->assertEquals('{"message":"test","context":{},"level":300,"level_name":"WARNING","channel":"test","datetime":"'.$record['datetime']->format('Y-m-d\TH:i:s.uP').'","extra":{}}', $formatter->format($record)); } + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormatWithPrettyPrint() + { + $formatter = new JsonFormatter(); + $formatter->setJsonPrettyPrint(true); + $record = $this->getRecord(); + $record['context'] = $record['extra'] = new \stdClass; + $this->assertEquals(json_encode($record, JSON_PRETTY_PRINT)."\n", $formatter->format($record)); + + $formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false); + $formatter->setJsonPrettyPrint(true); + $record = $this->getRecord(); + $this->assertEquals( + '{ + "message": "test", + "context": {}, + "level": 300, + "level_name": "WARNING", + "channel": "test", + "datetime": "'.$record['datetime']->format('Y-m-d\TH:i:s.uP').'", + "extra": {} +}', $formatter->format($record)); + + $formatter->setJsonPrettyPrint(false); + $record = $this->getRecord(); + $this->assertEquals('{"message":"test","context":{},"level":300,"level_name":"WARNING","channel":"test","datetime":"'.$record['datetime']->format('Y-m-d\TH:i:s.uP').'","extra":{}}', $formatter->format($record)); + } + /** * @covers Monolog\Formatter\JsonFormatter::formatBatch * @covers Monolog\Formatter\JsonFormatter::formatBatchJson diff --git a/tests/Monolog/Formatter/NormalizerFormatterTest.php b/tests/Monolog/Formatter/NormalizerFormatterTest.php index 01bf36d2..8f3499a5 100644 --- a/tests/Monolog/Formatter/NormalizerFormatterTest.php +++ b/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -418,6 +418,45 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase ); } + /** + * This test was copied from `testExceptionTraceWithArgs` in order to ensure that pretty prints works + */ + public function testPrettyPrint() + { + try { + // This will contain $resource and $wrappedResource as arguments in the trace item + $resource = fopen('php://memory', 'rw+'); + fwrite($resource, 'test_resource'); + $wrappedResource = new TestFooNorm; + $wrappedResource->foo = $resource; + // Just do something stupid with a resource/wrapped resource as argument + $arr = [$wrappedResource, $resource]; + // modifying the array inside throws a "usort(): Array was modified by the user comparison function" + usort($arr, function ($a, $b) { + throw new \ErrorException('Foo'); + }); + } catch (\Throwable $e) { + } + + $formatter = new NormalizerFormatter(); + $record = ['context' => ['exception' => $e]]; + $formatter->setJsonPrettyPrint(true); + $result = $formatter->format($record); + + $this->assertSame( + '{ + "function": "Monolog\\\\Formatter\\\\{closure}", + "class": "Monolog\\\\Formatter\\\\NormalizerFormatterTest", + "type": "->", + "args": [ + "[object] (Monolog\\\\Formatter\\\\TestFooNorm)", + "[resource(stream)]" + ] +}', + $result['context']['exception']['trace'][0] + ); + } + /** * @param NormalizerFormatter $formatter * @param \Throwable $exception