From ac5dfcee736cd78cc2b08cfde2be81579362a3a5 Mon Sep 17 00:00:00 2001 From: Daniel Gurev Date: Thu, 14 Jul 2022 00:00:24 +0300 Subject: [PATCH 1/5] Increase psalm error level to 1 --- psalm.xml | 2 +- src/Logger.php | 19 +++++++++++++++++-- src/Message.php | 31 ++++++++++++++++++++++++++----- src/Message/Formatter.php | 24 +++++++++++++++++++----- src/PsrTarget.php | 4 +++- src/Target.php | 11 +++++++---- 6 files changed, 73 insertions(+), 18 deletions(-) diff --git a/psalm.xml b/psalm.xml index 4fa758186..deb39dc13 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ targets; } + /** + * @param string $level + * @param string|Stringable $message + * @param array $context + * @psalm-param LogMessageContext $context + * @psalm-suppress MoreSpecificImplementedParamType + * + * @return void + */ public function log(mixed $level, string|Stringable $message, array $context = []): void { $context['time'] ??= microtime(true); @@ -205,7 +217,7 @@ public function setTraceLevel(int $traceLevel): self /** * Sets an array of paths to exclude from tracing when tracing is enabled with {@see Logger::setTraceLevel()}. * - * @param array $excludedTracePaths The paths to exclude from tracing. + * @param string[] $excludedTracePaths The paths to exclude from tracing. * * @throws InvalidArgumentException for non-string values. * @@ -216,6 +228,7 @@ public function setTraceLevel(int $traceLevel): self public function setExcludedTracePaths(array $excludedTracePaths): self { foreach ($excludedTracePaths as $excludedTracePath) { + /** @psalm-suppress DocblockTypeContradiction */ if (!is_string($excludedTracePath)) { throw new InvalidArgumentException(sprintf( 'The trace path must be a string, %s received.', @@ -231,7 +244,7 @@ public function setExcludedTracePaths(array $excludedTracePaths): self /** * Sets a target to {@see Logger::$targets}. * - * @param array $targets The log targets. Each array element represents a single {@see \Yiisoft\Log\Target} + * @param Target[] $targets The log targets. Each array element represents a single {@see \Yiisoft\Log\Target} * instance or the configuration for creating the log target instance. * * @throws InvalidArgumentException for non-instance Target. @@ -281,8 +294,10 @@ private function dispatch(array $messages, bool $final): void * Collects a trace when tracing is enabled with {@see Logger::setTraceLevel()}. * * @param array $backtrace The list of call stack information. + * @psalm-param Backtrace $backtrace * * @return array Collected a list of call stack information. + * @psalm-return Backtrace */ private function collectTrace(array $backtrace): array { diff --git a/src/Message.php b/src/Message.php index 19212699d..cab1a058b 100644 --- a/src/Message.php +++ b/src/Message.php @@ -13,6 +13,22 @@ /** * Message is a data object that stores log message data. + * + * @psalm-type Backtrace = list + * @psalm-type LogMessageContext = array{ + * category?:string, + * memory?:int, + * time?:float, + * trace?:Backtrace, + * }&array */ final class Message { @@ -29,7 +45,8 @@ final class Message private string $message; /** - * @var array Log message context. + * @var array Log message context. + * @psalm-var LogMessageContext * * Message context has a following keys: * @@ -41,16 +58,17 @@ final class Message private array $context; /** - * @param mixed $level Log message level. + * @param string $level Log message level. * @param string|Stringable $message Log message. - * @param array $context Log message context. + * @param array $context Log message context. + * @psalm-param LogMessageContext $context * * @throws InvalidArgumentException for invalid log message level. * * @see LoggerTrait::log() * @see LogLevel */ - public function __construct(mixed $level, string|Stringable $message, array $context = []) + public function __construct(string $level, string|Stringable $message, array $context = []) { $this->level = Logger::validateLevel($level); $this->message = $this->parse($message, $context); @@ -86,6 +104,7 @@ public function message(): string * @param mixed $default If the context parameter does not exist, the `$default` will be returned. * * @return mixed The context parameter value. + * @psalm-return LogMessageContext|mixed */ public function context(string $name = null, mixed $default = null): mixed { @@ -101,7 +120,8 @@ public function context(string $name = null, mixed $default = null): mixed * where foo will be replaced by the context data in key "foo". * * @param string|Stringable $message Raw log message. - * @param array $context Message context. + * @param array $context Message context. + * @psalm-param LogMessageContext $context * * @return string Parsed message. */ @@ -113,6 +133,7 @@ private function parse(string|Stringable $message, array $context): string $placeholderName = $matches[1]; if (isset($context[$placeholderName])) { + /** @psalm-suppress PossiblyInvalidCast */ return (string) $context[$placeholderName]; } diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index fb34f27c3..af2267703 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -21,6 +21,8 @@ * Formatter formats log messages. * * @internal + * + * @psalm-import-type Backtrace from Message */ final class Formatter { @@ -125,6 +127,7 @@ private function defaultFormat(Message $message, array $commonContext): string $time = $this->getTime($message); $prefix = $this->getPrefix($message, $commonContext); $context = $this->getContext($message, $commonContext); + /** @var string $category */ $category = $message->context('category', CategoryFilter::DEFAULT); return "{$time} {$prefix}[{$message->level()}][{$category}] {$message->message()}{$context}"; @@ -139,15 +142,16 @@ private function defaultFormat(Message $message, array $commonContext): string */ private function getTime(Message $message): string { - $timestamp = (string) $message->context('time', microtime(true)); + /** @var float $timestamp */ + $timestamp = $message->context('time', microtime(true)); $format = match (true) { - str_contains($timestamp, '.') => 'U.u', - str_contains($timestamp, ',') => 'U,u', + str_contains((string) $timestamp, '.') => 'U.u', + str_contains((string) $timestamp, ',') => 'U,u', default => 'U', }; - return DateTime::createFromFormat($format, $timestamp)->format($this->timestampFormat); + return DateTime::createFromFormat($format, (string) $timestamp)->format($this->timestampFormat); } /** @@ -199,12 +203,19 @@ private function getContext(Message $message, array $commonContext): string $context[] = $trace; } + /** + * @var array-key $name + * @var mixed $value + */ foreach ($message->context() as $name => $value) { if ($name !== 'trace') { $context[] = "{$name}: " . $this->convertToString($value); } } + /** + * @var mixed $value + */ foreach ($commonContext as $name => $value) { $common[] = "{$name}: " . $this->convertToString($value); } @@ -222,7 +233,8 @@ private function getContext(Message $message, array $commonContext): string */ private function getTrace(Message $message): string { - $traces = (array) $message->context('trace', []); + /** @psalm-var Backtrace $traces */ + $traces = $message->context('trace', []); foreach ($traces as $key => $trace) { if (isset($trace['file'], $trace['line'])) { @@ -230,6 +242,7 @@ private function getTrace(Message $message): string } } + /** @var string[] $traces */ return empty($traces) ? '' : "trace:\n " . implode("\n ", $traces); } @@ -243,6 +256,7 @@ private function getTrace(Message $message): string private function convertToString(mixed $value): string { if (is_object($value) && method_exists($value, '__toString')) { + /** @var string */ return $value->__toString(); } diff --git a/src/PsrTarget.php b/src/PsrTarget.php index 7bc078436..58dcc36f1 100644 --- a/src/PsrTarget.php +++ b/src/PsrTarget.php @@ -38,7 +38,9 @@ public function getLogger(): LoggerInterface protected function export(): void { foreach ($this->getMessages() as $message) { - $this->logger->log($message->level(), $message->message(), $message->context()); + /** @var array $context */ + $context = $message->context(); + $this->logger->log($message->level(), $message->message(), $context); } } } diff --git a/src/Target.php b/src/Target.php index 1be188b37..b64ff265f 100644 --- a/src/Target.php +++ b/src/Target.php @@ -115,7 +115,7 @@ public function collect(array $messages, bool $final): void /** * Sets a list of log message categories that this target is interested in. * - * @param array $categories The list of log message categories. + * @param string[] $categories The list of log message categories. * * @throws InvalidArgumentException for invalid log message categories structure. * @@ -132,7 +132,7 @@ public function setCategories(array $categories): self /** * Sets a list of log message categories that this target is NOT interested in. * - * @param array $except The list of log message categories. + * @param string[] $except The list of log message categories. * * @throws InvalidArgumentException for invalid log message categories structure. * @@ -149,7 +149,7 @@ public function setExcept(array $except): self /** * Sets a list of log message levels that current target is interested in. * - * @param array $levels The list of log message levels. + * @param string[] $levels The list of log message levels. * * @throws InvalidArgumentException for invalid log message level. * @@ -383,7 +383,10 @@ private function filterMessages(array $messages): void continue; } - if ($this->categories->isExcluded($message->context('category', ''))) { + /** @var string $category */ + $category = $message->context('category', ''); + + if ($this->categories->isExcluded($category)) { unset($messages[$i]); continue; } From d20be237f410cb35f30639afad5e85e1d5b0efc3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 14 Jul 2022 15:57:43 +0300 Subject: [PATCH 2/5] Update src/Message/Formatter.php Co-authored-by: Sergei Predvoditelev --- src/Message/Formatter.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index af2267703..6c56b5f01 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -256,8 +256,7 @@ private function getTrace(Message $message): string private function convertToString(mixed $value): string { if (is_object($value) && method_exists($value, '__toString')) { - /** @var string */ - return $value->__toString(); + return (string) $value; } return VarDumper::create($value)->asString(); From cc3367080f202fa22cd7fb6ae358016353f2344a Mon Sep 17 00:00:00 2001 From: Daniel Gurev Date: Thu, 14 Jul 2022 21:50:45 +0300 Subject: [PATCH 3/5] Remove "args" and "object" keys from the Message Backtrace type --- src/Logger.php | 2 +- src/Message.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Logger.php b/src/Logger.php index 8aaf7b220..a1b485755 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -294,7 +294,7 @@ private function dispatch(array $messages, bool $final): void * Collects a trace when tracing is enabled with {@see Logger::setTraceLevel()}. * * @param array $backtrace The list of call stack information. - * @psalm-param Backtrace $backtrace + * @psalm-param Backtrace|list $backtrace * * @return array Collected a list of call stack information. * @psalm-return Backtrace diff --git a/src/Message.php b/src/Message.php index cab1a058b..d1e49bd3e 100644 --- a/src/Message.php +++ b/src/Message.php @@ -20,8 +20,6 @@ * function?:string, * class?:string, * type?:string, - * args?:array, - * object?:object, * }> * @psalm-type LogMessageContext = array{ * category?:string, From 73c2f0052e71ff20abb6863197407d1b8514f594 Mon Sep 17 00:00:00 2001 From: Daniel Gurev Date: Thu, 14 Jul 2022 21:59:42 +0300 Subject: [PATCH 4/5] Update Formatter --- src/Message/Formatter.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index 6c56b5f01..bc7d337d1 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -142,16 +142,16 @@ private function defaultFormat(Message $message, array $commonContext): string */ private function getTime(Message $message): string { - /** @var float $timestamp */ - $timestamp = $message->context('time', microtime(true)); + /** @psalm-suppress PossiblyInvalidCast */ + $timestamp = (string) $message->context('time', microtime(true)); $format = match (true) { - str_contains((string) $timestamp, '.') => 'U.u', - str_contains((string) $timestamp, ',') => 'U,u', + str_contains($timestamp, '.') => 'U.u', + str_contains($timestamp, ',') => 'U,u', default => 'U', }; - return DateTime::createFromFormat($format, (string) $timestamp)->format($this->timestampFormat); + return DateTime::createFromFormat($format, $timestamp)->format($this->timestampFormat); } /** From 8894b29b0c4a0320a1008688935b502c109922f4 Mon Sep 17 00:00:00 2001 From: Daniel Gurev Date: Thu, 14 Jul 2022 22:27:42 +0300 Subject: [PATCH 5/5] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 049eca312..e80fb1d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.0.1 under development -- no changes in this release. +- Bug #84: Change the type of the `$level` parameter in the `Message` constructor to `string` (dood-) ## 2.0.0 May 22, 2022