From bed7538dcdb50ab978ac2eabb5423b0a048e75bd Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 17:17:56 +0600 Subject: [PATCH 1/8] Move Target config to constructor params, deprecate setters --- CHANGELOG.md | 2 +- README.md | 8 +++- src/Message/Formatter.php | 19 +++++++++ src/PsrTarget.php | 22 ++++++++-- src/StreamTarget.php | 22 ++++++++-- src/Target.php | 68 ++++++++++++++++++++++++++---- tests/TargetTest.php | 75 +++++++++++++++++++++++++++++++++ tests/TestAsset/DummyTarget.php | 16 +++++-- 8 files changed, 210 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3c52de..8a99db7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.2.2 under development -- no changes in this release. +- Enh #XXX: Move `Target` configuration to constructor parameters and deprecate the corresponding setters (@WarLikeLaux) ## 2.2.1 March 22, 2026 diff --git a/README.md b/README.md index 38b915b5..44e33814 100644 --- a/README.md +++ b/README.md @@ -130,12 +130,16 @@ $logger->setFlushInterval(100); // default is 1000 Each log target also collects and stores messages in memory. Message exporting in a target follows the same principle as in the logger. -To change the number of stored messages, call the `\Yiisoft\Log\Target::setExportInterval()` method: +To change the number of stored messages, pass the `exportInterval` constructor parameter: ```php -$target->setExportInterval(100); // default is 1000 +$target = new \Yiisoft\Log\StreamTarget(exportInterval: 100); // default is 1000 ``` +> The `setExportInterval()` setter is deprecated since 2.2.2; use the constructor parameter instead. The same +> applies to `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, `setTimestampFormat()` +> and `setEnabled()`. + > Note: All message flushing and exporting also occurs when the application ends. ### Logging targets diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index 6563ff00..df282a88 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -46,6 +46,25 @@ final class Formatter */ private string $timestampFormat = 'Y-m-d H:i:s.u'; + /** + * @param callable|null $format A PHP callable that returns a string representation of the log message. + * Its signature should be `function (Message $message, array $commonContext): string;`. + * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. + * Its signature should be `function (Message $message, array $commonContext): string;`. + * @param string|null $timestampFormat The date format for the log timestamp. Defaults to `Y-m-d H:i:s.u`. + */ + public function __construct( + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + ) { + $this->format = $format; + $this->prefix = $prefix; + if ($timestampFormat !== null) { + $this->timestampFormat = $timestampFormat; + } + } + /** * Sets the format for the string representation of the log message. * diff --git a/src/PsrTarget.php b/src/PsrTarget.php index d4172dc2..fa19c1c1 100644 --- a/src/PsrTarget.php +++ b/src/PsrTarget.php @@ -17,10 +17,26 @@ final class PsrTarget extends Target * * @param LoggerInterface $logger The logger instance to be used for messages processing. * @param string[] $levels The {@see LogLevel log message levels} that this target is interested in. + * @param string[] $categories The log message categories that this target is interested in. + * @param string[] $except The log message categories that this target is NOT interested in. + * @param callable|null $format A PHP callable that returns a string representation of the log message. + * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. + * @param string|null $timestampFormat The date format for the log timestamp. + * @param int|null $exportInterval How many messages should be accumulated before they are exported. + * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ - public function __construct(private LoggerInterface $logger, array $levels = []) - { - parent::__construct($levels); + public function __construct( + private LoggerInterface $logger, + array $levels = [], + array $categories = [], + array $except = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + ?int $exportInterval = null, + bool|callable $enabled = true, + ) { + parent::__construct($levels, $categories, $except, $format, $prefix, $timestampFormat, $exportInterval, $enabled); } /** diff --git a/src/StreamTarget.php b/src/StreamTarget.php index e307018f..cca13512 100644 --- a/src/StreamTarget.php +++ b/src/StreamTarget.php @@ -31,10 +31,26 @@ final class StreamTarget extends Target /** * @param resource|string $stream A string stream identifier or a stream resource. * @param string[] $levels The {@see LogLevel log message levels} that this target is interested in. + * @param string[] $categories The log message categories that this target is interested in. + * @param string[] $except The log message categories that this target is NOT interested in. + * @param callable|null $format A PHP callable that returns a string representation of the log message. + * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. + * @param string|null $timestampFormat The date format for the log timestamp. + * @param int|null $exportInterval How many messages should be accumulated before they are exported. + * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ - public function __construct(private $stream = 'php://stdout', array $levels = []) - { - parent::__construct($levels); + public function __construct( + private $stream = 'php://stdout', + array $levels = [], + array $categories = [], + array $except = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + ?int $exportInterval = null, + bool|callable $enabled = true, + ) { + parent::__construct($levels, $categories, $except, $format, $prefix, $timestampFormat, $exportInterval, $enabled); } protected function export(): void diff --git a/src/Target.php b/src/Target.php index ceff0a86..1a6b081d 100644 --- a/src/Target.php +++ b/src/Target.php @@ -77,12 +77,36 @@ abstract class Target * When defining a constructor in child classes, you must call `parent::__construct()`. * * @param string[] $levels The {@see \Psr\Log\LogLevel log message levels} that this target is interested in. + * @param string[] $categories The log message categories that this target is interested in. + * @param string[] $except The log message categories that this target is NOT interested in. + * @param callable|null $format A PHP callable that returns a string representation of the log message. + * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. + * @param string|null $timestampFormat The date format for the log timestamp. + * @param int|null $exportInterval How many messages should be accumulated before they are exported. + * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ - public function __construct(array $levels = []) - { + public function __construct( + array $levels = [], + array $categories = [], + array $except = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + ?int $exportInterval = null, + bool|callable $enabled = true, + ) { $this->categories = new CategoryFilter(); - $this->formatter = new Formatter(); - $this->setLevels($levels); + $this->categories->include($categories); + $this->categories->exclude($except); + $this->formatter = new Formatter($format, $prefix, $timestampFormat); + $this->assertLevelsAreValid($levels); + $this->levels = $levels; + + if ($exportInterval !== null) { + $this->exportInterval = $exportInterval; + } + + $this->enabled = $enabled; } /** @@ -119,6 +143,8 @@ public function collect(array $messages, bool $final): void * @return self * * @see CategoryFilter::$include + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$categories` constructor parameter instead. */ public function setCategories(array $categories): self { @@ -136,6 +162,8 @@ public function setCategories(array $categories): self * @return self * * @see CategoryFilter::$exclude + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$except` constructor parameter instead. */ public function setExcept(array $except): self { @@ -153,14 +181,12 @@ public function setExcept(array $except): self * @return self * * @see Target::$levels + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$levels` constructor parameter instead. */ public function setLevels(array $levels): self { - foreach ($levels as $key => $level) { - Logger::assertLevelIsValid($level); - $levels[$key] = $level; - } - + $this->assertLevelsAreValid($levels); $this->levels = $levels; return $this; } @@ -190,6 +216,8 @@ public function setCommonContext(array $commonContext): self * @return self * * @see Formatter::$format + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$format` constructor parameter instead. */ public function setFormat(callable $format): self { @@ -205,6 +233,8 @@ public function setFormat(callable $format): self * @return self * * @see Formatter::$prefix + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$prefix` constructor parameter instead. */ public function setPrefix(callable $prefix): self { @@ -220,6 +250,8 @@ public function setPrefix(callable $prefix): self * @return self * * @see Target::$exportInterval + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$exportInterval` constructor parameter instead. */ public function setExportInterval(int $exportInterval): self { @@ -235,6 +267,8 @@ public function setExportInterval(int $exportInterval): self * @return self * * @see Target::$timestampFormat + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$timestampFormat` constructor parameter instead. */ public function setTimestampFormat(string $format): self { @@ -252,6 +286,8 @@ public function setTimestampFormat(string $format): self * @return self * * @see Target::$enabled + * + * @deprecated since 2.2.2, to be removed in 3.0. Use the `$enabled` constructor parameter instead. */ public function setEnabled(callable $value): self { @@ -372,6 +408,20 @@ protected function getCommonContext(): array return $this->commonContext; } + /** + * Asserts that every given log message level is valid. + * + * @param string[] $levels The list of log message levels. + * + * @throws InvalidArgumentException for invalid log message level. + */ + private function assertLevelsAreValid(array $levels): void + { + foreach ($levels as $level) { + Logger::assertLevelIsValid($level); + } + } + /** * Filters the given messages according to their categories and levels. * diff --git a/tests/TargetTest.php b/tests/TargetTest.php index 7b7867d5..14740a5f 100644 --- a/tests/TargetTest.php +++ b/tests/TargetTest.php @@ -346,6 +346,81 @@ public function testSetExportIntervalAndSetFormat(array $messages, bool $export) $this->assertSame((int) $export, $this->target->getExportCount()); } + public function testTimestampFormatViaConstructor(): void + { + $target = new DummyTarget(timestampFormat: 'Y-m-d H:i:s'); + $target->collect([new Message(LogLevel::INFO, 'message', ['category' => 'app', 'time' => 1_508_160_390])], true); + $expected = '2017-10-16 13:26:30 [info][app] message' + . "\n\nMessage context:\n\ncategory: 'app'\ntime: 1508160390\n"; + + $this->assertSame($expected, $target->formatMessages()); + } + + public function testExportIntervalViaConstructor(): void + { + $target = new DummyTarget(exportInterval: 1); + $target->collect([new Message(LogLevel::INFO, 'message', ['category' => 'app'])], false); + + $this->assertSame(1, $target->getExportCount()); + } + + public function testFormatViaConstructor(): void + { + $target = new DummyTarget(format: static fn(Message $message) => "[{$message->level()}] {$message->message()}"); + $target->collect([new Message(LogLevel::INFO, 'message', ['category' => 'app'])], true); + + $this->assertSame('[info] message', $target->formatMessages()); + } + + public function testPrefixViaConstructor(): void + { + $target = new DummyTarget(prefix: static fn(): string => 'PFX: ', timestampFormat: 'Y-m-d H:i:s'); + $target->collect([new Message(LogLevel::INFO, 'message', ['category' => 'app', 'time' => 1_508_160_390])], true); + $expected = '2017-10-16 13:26:30 PFX: [info][app] message' + . "\n\nMessage context:\n\ncategory: 'app'\ntime: 1508160390\n"; + + $this->assertSame($expected, $target->formatMessages()); + } + + public function testCategoriesViaConstructor(): void + { + $target = new DummyTarget(categories: ['app']); + $target->collect( + [ + new Message(LogLevel::INFO, 'in', ['category' => 'app']), + new Message(LogLevel::INFO, 'out', ['category' => 'other']), + ], + true, + ); + $messages = $target->getExportMessages(); + + $this->assertCount(1, $messages); + $this->assertSame('in', $messages[0]->message()); + } + + public function testExceptViaConstructor(): void + { + $target = new DummyTarget(except: ['app']); + $target->collect( + [ + new Message(LogLevel::INFO, 'in', ['category' => 'other']), + new Message(LogLevel::INFO, 'out', ['category' => 'app']), + ], + true, + ); + $messages = $target->getExportMessages(); + + $this->assertCount(1, $messages); + $this->assertSame('in', $messages[0]->message()); + } + + public function testEnabledViaConstructor(): void + { + $target = new DummyTarget(enabled: false); + + $this->assertFalse($target->isEnabled()); + } + public function contextProvider(): array { return [ diff --git a/tests/TestAsset/DummyTarget.php b/tests/TestAsset/DummyTarget.php index f311831f..d4ad0f23 100644 --- a/tests/TestAsset/DummyTarget.php +++ b/tests/TestAsset/DummyTarget.php @@ -14,10 +14,18 @@ final class DummyTarget extends Target private array $exportMessages = []; private Formatter $exportFormatter; - public function __construct(array $levels = []) - { - parent::__construct($levels); - $this->exportFormatter = new Formatter(); + public function __construct( + array $levels = [], + array $categories = [], + array $except = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + ?int $exportInterval = null, + bool|callable $enabled = true, + ) { + parent::__construct($levels, $categories, $except, $format, $prefix, $timestampFormat, $exportInterval, $enabled); + $this->exportFormatter = new Formatter($format, $prefix, $timestampFormat); } public function export(): void From 53f993c890de1e1c3a2778f3566ed71e1313f0c1 Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 17:27:56 +0600 Subject: [PATCH 2/8] Reference #146 in changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a99db7c..754ef600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.2.2 under development -- Enh #XXX: Move `Target` configuration to constructor parameters and deprecate the corresponding setters (@WarLikeLaux) +- Enh #146: Move `Target` configuration to constructor parameters and deprecate the corresponding setters (@WarLikeLaux) ## 2.2.1 March 22, 2026 From 685387a121f22ba20e34c5947378941b991e2abe Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 17:34:16 +0600 Subject: [PATCH 3/8] Update Russian guide for Target constructor parameters --- docs/guide/ru/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/guide/ru/README.md b/docs/guide/ru/README.md index 9a827529..91e60743 100644 --- a/docs/guide/ru/README.md +++ b/docs/guide/ru/README.md @@ -114,12 +114,16 @@ $logger->setFlushInterval(100); // по умолчанию 1000 ``` Каждый таргет тоже накапливает сообщения в памяти. Экспорт работает по тому же принципу. -Количество сообщений перед экспортом настраивается через `\Yiisoft\Log\Target::setExportInterval()`: +Количество сообщений перед экспортом задаётся параметром конструктора `exportInterval`: ```php -$target->setExportInterval(100); // по умолчанию 1000 +$target = new \Yiisoft\Log\StreamTarget(exportInterval: 100); // по умолчанию 1000 ``` +> Сеттер `setExportInterval()` объявлен устаревшим с версии 2.2.2, используйте вместо него параметр конструктора. +> То же касается `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, +> `setTimestampFormat()` и `setEnabled()`. + > Примечание: сброс и экспорт всех сообщений также происходит при завершении приложения. ### Таргеты логирования From f0cbf1f5c87cb94dc20db768b5332602ebbaf163 Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 17:58:35 +0600 Subject: [PATCH 4/8] Fix MD028 blank line between blockquotes in docs --- README.md | 6 +++--- docs/guide/ru/README.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 44e33814..a94f50e7 100644 --- a/README.md +++ b/README.md @@ -136,9 +136,9 @@ To change the number of stored messages, pass the `exportInterval` constructor p $target = new \Yiisoft\Log\StreamTarget(exportInterval: 100); // default is 1000 ``` -> The `setExportInterval()` setter is deprecated since 2.2.2; use the constructor parameter instead. The same -> applies to `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, `setTimestampFormat()` -> and `setEnabled()`. +The `setExportInterval()` setter is deprecated since 2.2.2; use the constructor parameter instead. The same +applies to `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, `setTimestampFormat()` +and `setEnabled()`. > Note: All message flushing and exporting also occurs when the application ends. diff --git a/docs/guide/ru/README.md b/docs/guide/ru/README.md index 91e60743..31e8b8a0 100644 --- a/docs/guide/ru/README.md +++ b/docs/guide/ru/README.md @@ -120,9 +120,9 @@ $logger->setFlushInterval(100); // по умолчанию 1000 $target = new \Yiisoft\Log\StreamTarget(exportInterval: 100); // по умолчанию 1000 ``` -> Сеттер `setExportInterval()` объявлен устаревшим с версии 2.2.2, используйте вместо него параметр конструктора. -> То же касается `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, -> `setTimestampFormat()` и `setEnabled()`. +Сеттер `setExportInterval()` объявлен устаревшим с версии 2.2.2, используйте вместо него параметр конструктора. +То же касается `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, +`setTimestampFormat()` и `setEnabled()`. > Примечание: сброс и экспорт всех сообщений также происходит при завершении приложения. From d56cc4767d78039b213e0194977765e99010b7da Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 21:27:27 +0600 Subject: [PATCH 5/8] Apply review fixes: promotion, renames, default constants --- README.md | 2 +- docs/guide/ru/README.md | 2 +- src/Message/CategoryFilter.php | 12 ++++++ src/Message/Formatter.php | 42 ++++--------------- src/PsrTarget.php | 10 ++--- src/StreamTarget.php | 10 ++--- src/Target.php | 72 +++++++++------------------------ tests/TargetTest.php | 2 +- tests/TestAsset/DummyTarget.php | 6 +-- 9 files changed, 56 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index a94f50e7..efa44e50 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ To change the number of stored messages, pass the `exportInterval` constructor p $target = new \Yiisoft\Log\StreamTarget(exportInterval: 100); // default is 1000 ``` -The `setExportInterval()` setter is deprecated since 2.2.2; use the constructor parameter instead. The same +The `setExportInterval()` setter is deprecated; use the constructor parameter instead. The same applies to `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, `setTimestampFormat()` and `setEnabled()`. diff --git a/docs/guide/ru/README.md b/docs/guide/ru/README.md index 31e8b8a0..8fc236a9 100644 --- a/docs/guide/ru/README.md +++ b/docs/guide/ru/README.md @@ -120,7 +120,7 @@ $logger->setFlushInterval(100); // по умолчанию 1000 $target = new \Yiisoft\Log\StreamTarget(exportInterval: 100); // по умолчанию 1000 ``` -Сеттер `setExportInterval()` объявлен устаревшим с версии 2.2.2, используйте вместо него параметр конструктора. +Сеттер `setExportInterval()` объявлен устаревшим, используйте вместо него параметр конструктора. То же касается `setCategories()`, `setExcept()`, `setLevels()`, `setFormat()`, `setPrefix()`, `setTimestampFormat()` и `setEnabled()`. diff --git a/src/Message/CategoryFilter.php b/src/Message/CategoryFilter.php index 83da72e1..13a6ec79 100644 --- a/src/Message/CategoryFilter.php +++ b/src/Message/CategoryFilter.php @@ -45,6 +45,18 @@ final class CategoryFilter */ private array $exclude = []; + /** + * @param string[] $include The list of included log message categories. + * @param string[] $exclude The list of excluded log message categories. + * + * @throws InvalidArgumentException for invalid log message categories structure. + */ + public function __construct(array $include = [], array $exclude = []) + { + $this->include($include); + $this->exclude($exclude); + } + /** * Sets the log message categories to be included. * diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index df282a88..b939873f 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -22,48 +22,22 @@ */ final class Formatter { - /** - * @var callable|null PHP callable that returns a string representation of the log message. - * - * If not set, {@see Formatter::defaultFormat()} will be used. - * - * The signature of the callable should be `function (Message $message, array $commonContext): string;`. - */ - private $format; - - /** - * @var callable|null PHP callable that returns a string to be prefixed to every exported message. - * - * If not set, {@see Formatter::getPrefix()} will be used, which prefixes - * the message with context information such as user IP, user ID and session ID. - * - * The signature of the callable should be `function (Message $message, array $commonContext): string;`. - */ - private $prefix; - - /** - * @var string The date format for the log timestamp. Defaults to `Y-m-d H:i:s.u`. - */ - private string $timestampFormat = 'Y-m-d H:i:s.u'; + private const DEFAULT_TIMESTAMP_FORMAT = 'Y-m-d H:i:s.u'; /** * @param callable|null $format A PHP callable that returns a string representation of the log message. + * If not set, {@see Formatter::defaultFormat()} is used. * Its signature should be `function (Message $message, array $commonContext): string;`. * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. + * If not set, {@see Formatter::getPrefix()} is used. * Its signature should be `function (Message $message, array $commonContext): string;`. * @param string|null $timestampFormat The date format for the log timestamp. Defaults to `Y-m-d H:i:s.u`. */ public function __construct( - ?callable $format = null, - ?callable $prefix = null, - ?string $timestampFormat = null, - ) { - $this->format = $format; - $this->prefix = $prefix; - if ($timestampFormat !== null) { - $this->timestampFormat = $timestampFormat; - } - } + private $format = null, + private $prefix = null, + private ?string $timestampFormat = null, + ) {} /** * Sets the format for the string representation of the log message. @@ -139,7 +113,7 @@ public function format(Message $message, array $commonContext): string */ private function defaultFormat(Message $message, array $commonContext): string { - $time = $message->time()->format($this->timestampFormat); + $time = $message->time()->format($this->timestampFormat ?? self::DEFAULT_TIMESTAMP_FORMAT); $prefix = $this->getPrefix($message, $commonContext); $context = $this->getContext($message, $commonContext); diff --git a/src/PsrTarget.php b/src/PsrTarget.php index fa19c1c1..145f71ae 100644 --- a/src/PsrTarget.php +++ b/src/PsrTarget.php @@ -18,25 +18,25 @@ final class PsrTarget extends Target * @param LoggerInterface $logger The logger instance to be used for messages processing. * @param string[] $levels The {@see LogLevel log message levels} that this target is interested in. * @param string[] $categories The log message categories that this target is interested in. - * @param string[] $except The log message categories that this target is NOT interested in. + * @param string[] $exceptCategories The log message categories that this target is NOT interested in. * @param callable|null $format A PHP callable that returns a string representation of the log message. * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. * @param string|null $timestampFormat The date format for the log timestamp. - * @param int|null $exportInterval How many messages should be accumulated before they are exported. + * @param int $exportInterval How many messages should be accumulated before they are exported. * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ public function __construct( private LoggerInterface $logger, array $levels = [], array $categories = [], - array $except = [], + array $exceptCategories = [], ?callable $format = null, ?callable $prefix = null, ?string $timestampFormat = null, - ?int $exportInterval = null, + int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, bool|callable $enabled = true, ) { - parent::__construct($levels, $categories, $except, $format, $prefix, $timestampFormat, $exportInterval, $enabled); + parent::__construct($levels, $categories, $exceptCategories, $format, $prefix, $timestampFormat, $exportInterval, $enabled); } /** diff --git a/src/StreamTarget.php b/src/StreamTarget.php index cca13512..b27856dd 100644 --- a/src/StreamTarget.php +++ b/src/StreamTarget.php @@ -32,25 +32,25 @@ final class StreamTarget extends Target * @param resource|string $stream A string stream identifier or a stream resource. * @param string[] $levels The {@see LogLevel log message levels} that this target is interested in. * @param string[] $categories The log message categories that this target is interested in. - * @param string[] $except The log message categories that this target is NOT interested in. + * @param string[] $exceptCategories The log message categories that this target is NOT interested in. * @param callable|null $format A PHP callable that returns a string representation of the log message. * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. * @param string|null $timestampFormat The date format for the log timestamp. - * @param int|null $exportInterval How many messages should be accumulated before they are exported. + * @param int $exportInterval How many messages should be accumulated before they are exported. * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ public function __construct( private $stream = 'php://stdout', array $levels = [], array $categories = [], - array $except = [], + array $exceptCategories = [], ?callable $format = null, ?callable $prefix = null, ?string $timestampFormat = null, - ?int $exportInterval = null, + int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, bool|callable $enabled = true, ) { - parent::__construct($levels, $categories, $except, $format, $prefix, $timestampFormat, $exportInterval, $enabled); + parent::__construct($levels, $categories, $exceptCategories, $format, $prefix, $timestampFormat, $exportInterval, $enabled); } protected function export(): void diff --git a/src/Target.php b/src/Target.php index 1a6b081d..d0bbeed9 100644 --- a/src/Target.php +++ b/src/Target.php @@ -9,6 +9,7 @@ use Yiisoft\Log\ContextProvider\CommonContextProvider; use Yiisoft\Log\Message\CategoryFilter; use Yiisoft\Log\Message\Formatter; +use Psr\Log\LogLevel; use function count; use function in_array; @@ -28,6 +29,8 @@ */ abstract class Target { + public const DEFAULT_EXPORT_INTERVAL = 1000; + private CategoryFilter $categories; private Formatter $formatter; @@ -36,38 +39,11 @@ abstract class Target */ private array $messages = []; - /** - * @var string[] The log message levels that this target is interested in. - * - * @see LogLevel See constants for valid level names. - * - * The value should be an array of level names. - * - * For example: - * - * ```php - * ['error', 'warning'], - * // or - * [LogLevel::ERROR, LogLevel::WARNING] - * ``` - * - * Defaults is empty array, meaning all available levels. - */ - private array $levels = []; - /** * @var array The user parameters in the `key => value` format that should be logged in a each message. */ private array $commonContext = []; - /** - * @var int How many log messages should be accumulated before they are exported. - * - * Defaults to 1000. Note that messages will always be exported when the application terminates. - * Set this property to be 0 if you don't want to export messages until the application terminates. - */ - private int $exportInterval = 1000; - /** * @var bool|callable Enables or disables the current target to export. */ @@ -76,36 +52,28 @@ abstract class Target /** * When defining a constructor in child classes, you must call `parent::__construct()`. * - * @param string[] $levels The {@see \Psr\Log\LogLevel log message levels} that this target is interested in. + * @param string[] $levels The {@see LogLevel log message levels} that this target is interested in. * @param string[] $categories The log message categories that this target is interested in. - * @param string[] $except The log message categories that this target is NOT interested in. + * @param string[] $exceptCategories The log message categories that this target is NOT interested in. * @param callable|null $format A PHP callable that returns a string representation of the log message. * @param callable|null $prefix A PHP callable that returns a string to be prefixed to every exported message. * @param string|null $timestampFormat The date format for the log timestamp. - * @param int|null $exportInterval How many messages should be accumulated before they are exported. + * @param int $exportInterval How many messages should be accumulated before they are exported. * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ public function __construct( - array $levels = [], + private array $levels = [], array $categories = [], - array $except = [], + array $exceptCategories = [], ?callable $format = null, ?callable $prefix = null, ?string $timestampFormat = null, - ?int $exportInterval = null, + private int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, bool|callable $enabled = true, ) { - $this->categories = new CategoryFilter(); - $this->categories->include($categories); - $this->categories->exclude($except); + $this->assertLevelsAreValid($this->levels); + $this->categories = new CategoryFilter($categories, $exceptCategories); $this->formatter = new Formatter($format, $prefix, $timestampFormat); - $this->assertLevelsAreValid($levels); - $this->levels = $levels; - - if ($exportInterval !== null) { - $this->exportInterval = $exportInterval; - } - $this->enabled = $enabled; } @@ -144,7 +112,7 @@ public function collect(array $messages, bool $final): void * * @see CategoryFilter::$include * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$categories` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$categories` constructor parameter instead. */ public function setCategories(array $categories): self { @@ -163,7 +131,7 @@ public function setCategories(array $categories): self * * @see CategoryFilter::$exclude * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$except` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$exceptCategories` constructor parameter instead. */ public function setExcept(array $except): self { @@ -172,7 +140,7 @@ public function setExcept(array $except): self } /** - * Sets a list of {@see \Psr\Log\LogLevel log message levels} that current target is interested in. + * Sets a list of {@see LogLevel log message levels} that current target is interested in. * * @param string[] $levels The list of log message levels. * @@ -182,7 +150,7 @@ public function setExcept(array $except): self * * @see Target::$levels * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$levels` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$levels` constructor parameter instead. */ public function setLevels(array $levels): self { @@ -217,7 +185,7 @@ public function setCommonContext(array $commonContext): self * * @see Formatter::$format * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$format` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$format` constructor parameter instead. */ public function setFormat(callable $format): self { @@ -234,7 +202,7 @@ public function setFormat(callable $format): self * * @see Formatter::$prefix * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$prefix` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$prefix` constructor parameter instead. */ public function setPrefix(callable $prefix): self { @@ -251,7 +219,7 @@ public function setPrefix(callable $prefix): self * * @see Target::$exportInterval * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$exportInterval` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$exportInterval` constructor parameter instead. */ public function setExportInterval(int $exportInterval): self { @@ -268,7 +236,7 @@ public function setExportInterval(int $exportInterval): self * * @see Target::$timestampFormat * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$timestampFormat` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$timestampFormat` constructor parameter instead. */ public function setTimestampFormat(string $format): self { @@ -287,7 +255,7 @@ public function setTimestampFormat(string $format): self * * @see Target::$enabled * - * @deprecated since 2.2.2, to be removed in 3.0. Use the `$enabled` constructor parameter instead. + * @deprecated To be removed in 3.0. Use the `$enabled` constructor parameter instead. */ public function setEnabled(callable $value): self { diff --git a/tests/TargetTest.php b/tests/TargetTest.php index 14740a5f..c78e1bf1 100644 --- a/tests/TargetTest.php +++ b/tests/TargetTest.php @@ -400,7 +400,7 @@ public function testCategoriesViaConstructor(): void public function testExceptViaConstructor(): void { - $target = new DummyTarget(except: ['app']); + $target = new DummyTarget(exceptCategories: ['app']); $target->collect( [ new Message(LogLevel::INFO, 'in', ['category' => 'other']), diff --git a/tests/TestAsset/DummyTarget.php b/tests/TestAsset/DummyTarget.php index d4ad0f23..9bc7aee6 100644 --- a/tests/TestAsset/DummyTarget.php +++ b/tests/TestAsset/DummyTarget.php @@ -17,14 +17,14 @@ final class DummyTarget extends Target public function __construct( array $levels = [], array $categories = [], - array $except = [], + array $exceptCategories = [], ?callable $format = null, ?callable $prefix = null, ?string $timestampFormat = null, - ?int $exportInterval = null, + int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, bool|callable $enabled = true, ) { - parent::__construct($levels, $categories, $except, $format, $prefix, $timestampFormat, $exportInterval, $enabled); + parent::__construct($levels, $categories, $exceptCategories, $format, $prefix, $timestampFormat, $exportInterval, $enabled); $this->exportFormatter = new Formatter($format, $prefix, $timestampFormat); } From 5188bb9b96b8036da1ec749980ec5a20ce7b0dba Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 21:32:12 +0600 Subject: [PATCH 6/8] Declare types for default constants to satisfy psalm --- src/Message/Formatter.php | 3 +++ src/Target.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index b939873f..b59b2ecc 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -22,6 +22,9 @@ */ final class Formatter { + /** + * @var string + */ private const DEFAULT_TIMESTAMP_FORMAT = 'Y-m-d H:i:s.u'; /** diff --git a/src/Target.php b/src/Target.php index d0bbeed9..bbec2a7e 100644 --- a/src/Target.php +++ b/src/Target.php @@ -29,6 +29,9 @@ */ abstract class Target { + /** + * @var int + */ public const DEFAULT_EXPORT_INTERVAL = 1000; private CategoryFilter $categories; From 8995795b83e9a357ca32e5b4c83f3d39baebadc7 Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Sun, 31 May 2026 21:42:01 +0600 Subject: [PATCH 7/8] Suppress MissingClassConstType on default constant --- src/Message/Formatter.php | 3 --- src/Target.php | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index b59b2ecc..b939873f 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -22,9 +22,6 @@ */ final class Formatter { - /** - * @var string - */ private const DEFAULT_TIMESTAMP_FORMAT = 'Y-m-d H:i:s.u'; /** diff --git a/src/Target.php b/src/Target.php index bbec2a7e..5badbdd2 100644 --- a/src/Target.php +++ b/src/Target.php @@ -30,7 +30,7 @@ abstract class Target { /** - * @var int + * @psalm-suppress MissingClassConstType */ public const DEFAULT_EXPORT_INTERVAL = 1000; From b2b18f5b8a84f3594d3d1d60bcbec1aa7caaa585 Mon Sep 17 00:00:00 2001 From: WarLikeLaux Date: Mon, 1 Jun 2026 23:57:13 +0600 Subject: [PATCH 8/8] Call setLevels() from constructor to preserve overrides --- src/Target.php | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Target.php b/src/Target.php index 5badbdd2..43279a0a 100644 --- a/src/Target.php +++ b/src/Target.php @@ -42,6 +42,25 @@ abstract class Target */ private array $messages = []; + /** + * @var string[] The log message levels that this target is interested in. + * + * @see LogLevel See constants for valid level names. + * + * The value should be an array of level names. + * + * For example: + * + * ```php + * ['error', 'warning'], + * // or + * [LogLevel::ERROR, LogLevel::WARNING] + * ``` + * + * Defaults is empty array, meaning all available levels. + */ + private array $levels = []; + /** * @var array The user parameters in the `key => value` format that should be logged in a each message. */ @@ -65,7 +84,7 @@ abstract class Target * @param bool|callable $enabled Whether this target is enabled, or a PHP callable that returns a boolean. */ public function __construct( - private array $levels = [], + array $levels = [], array $categories = [], array $exceptCategories = [], ?callable $format = null, @@ -74,9 +93,10 @@ public function __construct( private int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, bool|callable $enabled = true, ) { - $this->assertLevelsAreValid($this->levels); $this->categories = new CategoryFilter($categories, $exceptCategories); $this->formatter = new Formatter($format, $prefix, $timestampFormat); + /** @psalm-suppress DeprecatedMethod */ + $this->setLevels($levels); $this->enabled = $enabled; } @@ -157,7 +177,10 @@ public function setExcept(array $except): self */ public function setLevels(array $levels): self { - $this->assertLevelsAreValid($levels); + foreach ($levels as $level) { + Logger::assertLevelIsValid($level); + } + $this->levels = $levels; return $this; } @@ -379,20 +402,6 @@ protected function getCommonContext(): array return $this->commonContext; } - /** - * Asserts that every given log message level is valid. - * - * @param string[] $levels The list of log message levels. - * - * @throws InvalidArgumentException for invalid log message level. - */ - private function assertLevelsAreValid(array $levels): void - { - foreach ($levels as $level) { - Logger::assertLevelIsValid($level); - } - } - /** * Filters the given messages according to their categories and levels. *