From a258e4fe9081437637bb739e9ad623a18264249f Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Thu, 5 Dec 2024 15:38:02 +0100 Subject: [PATCH] Deprecate DateTimeImmutable (#1928) Fixes #1926 --- CHANGELOG.md | 4 ++ UPGRADE.md | 6 +++ doc/message-structure.md | 2 +- src/Monolog/DateTimeImmutable.php | 37 +++----------- src/Monolog/Formatter/NormalizerFormatter.php | 6 +-- src/Monolog/Handler/ChromePHPHandler.php | 4 +- .../JsonSerializableDateTimeImmutable.php | 48 +++++++++++++++++++ src/Monolog/Logger.php | 9 ++-- .../Processor/PsrLogMessageProcessor.php | 2 +- src/Monolog/Test/TestCase.php | 4 +- .../Monolog/Formatter/ScalarFormatterTest.php | 4 +- tests/Monolog/LoggerTest.php | 6 +-- 12 files changed, 83 insertions(+), 49 deletions(-) create mode 100644 src/Monolog/JsonSerializableDateTimeImmutable.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ce16eecbd..2b948ad30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### Unreleased + + * Deprecated Monolog\DateTimeImmutable in favor of Monolog\JsonSerializableDateTimeImmutable + ### 3.8.0 (2024-11-12) * Added `$fileOpenMode` param to `StreamHandler` to define a custom fopen mode to open the log file (#1913) diff --git a/UPGRADE.md b/UPGRADE.md index 0446803f7..5e82918fa 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,3 +1,9 @@ +### 4.0.0 + +Overall / notable changes: + +- Monolog\DateTimeImmutable has been removed in favor of Monolog\JsonSerializableDateTimeImmutable. + ### 3.0.0 Overall / notable changes: diff --git a/doc/message-structure.md b/doc/message-structure.md index b5e088c32..800907267 100644 --- a/doc/message-structure.md +++ b/doc/message-structure.md @@ -11,7 +11,7 @@ message | string | The log message. When the `PsrLogMessag level | Monolog\Level case | Severity of the log message. See log levels described in [01-usage.md](01-usage.md#log-levels). context | array | Arbitrary data passed with the construction of the message. For example the username of the current user or their IP address. channel | string | The channel this message was logged to. This is the name that was passed when the logger was created with `new Logger('channel')`. -datetime | Monolog\DateTimeImmutable | Date and time when the message was logged. Class extends `\DateTimeImmutable`. +datetime | Monolog\JsonSerializableDateTimeImmutable | Date and time when the message was logged. Class extends `\DateTimeImmutable`. extra | array | A placeholder array where processors can put additional data. Always available, but empty if there are no processors registered. At first glance `context` and `extra` look very similar, and they are in the sense that they both carry arbitrary data that is related to the log message somehow. diff --git a/src/Monolog/DateTimeImmutable.php b/src/Monolog/DateTimeImmutable.php index 3d9477f40..968c58a6b 100644 --- a/src/Monolog/DateTimeImmutable.php +++ b/src/Monolog/DateTimeImmutable.php @@ -11,38 +11,13 @@ namespace Monolog; -use DateTimeZone; +class_alias(JsonSerializableDateTimeImmutable::class, 'Monolog\DateTimeImmutable'); -/** - * Overrides default json encoding of date time objects - * - * @author Menno Holtkamp - * @author Jordi Boggiano - */ -class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable -{ - private bool $useMicroseconds; - - public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) - { - $this->useMicroseconds = $useMicroseconds; - - // if you like to use a custom time to pass to Logger::addRecord directly, - // call modify() or setTimestamp() on this instance to change the date after creating it - parent::__construct('now', $timezone); - } - - public function jsonSerialize(): string - { - if ($this->useMicroseconds) { - return $this->format('Y-m-d\TH:i:s.uP'); - } - - return $this->format('Y-m-d\TH:i:sP'); - } - - public function __toString(): string +if (false) { + /** + * @deprecated Use \Monolog\JsonSerializableDateTimeImmutable instead. + */ + class DateTimeImmutable extends JsonSerializableDateTimeImmutable { - return $this->jsonSerialize(); } } diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index c2660359b..60da29cf7 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -11,7 +11,7 @@ namespace Monolog\Formatter; -use Monolog\DateTimeImmutable; +use Monolog\JsonSerializableDateTimeImmutable; use Monolog\Utils; use Throwable; use Monolog\LogRecord; @@ -322,9 +322,9 @@ protected function toJson($data, bool $ignoreErrors = false): string protected function formatDate(\DateTimeInterface $date): string { - // in case the date format isn't custom then we defer to the custom DateTimeImmutable + // in case the date format isn't custom then we defer to the custom JsonSerializableDateTimeImmutable // formatting logic, which will pick the right format based on whether useMicroseconds is on - if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof DateTimeImmutable) { + if ($this->dateFormat === self::SIMPLE_DATE && $date instanceof JsonSerializableDateTimeImmutable) { return (string) $date; } diff --git a/src/Monolog/Handler/ChromePHPHandler.php b/src/Monolog/Handler/ChromePHPHandler.php index 61c9e83f9..6598e822c 100644 --- a/src/Monolog/Handler/ChromePHPHandler.php +++ b/src/Monolog/Handler/ChromePHPHandler.php @@ -16,7 +16,7 @@ use Monolog\Level; use Monolog\Utils; use Monolog\LogRecord; -use Monolog\DateTimeImmutable; +use Monolog\JsonSerializableDateTimeImmutable; /** * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) @@ -150,7 +150,7 @@ protected function send(): void message: 'Incomplete logs, chrome header size limit reached', level: Level::Warning, channel: 'monolog', - datetime: new DateTimeImmutable(true), + datetime: new JsonSerializableDateTimeImmutable(true), ); self::$json['rows'][\count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); $json = Utils::jsonEncode(self::$json, Utils::DEFAULT_JSON_FLAGS & ~JSON_UNESCAPED_UNICODE, true); diff --git a/src/Monolog/JsonSerializableDateTimeImmutable.php b/src/Monolog/JsonSerializableDateTimeImmutable.php new file mode 100644 index 000000000..de2cc5e96 --- /dev/null +++ b/src/Monolog/JsonSerializableDateTimeImmutable.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use DateTimeZone; + +/** + * Overrides default json encoding of date time objects + * + * @author Menno Holtkamp + * @author Jordi Boggiano + */ +class JsonSerializableDateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable +{ + private bool $useMicroseconds; + + public function __construct(bool $useMicroseconds, ?DateTimeZone $timezone = null) + { + $this->useMicroseconds = $useMicroseconds; + + // if you like to use a custom time to pass to Logger::addRecord directly, + // call modify() or setTimestamp() on this instance to change the date after creating it + parent::__construct('now', $timezone); + } + + public function jsonSerialize(): string + { + if ($this->useMicroseconds) { + return $this->format('Y-m-d\TH:i:s.uP'); + } + + return $this->format('Y-m-d\TH:i:sP'); + } + + public function __toString(): string + { + return $this->jsonSerialize(); + } +} diff --git a/src/Monolog/Logger.php b/src/Monolog/Logger.php index f57fe332e..e545c4487 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -323,12 +323,13 @@ public function useLoggingLoopDetection(bool $detectCycles): self * @param int $level The logging level (a Monolog or RFC 5424 level) * @param string $message The log message * @param mixed[] $context The log context - * @param DateTimeImmutable|null $datetime Optional log date to log into the past or future + * @param JsonSerializableDateTimeImmutable|null $datetime Optional log date to log into the past or future + * * @return bool Whether the record has been processed * * @phpstan-param value-of|Level $level */ - public function addRecord(int|Level $level, string $message, array $context = [], DateTimeImmutable|null $datetime = null): bool + public function addRecord(int|Level $level, string $message, array $context = [], JsonSerializableDateTimeImmutable|null $datetime = null): bool { if (\is_int($level) && isset(self::RFC_5424_LEVELS[$level])) { $level = self::RFC_5424_LEVELS[$level]; @@ -356,7 +357,7 @@ public function addRecord(int|Level $level, string $message, array $context = [] $recordInitialized = \count($this->processors) === 0; $record = new LogRecord( - datetime: $datetime ?? new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + datetime: $datetime ?? new JsonSerializableDateTimeImmutable($this->microsecondTimestamps, $this->timezone), channel: $this->name, level: self::toMonologLevel($level), message: $message, @@ -518,7 +519,7 @@ public static function toMonologLevel(string|int|Level $level): Level public function isHandling(int|string|Level $level): bool { $record = new LogRecord( - datetime: new DateTimeImmutable($this->microsecondTimestamps, $this->timezone), + datetime: new JsonSerializableDateTimeImmutable($this->microsecondTimestamps, $this->timezone), channel: $this->name, message: '', level: self::toMonologLevel($level), diff --git a/src/Monolog/Processor/PsrLogMessageProcessor.php b/src/Monolog/Processor/PsrLogMessageProcessor.php index 76adf258f..291361d2f 100644 --- a/src/Monolog/Processor/PsrLogMessageProcessor.php +++ b/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -60,7 +60,7 @@ public function __invoke(LogRecord $record): LogRecord if (null === $val || \is_scalar($val) || (\is_object($val) && method_exists($val, "__toString"))) { $replacements[$placeholder] = $val; } elseif ($val instanceof \DateTimeInterface) { - if (null === $this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) { + if (null === $this->dateFormat && $val instanceof \Monolog\JsonSerializableDateTimeImmutable) { // handle monolog dates using __toString if no specific dateFormat was asked for // so that it follows the useMicroseconds flag $replacements[$placeholder] = (string) $val; diff --git a/src/Monolog/Test/TestCase.php b/src/Monolog/Test/TestCase.php index a2ff3f7ad..ec5751b76 100644 --- a/src/Monolog/Test/TestCase.php +++ b/src/Monolog/Test/TestCase.php @@ -14,7 +14,7 @@ use Monolog\Level; use Monolog\Logger; use Monolog\LogRecord; -use Monolog\DateTimeImmutable; +use Monolog\JsonSerializableDateTimeImmutable; use Monolog\Formatter\FormatterInterface; use Psr\Log\LogLevel; use ReflectionProperty; @@ -34,7 +34,7 @@ class TestCase extends \PHPUnit\Framework\TestCase * * @phpstan-param value-of|value-of|Level|LogLevel::* $level */ - protected function getRecord(int|string|Level $level = Level::Warning, string|\Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new DateTimeImmutable(true), array $extra = []): LogRecord + protected function getRecord(int|string|Level $level = Level::Warning, string|\Stringable $message = 'test', array $context = [], string $channel = 'test', \DateTimeImmutable $datetime = new JsonSerializableDateTimeImmutable(true), array $extra = []): LogRecord { return new LogRecord( message: (string) $message, diff --git a/tests/Monolog/Formatter/ScalarFormatterTest.php b/tests/Monolog/Formatter/ScalarFormatterTest.php index 2cdf68308..693d47da5 100644 --- a/tests/Monolog/Formatter/ScalarFormatterTest.php +++ b/tests/Monolog/Formatter/ScalarFormatterTest.php @@ -11,7 +11,7 @@ namespace Monolog\Formatter; -use Monolog\DateTimeImmutable; +use Monolog\JsonSerializableDateTimeImmutable; use Monolog\Test\TestCase; class ScalarFormatterTest extends TestCase @@ -57,7 +57,7 @@ public function testFormat() 'baz' => false, 'bam' => [1, 2, 3], 'bat' => ['foo' => 'bar'], - 'bap' => $dt = new DateTimeImmutable(true), + 'bap' => $dt = new JsonSerializableDateTimeImmutable(true), 'ban' => $exception, ])); diff --git a/tests/Monolog/LoggerTest.php b/tests/Monolog/LoggerTest.php index 67a5534ff..fab006220 100644 --- a/tests/Monolog/LoggerTest.php +++ b/tests/Monolog/LoggerTest.php @@ -556,7 +556,7 @@ function ($tz) { /** * @covers Logger::setTimezone - * @covers DateTimeImmutable::__construct + * @covers JsonSerializableDateTimeImmutable::__construct */ public function testTimezoneIsRespectedInUTC() { @@ -578,7 +578,7 @@ public function testTimezoneIsRespectedInUTC() /** * @covers Logger::setTimezone - * @covers DateTimeImmutable::__construct + * @covers JsonSerializableDateTimeImmutable::__construct */ public function testTimezoneIsRespectedInOtherTimezone() { @@ -807,7 +807,7 @@ public function testLogWithDateTime() $logger->pushHandler($loggingHandler); $logger->pushHandler($testHandler); - $datetime = (new DateTimeImmutable($microseconds))->modify('2022-03-04 05:06:07'); + $datetime = (new JsonSerializableDateTimeImmutable($microseconds))->modify('2022-03-04 05:06:07'); $logger->addRecord(Level::Debug, 'test', [], $datetime); list($record) = $testHandler->getRecords();