From cf0f4b38148bc5ee19e83585b5dea135cbbfe3ca Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Fri, 22 Jul 2022 15:27:46 +0200 Subject: [PATCH] Handle __toString to serialize objects which are not json-serializable in JsonFormatter, fixes #1733 --- src/Monolog/Formatter/JsonFormatter.php | 23 ++++++-- tests/Monolog/Formatter/JsonFormatterTest.php | 56 +++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/src/Monolog/Formatter/JsonFormatter.php b/src/Monolog/Formatter/JsonFormatter.php index 42735a6b4..b737d82e3 100644 --- a/src/Monolog/Formatter/JsonFormatter.php +++ b/src/Monolog/Formatter/JsonFormatter.php @@ -178,12 +178,25 @@ protected function normalize($data, int $depth = 0) return $normalized; } - if ($data instanceof \DateTimeInterface) { - return $this->formatDate($data); - } + if (is_object($data)) { + if ($data instanceof \DateTimeInterface) { + return $this->formatDate($data); + } + + if ($data instanceof Throwable) { + return $this->normalizeException($data, $depth); + } + + // if the object has specific json serializability we want to make sure we skip the __toString treatment below + if ($data instanceof \JsonSerializable) { + return $data; + } + + if (method_exists($data, '__toString')) { + return $data->__toString(); + } - if ($data instanceof Throwable) { - return $this->normalizeException($data, $depth); + return $data; } if (is_resource($data)) { diff --git a/tests/Monolog/Formatter/JsonFormatterTest.php b/tests/Monolog/Formatter/JsonFormatterTest.php index 17a3ba823..147021029 100644 --- a/tests/Monolog/Formatter/JsonFormatterTest.php +++ b/tests/Monolog/Formatter/JsonFormatterTest.php @@ -11,6 +11,7 @@ namespace Monolog\Formatter; +use JsonSerializable; use Monolog\Logger; use Monolog\Test\TestCase; @@ -314,4 +315,59 @@ public function testEmptyContextAndExtraFieldsCanBeIgnored() $record ); } + + public function testFormatObjects() + { + $formatter = new JsonFormatter(); + + $record = $formatter->format(array( + 'level' => 100, + 'level_name' => 'DEBUG', + 'channel' => 'test', + 'message' => 'Testing', + 'context' => array( + 'public' => new TestJsonNormPublic, + 'private' => new TestJsonNormPrivate, + 'withToStringAndJson' => new TestJsonNormWithToStringAndJson, + 'withToString' => new TestJsonNormWithToString, + ), + 'extra' => array(), + )); + + $this->assertSame( + '{"level":100,"level_name":"DEBUG","channel":"test","message":"Testing","context":{"public":{"foo":"fooValue"},"private":{},"withToStringAndJson":["json serialized"],"withToString":"stringified"},"extra":{}}'."\n", + $record + ); + } +} + +class TestJsonNormPublic +{ + public $foo = 'fooValue'; +} + +class TestJsonNormPrivate +{ + private $foo = 'fooValue'; +} + +class TestJsonNormWithToStringAndJson implements JsonSerializable +{ + public function jsonSerialize() + { + return ['json serialized']; + } + + public function __toString() + { + return 'SHOULD NOT SHOW UP'; + } +} + +class TestJsonNormWithToString +{ + public function __toString() + { + return 'stringified'; + } }