diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c3c52de..754ef600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## 2.2.2 under development -- no changes in this release. +- Enh #146: 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..efa44e50 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; 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/docs/guide/ru/README.md b/docs/guide/ru/README.md index 9a827529..8fc236a9 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()` объявлен устаревшим, используйте вместо него параметр конструктора. +То же касается `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 6563ff00..b939873f 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -22,29 +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; + private const DEFAULT_TIMESTAMP_FORMAT = 'Y-m-d H:i:s.u'; /** - * @var string The date format for the log timestamp. Defaults to `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`. */ - private string $timestampFormat = 'Y-m-d H:i:s.u'; + public function __construct( + private $format = null, + private $prefix = null, + private ?string $timestampFormat = null, + ) {} /** * Sets the format for the string representation of the log message. @@ -120,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 d4172dc2..145f71ae 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[] $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 $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 $exceptCategories = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, + bool|callable $enabled = true, + ) { + parent::__construct($levels, $categories, $exceptCategories, $format, $prefix, $timestampFormat, $exportInterval, $enabled); } /** diff --git a/src/StreamTarget.php b/src/StreamTarget.php index e307018f..b27856dd 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[] $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 $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 $exceptCategories = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, + bool|callable $enabled = true, + ) { + 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 ceff0a86..43279a0a 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,11 @@ */ abstract class Target { + /** + * @psalm-suppress MissingClassConstType + */ + public const DEFAULT_EXPORT_INTERVAL = 1000; + private CategoryFilter $categories; private Formatter $formatter; @@ -60,14 +66,6 @@ abstract class Target */ 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,13 +74,30 @@ 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[] $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 $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 = []) - { - $this->categories = new CategoryFilter(); - $this->formatter = new Formatter(); + public function __construct( + array $levels = [], + array $categories = [], + array $exceptCategories = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + private int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, + bool|callable $enabled = true, + ) { + $this->categories = new CategoryFilter($categories, $exceptCategories); + $this->formatter = new Formatter($format, $prefix, $timestampFormat); + /** @psalm-suppress DeprecatedMethod */ $this->setLevels($levels); + $this->enabled = $enabled; } /** @@ -119,6 +134,8 @@ public function collect(array $messages, bool $final): void * @return self * * @see CategoryFilter::$include + * + * @deprecated To be removed in 3.0. Use the `$categories` constructor parameter instead. */ public function setCategories(array $categories): self { @@ -136,6 +153,8 @@ public function setCategories(array $categories): self * @return self * * @see CategoryFilter::$exclude + * + * @deprecated To be removed in 3.0. Use the `$exceptCategories` constructor parameter instead. */ public function setExcept(array $except): self { @@ -144,7 +163,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. * @@ -153,12 +172,13 @@ public function setExcept(array $except): self * @return self * * @see Target::$levels + * + * @deprecated To be removed in 3.0. Use the `$levels` constructor parameter instead. */ public function setLevels(array $levels): self { - foreach ($levels as $key => $level) { + foreach ($levels as $level) { Logger::assertLevelIsValid($level); - $levels[$key] = $level; } $this->levels = $levels; @@ -190,6 +210,8 @@ public function setCommonContext(array $commonContext): self * @return self * * @see Formatter::$format + * + * @deprecated To be removed in 3.0. Use the `$format` constructor parameter instead. */ public function setFormat(callable $format): self { @@ -205,6 +227,8 @@ public function setFormat(callable $format): self * @return self * * @see Formatter::$prefix + * + * @deprecated To be removed in 3.0. Use the `$prefix` constructor parameter instead. */ public function setPrefix(callable $prefix): self { @@ -220,6 +244,8 @@ public function setPrefix(callable $prefix): self * @return self * * @see Target::$exportInterval + * + * @deprecated To be removed in 3.0. Use the `$exportInterval` constructor parameter instead. */ public function setExportInterval(int $exportInterval): self { @@ -235,6 +261,8 @@ public function setExportInterval(int $exportInterval): self * @return self * * @see Target::$timestampFormat + * + * @deprecated To be removed in 3.0. Use the `$timestampFormat` constructor parameter instead. */ public function setTimestampFormat(string $format): self { @@ -252,6 +280,8 @@ public function setTimestampFormat(string $format): self * @return self * * @see Target::$enabled + * + * @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 7b7867d5..c78e1bf1 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(exceptCategories: ['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..9bc7aee6 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 $exceptCategories = [], + ?callable $format = null, + ?callable $prefix = null, + ?string $timestampFormat = null, + int $exportInterval = self::DEFAULT_EXPORT_INTERVAL, + bool|callable $enabled = true, + ) { + parent::__construct($levels, $categories, $exceptCategories, $format, $prefix, $timestampFormat, $exportInterval, $enabled); + $this->exportFormatter = new Formatter($format, $prefix, $timestampFormat); } public function export(): void