From bc991fc78ad45b8457c4b4ab6529f1f54e9dd5bb Mon Sep 17 00:00:00 2001 From: Sergiu Date: Mon, 26 Aug 2024 10:58:24 +0300 Subject: [PATCH 1/7] removed laminas-log dependency and kept stream functionality from it --- README.md | 24 +- composer.json | 4 +- docs/book/v3/configuring-writer.md | 2 +- docs/book/v3/example-with-formatter.md | 6 +- docs/book/v3/filtering-log-messages.md | 12 +- docs/book/v3/usage.md | 2 +- log.global.php.dist | 60 -- src/ConfigProvider.php | 10 +- src/Exception/InvalidArgumentException.php | 9 + src/Exception/RuntimeException.php | 9 + src/Factory/FilterPluginManagerFactory.php | 2 +- src/Factory/FormatterPluginManagerFactory.php | 2 +- src/Factory/LoggerAbstractServiceFactory.php | 67 ++- src/Factory/ProcessorPluginManagerFactory.php | 2 +- src/Factory/WriterFactory.php | 107 ++++ src/Factory/WriterPluginManagerFactory.php | 2 +- src/Filter/FilterInterface.php | 13 + src/Filter/Priority.php | 47 ++ src/Filter/Regex.php | 54 ++ src/Filter/SuppressFilter.php | 52 ++ src/Filter/Validator.php | 45 ++ src/Formatter/Base.php | 125 ++++ src/Formatter/FormatterInterface.php | 34 ++ src/Formatter/Json.php | 56 ++ src/Formatter/Simple.php | 65 +++ src/Logger.php | 544 ++++++++++++++++++ src/LoggerInterface.php | 24 + src/LoggerServiceFactory.php | 80 +++ src/Manager/FilterPluginManager.php | 61 ++ src/Manager/FormatterPluginManager.php | 46 ++ src/Manager/ProcessorPluginManager.php | 56 ++ src/Manager/WriterPluginManager.php | 55 ++ src/Processor/Backtrace.php | 91 +++ src/Processor/ProcessorInterface.php | 13 + src/Processor/PsrPlaceholder.php | 38 ++ src/Processor/ReferenceId.php | 41 ++ src/Processor/RequestId.php | 52 ++ src/Writer/AbstractWriter.php | 266 +++++++++ src/Writer/Noop.php | 15 + src/Writer/Stream.php | 137 +++++ src/Writer/WriterInterface.php | 31 + 41 files changed, 2231 insertions(+), 130 deletions(-) create mode 100644 src/Exception/InvalidArgumentException.php create mode 100644 src/Exception/RuntimeException.php create mode 100644 src/Factory/WriterFactory.php create mode 100644 src/Filter/FilterInterface.php create mode 100644 src/Filter/Priority.php create mode 100644 src/Filter/Regex.php create mode 100644 src/Filter/SuppressFilter.php create mode 100644 src/Filter/Validator.php create mode 100644 src/Formatter/Base.php create mode 100644 src/Formatter/FormatterInterface.php create mode 100644 src/Formatter/Json.php create mode 100644 src/Formatter/Simple.php create mode 100644 src/Logger.php create mode 100644 src/LoggerInterface.php create mode 100644 src/LoggerServiceFactory.php create mode 100644 src/Manager/FilterPluginManager.php create mode 100644 src/Manager/FormatterPluginManager.php create mode 100644 src/Manager/ProcessorPluginManager.php create mode 100644 src/Manager/WriterPluginManager.php create mode 100644 src/Processor/Backtrace.php create mode 100644 src/Processor/ProcessorInterface.php create mode 100644 src/Processor/PsrPlaceholder.php create mode 100644 src/Processor/ReferenceId.php create mode 100644 src/Processor/RequestId.php create mode 100644 src/Writer/AbstractWriter.php create mode 100644 src/Writer/Noop.php create mode 100644 src/Writer/Stream.php create mode 100644 src/Writer/WriterInterface.php diff --git a/README.md b/README.md index 266b828..fa974d1 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Laminas\Log\Logger::ALERT, // this is equal to 1 + 'priority' => \Dot\Log\Manager\Logger::ALERT, // this is equal to 1 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', ], @@ -113,7 +113,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', 'filters' => [ @@ -121,7 +121,7 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '>=', - 'priority' => \Laminas\Log\Logger::EMERG, + 'priority' => \Dot\Log\Manager\Logger::EMERG, ] ], ], @@ -130,7 +130,7 @@ return [ // Only warnings 'OnlyWarningsWriter' => [ 'name' => 'stream', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/warnings_only.log', 'filters' => [ @@ -138,7 +138,7 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '==', - 'priority' => \Laminas\Log\Logger::WARN, + 'priority' => \Dot\Log\Manager\Logger::WARN, ], ], ], @@ -147,7 +147,7 @@ return [ // Warnings and more important messages 'WarningOrHigherWriter' => [ 'name' => 'stream', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/important_messages.log', 'filters' => [ @@ -157,7 +157,7 @@ return [ // note, the smaller the priority, the more important is the message // 0 - emergency, 1 - alert, 2- error, 3 - warn. .etc 'operator' => '<=', - 'priority' => \Laminas\Log\Logger::WARN, + 'priority' => \Dot\Log\Manager\Logger::WARN, ], ], ], @@ -197,7 +197,7 @@ The following formats the message as JSON data: ```php 'formatter' => [ - 'name' => \Laminas\Log\Formatter\Json::class, + 'name' => \Dot\Log\Manager\Formatter\Json::class, ], ``` @@ -220,7 +220,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', // explicitly log all messages @@ -229,12 +229,12 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '>=', - 'priority' => \Laminas\Log\Logger::EMERG, + 'priority' => \Dot\Log\Manager\Logger::EMERG, ], ], ], 'formatter' => [ - 'name' => \Laminas\Log\Formatter\Json::class, + 'name' => \Dot\Log\Manager\Formatter\Json::class, ], ], ], @@ -252,7 +252,7 @@ Basic usage of the logger is illustrated below. The messages are written to see which logs are written and which are not written. ```php -use Laminas\Log\Logger; +use Dot\Log\Manager\Logger; ``` ... diff --git a/composer.json b/composer.json index 70cae91..200ae39 100644 --- a/composer.json +++ b/composer.json @@ -20,10 +20,8 @@ ], "require": { "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "psr/http-message": "^1.0 || ^2.0", "laminas/laminas-servicemanager": "^3.22", - "laminas/laminas-log": "^2.17", - "dotkernel/dot-mail": "^4.1" + "laminas/laminas-validator": "^2.64" }, "require-dev": { "phpunit/phpunit": "^10.2", diff --git a/docs/book/v3/configuring-writer.md b/docs/book/v3/configuring-writer.md index a9ed716..3531f0e 100644 --- a/docs/book/v3/configuring-writer.md +++ b/docs/book/v3/configuring-writer.md @@ -18,7 +18,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Laminas\Log\Logger::ALERT, // this is equal to 1 + 'priority' => \Dot\Log\Manager\Logger::ALERT, // this is equal to 1 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', ], diff --git a/docs/book/v3/example-with-formatter.md b/docs/book/v3/example-with-formatter.md index ed8b278..de6460b 100644 --- a/docs/book/v3/example-with-formatter.md +++ b/docs/book/v3/example-with-formatter.md @@ -15,7 +15,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', // explicitly log all messages @@ -24,12 +24,12 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '>=', - 'priority' => \Laminas\Log\Logger::EMERG, + 'priority' => \Dot\Log\Manager\Logger::EMERG, ], ], ], 'formatter' => [ - 'name' => \Laminas\Log\Formatter\Json::class, + 'name' => \Dot\Log\Manager\Formatter\Json::class, ], ], ], diff --git a/docs/book/v3/filtering-log-messages.md b/docs/book/v3/filtering-log-messages.md index 32c185f..da1aac7 100644 --- a/docs/book/v3/filtering-log-messages.md +++ b/docs/book/v3/filtering-log-messages.md @@ -31,7 +31,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', 'filters' => [ @@ -39,7 +39,7 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '>=', - 'priority' => \Laminas\Log\Logger::EMERG, + 'priority' => \Dot\Log\Manager\Logger::EMERG, ] ], ], @@ -48,7 +48,7 @@ return [ // Only warnings 'OnlyWarningsWriter' => [ 'name' => 'stream', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/warnings_only.log', 'filters' => [ @@ -56,7 +56,7 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '==', - 'priority' => \Laminas\Log\Logger::WARN, + 'priority' => \Dot\Log\Manager\Logger::WARN, ], ], ], @@ -65,7 +65,7 @@ return [ // Warnings and more important messages 'WarningOrHigherWriter' => [ 'name' => 'stream', - 'priority' => \Laminas\Log\Logger::ALERT, + 'priority' => \Dot\Log\Manager\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/important_messages.log', 'filters' => [ @@ -75,7 +75,7 @@ return [ // note, the smaller the priority, the more important is the message // 0 - emergency, 1 - alert, 2- error, 3 - warn etc. 'operator' => '<=', - 'priority' => \Laminas\Log\Logger::WARN, + 'priority' => \Dot\Log\Manager\Logger::WARN, ], ], ], diff --git a/docs/book/v3/usage.md b/docs/book/v3/usage.md index fea781e..e479b9c 100644 --- a/docs/book/v3/usage.md +++ b/docs/book/v3/usage.md @@ -5,7 +5,7 @@ Basic usage of the logger is illustrated below. The messages are written to see which logs are written and which are not written. ```php -use Laminas\Log\Logger; +use Dot\Log\Manager\Logger; ``` ... diff --git a/log.global.php.dist b/log.global.php.dist index a4342c5..7b401f6 100644 --- a/log.global.php.dist +++ b/log.global.php.dist @@ -2,11 +2,8 @@ return [ 'dot_log' => [ - 'loggers' => [ //define log services here - - //example 1 - stream logger 'stream_logger' => [ 'writers' => [ 'name' => 'stream', @@ -24,63 +21,6 @@ return [ ], ], ], - - //example 2 - db logger - 'db_logger' => [ - 'writers' => [ - 'name' => 'db', - 'priority' => \Laminas\Log\Logger::INFO, - 'options' => [ - //service name of the database adapter - 'db' => 'database', - 'table' => 'log_table', - - //optional, column map - 'column' => [ - 'timestamp' => 'date', - 'priority' => 'type', - 'message' => 'event', - ], - - //optional, separator - 'separator' => '-', - - 'formatter' => [ - 'name' => 'MyFormatter', - ], - - 'filters' => [ - [ - 'name' => 'MyFilter', - ] - ] - ], - ], - ], - - //example 3 - mail logger using a mail service from dot-mail library - 'mail_logger' => [ - 'writers' => [ - 'name' => 'mail', - 'priority' => \Laminas\Log\Logger::ERR, - 'options' => [ - 'mail_service' => 'dot-mail.mail-service.service_name', - - 'subject_prepend_text' => '', - - 'formatter' => [ - 'name' => 'MyFormatter', - ], - - 'filters' => [ - [ - 'name' => 'MyFilter' - ], - ], - ] - ], - ], - ], ], ]; diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index bf64ab2..0805b44 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -9,12 +9,10 @@ use Dot\Log\Factory\LoggerAbstractServiceFactory; use Dot\Log\Factory\ProcessorPluginManagerFactory; use Dot\Log\Factory\WriterPluginManagerFactory; -use Laminas\Log\FilterPluginManager; -use Laminas\Log\FormatterPluginManager; -use Laminas\Log\Logger; -use Laminas\Log\LoggerServiceFactory; -use Laminas\Log\ProcessorPluginManager; -use Laminas\Log\WriterPluginManager; +use Dot\Log\Manager\FilterPluginManager; +use Dot\Log\Manager\FormatterPluginManager; +use Dot\Log\Manager\ProcessorPluginManager; +use Dot\Log\Manager\WriterPluginManager; class ConfigProvider { diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..bdc611f --- /dev/null +++ b/src/Exception/InvalidArgumentException.php @@ -0,0 +1,9 @@ +configKey = $configKey; } /** - * @param string $requestedName * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ @@ -46,22 +46,40 @@ public function canCreate(ContainerInterface $container, $requestedName): bool return false; } - return parent::canCreate($container, $parts[1]); + $config = $this->getConfig($container); + if (empty($config)) { + return false; + } + + return isset($config[$parts[1]]); } - /** - * @param string $requestedName - * @throws ContainerExceptionInterface - */ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): Logger { $parts = explode('.', $requestedName); - return parent::__invoke($container, $parts[1], $options); + + $config = $this->getConfig($container); + $config = $config[$parts[1]]; + + $this->processConfig($config, $container); + + return new Logger($config); } + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ protected function getConfig(ContainerInterface $services): array { - parent::getConfig($services); + if (! $services->has('config')) { + $this->config = []; + } + + $config = $services->get('config'); + if (isset($config[$this->configKey])) { + $this->config = $config[$this->configKey]; + } if ( ! empty($this->config) @@ -74,10 +92,6 @@ protected function getConfig(ContainerInterface $services): array return $this->config; } - /** - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - */ protected function processConfig(array &$config, ContainerInterface $services): void { if (isset($config['writers'])) { @@ -87,21 +101,6 @@ protected function processConfig(array &$config, ContainerInterface $services): $writerConfig['options']['stream'] ); } - if ( - isset($writerConfig['name']) - && in_array($writerConfig['name'], ['mail', Mail::class, 'laminaslogwritermail']) - && isset($writerConfig['options']['mail_service']) - && is_string($writerConfig['options']['mail_service']) - && $services->has($writerConfig['options']['mail_service']) - ) { - /** @var MailServiceInterface $mailService */ - $mailService = $services->get($writerConfig['options']['mail_service']); - $mail = $mailService->getMessage(); - $transport = $mailService->getTransport(); - - $config['writers'][$index]['options']['mail'] = $mail; - $config['writers'][$index]['options']['transport'] = $transport; - } } } diff --git a/src/Factory/ProcessorPluginManagerFactory.php b/src/Factory/ProcessorPluginManagerFactory.php index 61ee17f..006ea7b 100644 --- a/src/Factory/ProcessorPluginManagerFactory.php +++ b/src/Factory/ProcessorPluginManagerFactory.php @@ -5,7 +5,7 @@ namespace Dot\Log\Factory; use Exception; -use Laminas\Log\ProcessorPluginManager; +use Dot\Log\Manager\ProcessorPluginManager; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/src/Factory/WriterFactory.php b/src/Factory/WriterFactory.php new file mode 100644 index 0000000..9ebb970 --- /dev/null +++ b/src/Factory/WriterFactory.php @@ -0,0 +1,107 @@ +setCreationOptions($creationOptions); + } + } + + public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): object + { + $options = (array) $options; + + $options = $this->populateOptions($options, $container, 'filter_manager', 'LogFilterManager'); + $options = $this->populateOptions($options, $container, 'formatter_manager', 'LogFormatterManager'); + + return new $requestedName($options); + } + + /** + * Populates the options array with the correct container value. + */ + private function populateOptions( + array $options, + ContainerInterface $container, + string $name, + string $defaultService + ): array + { + if (isset($options[$name]) && is_string($options[$name])) { + $options[$name] = $container->get($options[$name]); + return $options; + } + + if (! isset($options[$name]) && $container->has($defaultService)) { + $options[$name] = $container->get($defaultService); + return $options; + } + + return $options; + } + + /** + * Create an instance of the named service. + * + * First, it checks if `$canonicalName` resolves to a class, and, if so, uses + * that value to proxy to `__invoke()`. + * + * Next, if `$requestedName` is non-empty and resolves to a class, this + * method uses that value to proxy to `__invoke()`. + * + * Finally, if the above each fail, it raises an exception. + * + * The approach above is performed as version 2 has two distinct behaviors + * under which factories are invoked: + * + * - If an alias was used, $canonicalName is the resolved name, and + * $requestedName is the service name requested, in which case $canonicalName + * is likely the qualified class name; + * - Otherwise, $canonicalName is the normalized name, and $requestedName + * is the original service name requested (typically the qualified class name). + * + * @throws ContainerExceptionInterface + */ + public function createService( + ServiceLocatorInterface $serviceLocator, + ?string $canonicalName = null, + ?string $requestedName = null + ): object + { + if (is_string($canonicalName) && class_exists($canonicalName)) { + return $this($serviceLocator->getServiceLocator(), $canonicalName, $this->creationOptions); + } + + if (is_string($requestedName) && class_exists($requestedName)) { + return $this($serviceLocator->getServiceLocator(), $requestedName, $this->creationOptions); + } + + throw new InvalidServiceException(sprintf( + '%s requires that the requested name is provided on invocation; ' + . 'please update your tests or consuming container', + self::class + )); + } + + public function setCreationOptions(array $creationOptions): void + { + $this->creationOptions = $creationOptions; + } +} diff --git a/src/Factory/WriterPluginManagerFactory.php b/src/Factory/WriterPluginManagerFactory.php index e628460..fcec022 100644 --- a/src/Factory/WriterPluginManagerFactory.php +++ b/src/Factory/WriterPluginManagerFactory.php @@ -5,7 +5,7 @@ namespace Dot\Log\Factory; use Exception; -use Laminas\Log\WriterPluginManager; +use Dot\Log\Manager\WriterPluginManager; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/src/Filter/FilterInterface.php b/src/Filter/FilterInterface.php new file mode 100644 index 0000000..3ceea26 --- /dev/null +++ b/src/Filter/FilterInterface.php @@ -0,0 +1,13 @@ +priority = (int) $priority; + $this->operator = $operator ?? '<='; + } + + /** + * Returns TRUE to accept the message, FALSE to block it. + */ + public function filter(array $event): bool|int + { + return version_compare((string) $event['priority'], (string) $this->priority, $this->operator); + } +} diff --git a/src/Filter/Regex.php b/src/Filter/Regex.php new file mode 100644 index 0000000..fafbfeb --- /dev/null +++ b/src/Filter/Regex.php @@ -0,0 +1,54 @@ +regex = $regex; + } + + /** + * Returns TRUE to accept the message, FALSE to block it. + */ + public function filter(array $event): bool + { + $message = $event['message']; + if (is_array($event['message'])) { + $message = var_export($message, true); + } + return preg_match($this->regex, $message) > 0; + } +} diff --git a/src/Filter/SuppressFilter.php b/src/Filter/SuppressFilter.php new file mode 100644 index 0000000..895f2f3 --- /dev/null +++ b/src/Filter/SuppressFilter.php @@ -0,0 +1,52 @@ +suppress($suppress); + } + + /** + * This is a simple boolean filter. + * + * Call suppress(true) to suppress all log events. + * Call suppress(false) to accept all log events. + */ + public function suppress(bool $suppress): void + { + $this->accept = !$suppress; + } + + /** + * Returns TRUE to accept the message, FALSE to block it. + */ + public function filter(array $event): bool + { + return $this->accept; + } +} diff --git a/src/Filter/Validator.php b/src/Filter/Validator.php new file mode 100644 index 0000000..4de1695 --- /dev/null +++ b/src/Filter/Validator.php @@ -0,0 +1,45 @@ +validator = $validator; + } + + /** + * Returns TRUE to accept the message, FALSE to block it. + */ + public function filter(array $event) + { + return $this->validator->isValid($event['message']); + } +} diff --git a/src/Formatter/Base.php b/src/Formatter/Base.php new file mode 100644 index 0000000..b650eba --- /dev/null +++ b/src/Formatter/Base.php @@ -0,0 +1,125 @@ +dateTimeFormat = $dateTimeFormat; + } + } + + /** + * Formats data to be written by the writer. + */ + public function format($event): array|string + { + foreach ($event as $key => $value) { + // Keep extra as an array + if ('extra' === $key && is_array($value)) { + $event[$key] = self::format($value); + } else { + $event[$key] = $this->normalize($value); + } + } + + return $event; + } + + /** + * Normalize all non-scalar data types (except null) in a string value + */ + protected function normalize(mixed $value): mixed + { + if (is_scalar($value) || null === $value) { + return $value; + } + + // better readable JSON + static $jsonFlags; + if ($jsonFlags === null) { + $jsonFlags = 0; + $jsonFlags |= defined('JSON_UNESCAPED_SLASHES') ? JSON_UNESCAPED_SLASHES : 0; + $jsonFlags |= defined('JSON_UNESCAPED_UNICODE') ? JSON_UNESCAPED_UNICODE : 0; + } + + // Error suppression is used in several of these cases as a fix for each of + // #5383 and #4616. Without it, #4616 fails whenever recursion occurs during + // json_encode() operations; usage of a dedicated error handler callback + // causes #5383 to fail when the Logger is being used as an error handler. + // The only viable solution here is error suppression, ugly as it may be. + if ($value instanceof DateTime) { + $value = $value->format($this->getDateTimeFormat()); + } elseif ($value instanceof Traversable) { + $value = @json_encode(iterator_to_array($value), $jsonFlags); + } elseif (is_array($value)) { + $value = @json_encode($value, $jsonFlags); + } elseif (is_object($value) && ! method_exists($value, '__toString')) { + $value = sprintf('object(%s) %s', get_class($value), @json_encode($value)); + } elseif (is_resource($value)) { + $value = sprintf('resource(%s)', get_resource_type($value)); + } elseif (! is_object($value)) { + $value = gettype($value); + } + + return (string) $value; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormat() + { + return $this->dateTimeFormat; + } + + /** + * {@inheritDoc} + */ + public function setDateTimeFormat($dateTimeFormat): static + { + $this->dateTimeFormat = (string) $dateTimeFormat; + return $this; + } +} diff --git a/src/Formatter/FormatterInterface.php b/src/Formatter/FormatterInterface.php new file mode 100644 index 0000000..8d90a8f --- /dev/null +++ b/src/Formatter/FormatterInterface.php @@ -0,0 +1,34 @@ +format($this->getDateTimeFormat()); + } + + return @json_encode( + $event, + JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_NUMERIC_CHECK | JSON_PRESERVE_ZERO_FRACTION + ); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormat(): string + { + return $this->dateTimeFormat; + } + + /** + * {@inheritDoc} + */ + public function setDateTimeFormat($dateTimeFormat): static + { + $this->dateTimeFormat = (string) $dateTimeFormat; + return $this; + } +} diff --git a/src/Formatter/Simple.php b/src/Formatter/Simple.php new file mode 100644 index 0000000..73645d4 --- /dev/null +++ b/src/Formatter/Simple.php @@ -0,0 +1,65 @@ +format = $format ?? static::DEFAULT_FORMAT; + + parent::__construct($dateTimeFormat); + } + + /** + * Formats data into a single line to be written by the writer. + */ + public function format($event): array|string + { + $output = $this->format; + + $event = parent::format($event); + foreach ($event as $name => $value) { + if ('extra' === $name && is_array($value) && count($value)) { + $value = $this->normalize($value); + } elseif ('extra' === $name) { + // Don't print an empty array + $value = ''; + } + $output = str_replace("%$name%", (string) $value, $output); + } + + if ( + array_key_exists('extra', $event) && empty($event['extra']) + && false !== strpos($this->format, '%extra%') + ) { + $output = rtrim($output, ' '); + } + return $output; + } +} diff --git a/src/Logger.php b/src/Logger.php new file mode 100644 index 0000000..e114b28 --- /dev/null +++ b/src/Logger.php @@ -0,0 +1,544 @@ + self::NOTICE, + E_USER_NOTICE => self::NOTICE, + E_WARNING => self::WARN, + E_CORE_WARNING => self::WARN, + E_USER_WARNING => self::WARN, + E_ERROR => self::ERR, + E_USER_ERROR => self::ERR, + E_CORE_ERROR => self::ERR, + E_RECOVERABLE_ERROR => self::ERR, + E_PARSE => self::ERR, + E_COMPILE_ERROR => self::ERR, + E_COMPILE_WARNING => self::ERR, + E_STRICT => self::DEBUG, + E_DEPRECATED => self::DEBUG, + E_USER_DEPRECATED => self::DEBUG, + ]; + + /** + * Registered error handler + */ + protected static bool $registeredErrorHandler = false; + + /** + * Registered shutdown error handler + */ + protected static bool $registeredFatalErrorShutdownFunction = false; + + /** + * Registered exception handler + */ + protected static bool $registeredExceptionHandler = false; + + /** + * List of priority code => priority (short) name + */ + protected array $priorities = [ + self::EMERG => 'EMERG', + self::ALERT => 'ALERT', + self::CRIT => 'CRIT', + self::ERR => 'ERR', + self::WARN => 'WARN', + self::NOTICE => 'NOTICE', + self::INFO => 'INFO', + self::DEBUG => 'DEBUG', + ]; + + protected SplPriorityQueue $writers; + + protected SplPriorityQueue $processors; + + protected WriterPluginManager $writerPlugins; + + protected ProcessorPluginManager $processorPlugins; + + /** + * Constructor + * + * Set options for a logger. Accepted options are: + * - writers: array of writers to add to this logger + * - exceptionhandler: if true register this logger as exceptionhandler + * - errorhandler: if true register this logger as errorhandler + */ + public function __construct(iterable $options = null) + { + $this->writers = new SplPriorityQueue(); + $this->processors = new SplPriorityQueue(); + + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } + + if (! $options) { + return; + } + + if (! is_array($options)) { + throw new InvalidArgumentException( + 'Options must be an array or an object implementing \Traversable ' + ); + } + + // Inject writer plugin manager, if available + if ( + isset($options['writer_plugin_manager']) + && $options['writer_plugin_manager'] instanceof AbstractPluginManager + ) { + $this->setWriterPluginManager($options['writer_plugin_manager']); + } + + // Inject processor plugin manager, if available + if ( + isset($options['processor_plugin_manager']) + && $options['processor_plugin_manager'] instanceof AbstractPluginManager + ) { + $this->setProcessorPluginManager($options['processor_plugin_manager']); + } + + if (isset($options['writers']) && is_array($options['writers'])) { + foreach ($options['writers'] as $writer) { + if (! isset($writer['name'])) { + throw new InvalidArgumentException('Options must contain a name for the writer'); + } + + $priority = $writer['priority'] ?? null; + $writerOptions = $writer['options'] ?? null; + + $this->addWriter($writer['name'], $priority, $writerOptions); + } + } + + if (isset($options['processors']) && is_array($options['processors'])) { + foreach ($options['processors'] as $processor) { + if (! isset($processor['name'])) { + throw new InvalidArgumentException('Options must contain a name for the processor'); + } + + $priority = $processor['priority'] ?? null; + $processorOptions = $processor['options'] ?? null; + + $this->addProcessor($processor['name'], $priority, $processorOptions); + } + } + + if (isset($options['exceptionhandler']) && $options['exceptionhandler'] === true) { + static::registerExceptionHandler($this); + } + + if (isset($options['errorhandler']) && $options['errorhandler'] === true) { + static::registerErrorHandler($this); + } + + if (isset($options['fatal_error_shutdownfunction']) && $options['fatal_error_shutdownfunction'] === true) { + static::registerFatalErrorShutdownFunction($this); + } + } + + /** + * Shutdown all writers + */ + public function __destruct() + { + foreach ($this->writers as $writer) { + try { + $writer->shutdown(); + } catch (Exception $e) { + } + } + } + + public function getWriterPluginManager(): WriterPluginManager + { + if (null === $this->writerPlugins) { + $this->setWriterPluginManager(new WriterPluginManager(new ServiceManager())); + } + return $this->writerPlugins; + } + + public function setWriterPluginManager(WriterPluginManager $writerPlugins): static + { + $this->writerPlugins = $writerPlugins; + return $this; + } + + /** + * Get writer instance + */ + public function writerPlugin(string $name, ?array $options = null): WriterInterface + { + return $this->getWriterPluginManager()->get($name, $options); + } + + /** + * Add a writer to a logger + */ + public function addWriter(WriterInterface|string $writer, int $priority = 1, ?array $options = null): static + { + if (is_string($writer)) { + $writer = $this->writerPlugin($writer, $options); + } elseif (! $writer instanceof Writer\WriterInterface) { + throw new InvalidArgumentException(sprintf( + 'Writer must implement %s\Writer\WriterInterface; received "%s"', + __NAMESPACE__, + is_object($writer) ? get_class($writer) : gettype($writer) + )); + } + $this->writers->insert($writer, $priority); + + return $this; + } + + public function getWriters(): SplPriorityQueue + { + return $this->writers; + } + + public function setWriters(SplPriorityQueue $writers): static + { + foreach ($writers->toArray() as $writer) { + if (! $writer instanceof Writer\WriterInterface) { + throw new InvalidArgumentException( + 'Writers must be a SplPriorityQueue of Laminas\Log\Writer' + ); + } + } + $this->writers = $writers; + return $this; + } + + public function getProcessorPluginManager(): ProcessorPluginManager + { + if (null === $this->processorPlugins) { + $this->setProcessorPluginManager(new ProcessorPluginManager(new ServiceManager())); + } + return $this->processorPlugins; + } + + public function setProcessorPluginManager($plugins): static + { + if (is_string($plugins)) { + $plugins = new $plugins(); + } + if (! $plugins instanceof ProcessorPluginManager) { + throw new InvalidArgumentException(sprintf( + 'processor plugin manager must extend %s\ProcessorPluginManager; received %s', + __NAMESPACE__, + is_object($plugins) ? get_class($plugins) : gettype($plugins) + )); + } + + $this->processorPlugins = $plugins; + return $this; + } + + public function processorPlugin(string $name, ?array $options = null): ProcessorInterface + { + return $this->getProcessorPluginManager()->get($name, $options); + } + + public function addProcessor(ProcessorInterface|string $processor, int $priority = 1, ?array $options = null): static + { + if (is_string($processor)) { + $processor = $this->processorPlugin($processor, $options); + } elseif (! $processor instanceof Processor\ProcessorInterface) { + throw new InvalidArgumentException(sprintf( + 'Processor must implement Laminas\Log\ProcessorInterface; received "%s"', + is_object($processor) ? get_class($processor) : gettype($processor) + )); + } + $this->processors->insert($processor, $priority); + + return $this; + } + + public function getProcessors(): SplPriorityQueue + { + return $this->processors; + } + + public function log(int $priority, mixed $message, iterable $extra = []): static + { + if (! is_int($priority) || ($priority < 0) || ($priority >= count($this->priorities))) { + throw new InvalidArgumentException(sprintf( + '$priority must be an integer >= 0 and < %d; received %s', + count($this->priorities), + var_export($priority, true) + )); + } + if (is_object($message) && ! method_exists($message, '__toString')) { + throw new InvalidArgumentException( + '$message must implement magic __toString() method' + ); + } + + if (! is_array($extra) && ! $extra instanceof Traversable) { + throw new InvalidArgumentException( + '$extra must be an array or implement Traversable' + ); + } elseif ($extra instanceof Traversable) { + $extra = ArrayUtils::iteratorToArray($extra); + } + + if ($this->writers->count() === 0) { + throw new RuntimeException('No log writer specified'); + } + + $timestamp = new DateTime(); + + if (is_array($message)) { + $message = var_export($message, true); + } + + $event = [ + 'timestamp' => $timestamp, + 'priority' => $priority, + 'priorityName' => $this->priorities[$priority], + 'message' => (string) $message, + 'extra' => $extra, + ]; + + /** @var ProcessorInterface $processor */ + foreach ($this->processors->toArray() as $processor) { + $event = $processor->process($event); + } + + /** @var WriterInterface $writer */ + foreach ($this->writers->toArray() as $writer) { + $writer->write($event); + } + + return $this; + } + + public function emerg(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::EMERG, $message, $extra); + } + + public function alert(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::ALERT, $message, $extra); + } + + public function crit(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::CRIT, $message, $extra); + } + + public function err(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::ERR, $message, $extra); + } + + public function warn(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::WARN, $message, $extra); + } + + public function notice(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::NOTICE, $message, $extra); + } + + public function info(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::INFO, $message, $extra); + } + + public function debug(string $message, iterable $extra = []): LoggerInterface + { + return $this->log(self::DEBUG, $message, $extra); + } + + /** + * Register logging system as an error handler to log PHP errors + * + * @link http://www.php.net/manual/function.set-error-handler.php + */ + public static function registerErrorHandler(Logger $logger, bool $continueNativeHandler = false): bool|null|callable + { + // Only register once per instance + if (static::$registeredErrorHandler) { + return false; + } + + $errorPriorityMap = static::$errorPriorityMap; + + $previous = set_error_handler( + function ($level, $message, $file, $line) use ($logger, $errorPriorityMap, $continueNativeHandler) { + $iniLevel = error_reporting(); + + if ($iniLevel & $level) { + if (isset($errorPriorityMap[$level])) { + $priority = $errorPriorityMap[$level]; + } else { + $priority = Logger::INFO; + } + $logger->log($priority, $message, [ + 'errno' => $level, + 'file' => $file, + 'line' => $line, + ]); + } + + return ! $continueNativeHandler; + } + ); + + static::$registeredErrorHandler = true; + return $previous; + } + + public static function unregisterErrorHandler(): void + { + restore_error_handler(); + static::$registeredErrorHandler = false; + } + + /** + * Register a shutdown handler to log fatal errors + * + * @link http://www.php.net/manual/function.register-shutdown-function.php + */ + public static function registerFatalErrorShutdownFunction(Logger $logger): bool + { + // Only register once per instance + if (static::$registeredFatalErrorShutdownFunction) { + return false; + } + + $errorPriorityMap = static::$errorPriorityMap; + + register_shutdown_function(function () use ($logger, $errorPriorityMap) { + $error = error_get_last(); + + if ( + null === $error + || ! in_array( + $error['type'], + [ + E_ERROR, + E_PARSE, + E_CORE_ERROR, + E_CORE_WARNING, + E_COMPILE_ERROR, + E_COMPILE_WARNING, + ], + true + ) + ) { + return; + } + + $logger->log( + $errorPriorityMap[$error['type']], + $error['message'], + [ + 'file' => $error['file'], + 'line' => $error['line'], + ] + ); + }); + + static::$registeredFatalErrorShutdownFunction = true; + + return true; + } + + /** + * Register logging system as an exception handler to log PHP exceptions + * + * @link http://www.php.net/manual/en/function.set-exception-handler.php + */ + public static function registerExceptionHandler(Logger $logger): bool + { + // Only register once per instance + if (static::$registeredExceptionHandler) { + return false; + } + + $errorPriorityMap = static::$errorPriorityMap; + + set_exception_handler(function ($exception) use ($logger, $errorPriorityMap) { + $logMessages = []; + + do { + $priority = Logger::ERR; + if ($exception instanceof ErrorException && isset($errorPriorityMap[$exception->getSeverity()])) { + $priority = $errorPriorityMap[$exception->getSeverity()]; + } + + $extra = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'trace' => $exception->getTrace(), + ]; + if (isset($exception->xdebug_message)) { + $extra['xdebug'] = $exception->xdebug_message; + } + + $logMessages[] = [ + 'priority' => $priority, + 'message' => $exception->getMessage(), + 'extra' => $extra, + ]; + $exception = $exception->getPrevious(); + } while ($exception); + + foreach (array_reverse($logMessages) as $logMessage) { + $logger->log($logMessage['priority'], $logMessage['message'], $logMessage['extra']); + } + }); + + static::$registeredExceptionHandler = true; + return true; + } + + public static function unregisterExceptionHandler(): void + { + restore_exception_handler(); + static::$registeredExceptionHandler = false; + } +} + diff --git a/src/LoggerInterface.php b/src/LoggerInterface.php new file mode 100644 index 0000000..125bce6 --- /dev/null +++ b/src/LoggerInterface.php @@ -0,0 +1,24 @@ +get('config'); + $logConfig = $config['log'] ?? []; + + $this->processConfig($logConfig, $container); + + return new Logger($logConfig); + } + + /** + * Process and return the configuration from the container. + */ + protected function processConfig(array &$config, ContainerInterface $services): void + { + if ( + isset($config['writer_plugin_manager']) + && is_string($config['writer_plugin_manager']) + && $services->has($config['writer_plugin_manager']) + ) { + $config['writer_plugin_manager'] = $services->get($config['writer_plugin_manager']); + } + + if ( + (! isset($config['writer_plugin_manager']) + || ! $config['writer_plugin_manager'] instanceof AbstractPluginManager) + && $services->has('LogWriterManager') + ) { + $config['writer_plugin_manager'] = $services->get('LogWriterManager'); + } + + if ( + isset($config['processor_plugin_manager']) + && is_string($config['processor_plugin_manager']) + && $services->has($config['processor_plugin_manager']) + ) { + $config['processor_plugin_manager'] = $services->get($config['processor_plugin_manager']); + } + + if ( + (! isset($config['processor_plugin_manager']) + || ! $config['processor_plugin_manager'] instanceof AbstractPluginManager) + && $services->has('LogProcessorManager') + ) { + $config['processor_plugin_manager'] = $services->get('LogProcessorManager'); + } + + if (! isset($config['writers']) || ! is_iterable($config['writers'])) { + return; + } + + if (! is_array($config['writers'])) { + $config['writers'] = iterator_to_array($config['writers']); + } + + foreach ($config['writers'] as $writerConfig) { + if (! is_array($writerConfig) && ! $writerConfig instanceof ArrayAccess) { + $type = is_object($writerConfig) ? get_class($writerConfig) : gettype($writerConfig); + throw new InvalidArgumentException( + 'config log.writers[] must contain array or ArrayAccess, ' . $type . ' provided' + ); + } + } + } +} diff --git a/src/Manager/FilterPluginManager.php b/src/Manager/FilterPluginManager.php new file mode 100644 index 0000000..931f1d8 --- /dev/null +++ b/src/Manager/FilterPluginManager.php @@ -0,0 +1,61 @@ + Priority::class, + 'regex' => Regex::class, + 'suppress' => SuppressFilter::class, + 'suppressfilter' => SuppressFilter::class, + 'validator' => Validator::class, + ]; + + protected $factories = [ + Priority::class => InvokableFactory::class, + Regex::class => InvokableFactory::class, + SuppressFilter::class => InvokableFactory::class, + Validator::class => InvokableFactory::class, + ]; + + protected $instanceOf = FilterInterface::class; + + /** + * Allow many filters of the same type + */ + protected $sharedByDefault = false; + + /** + * Validate the plugin is of the expected type. + * + * Validates against `$instanceOf`. + */ + public function validate($instance): void + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s can only create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + } +} diff --git a/src/Manager/FormatterPluginManager.php b/src/Manager/FormatterPluginManager.php new file mode 100644 index 0000000..2083f9f --- /dev/null +++ b/src/Manager/FormatterPluginManager.php @@ -0,0 +1,46 @@ + Simple::class, + ]; + + protected $factories = [ + Simple::class => InvokableFactory::class, + ]; + + protected $instanceOf = FormatterInterface::class; + + /** + * Allow many formatters of the same type + */ + protected $sharedByDefault = false; + + /** + * Validate the plugin is of the expected type. + * + * Validates against `$instanceOf`. + */ + public function validate($instance): void + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s can only create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + } +} diff --git a/src/Manager/ProcessorPluginManager.php b/src/Manager/ProcessorPluginManager.php new file mode 100644 index 0000000..c91834c --- /dev/null +++ b/src/Manager/ProcessorPluginManager.php @@ -0,0 +1,56 @@ + Backtrace::class, + 'psrplaceholder' => PsrPlaceholder::class, + 'referenceid' => ReferenceId::class, + 'requestid' => RequestId::class, + ]; + + protected $factories = [ + Backtrace::class => InvokableFactory::class, + PsrPlaceholder::class => InvokableFactory::class, + ReferenceId::class => InvokableFactory::class, + RequestId::class => InvokableFactory::class, + ]; + + protected $instanceOf = ProcessorInterface::class; + + /** + * Allow many processors of the same type + */ + protected $sharedByDefault = false; + + /** + * Validate the plugin is of the expected type. + * + * Validates against `$instanceOf`. + */ + public function validate($instance): void + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s can only create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + } +} + diff --git a/src/Manager/WriterPluginManager.php b/src/Manager/WriterPluginManager.php new file mode 100644 index 0000000..a1be1dc --- /dev/null +++ b/src/Manager/WriterPluginManager.php @@ -0,0 +1,55 @@ + Noop::class, + 'stream' => Stream::class, + + // The following are for backwards compatibility only; users + // should update their code to use the noop writer instead. + 'null' => Noop::class, + 'laminaslogwriternull' => Noop::class, + ]; + + protected $factories = [ + Noop::class => WriterFactory::class, + Stream::class => WriterFactory::class, + ]; + + protected $instanceOf = WriterInterface::class; + + /** + * Allow many writers of the same type + */ + protected $sharedByDefault = false; + + /** + * Validate the plugin is of the expected type. + * + * Validates against `$instanceOf`. + */ + public function validate(mixed $instance): void + { + if (! $instance instanceof $this->instanceOf) { + throw new InvalidServiceException(sprintf( + '%s can only create instances of %s; %s is invalid', + static::class, + $this->instanceOf, + is_object($instance) ? get_class($instance) : gettype($instance) + )); + } + } +} + diff --git a/src/Processor/Backtrace.php b/src/Processor/Backtrace.php new file mode 100644 index 0000000..b239446 --- /dev/null +++ b/src/Processor/Backtrace.php @@ -0,0 +1,91 @@ + 5.4.0) + */ + protected int $traceLimit = 10; + + /** + * Classes within these namespaces in the stack are ignored + */ + protected array $ignoredNamespaces = ['Laminas\\Log']; + + /** + * Set options for a backtrace processor. Accepted options are: + * - ignoredNamespaces: array of namespaces to be excluded from the logged backtrace + */ + public function __construct(?array $options = null) + { + if (! empty($options['ignoredNamespaces'])) { + $this->ignoredNamespaces = array_merge($this->ignoredNamespaces, (array) $options['ignoredNamespaces']); + } + } + + /** + * Adds the origin of the log() call to the event extras + */ + public function process(array $event): array + { + $trace = $this->getBacktrace(); + + array_shift($trace); // ignore $this->getBacktrace(); + array_shift($trace); // ignore $this->process() + + $i = 0; + while ( + isset($trace[$i]['class']) + && $this->shouldIgnoreFrame($trace[$i]['class']) + ) { + $i++; + } + + $origin = [ + 'file' => $trace[$i - 1]['file'] ?? null, + 'line' => $trace[$i - 1]['line'] ?? null, + 'class' => $trace[$i]['class'] ?? null, + 'function' => $trace[$i]['function'] ?? null, + ]; + + $extra = $origin; + if (isset($event['extra'])) { + $extra = array_merge($origin, $event['extra']); + } + $event['extra'] = $extra; + + return $event; + } + + public function getIgnoredNamespaces(): array + { + return $this->ignoredNamespaces; + } + + /** + * Provide backtrace as slim as possible + */ + protected function getBacktrace(): array + { + return debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $this->traceLimit); + } + + /** + * Determine whether the current frame in the backtrace should be ignored based on the class name + */ + protected function shouldIgnoreFrame($class): bool + { + foreach ($this->ignoredNamespaces as $ignoredNamespace) { + if (false !== strpos($class, $ignoredNamespace)) { + return true; + } + } + + return false; + } +} + diff --git a/src/Processor/ProcessorInterface.php b/src/Processor/ProcessorInterface.php new file mode 100644 index 0000000..545bca2 --- /dev/null +++ b/src/Processor/ProcessorInterface.php @@ -0,0 +1,13 @@ + $val) { + if ( + $val === null + || is_scalar($val) + || (is_object($val) && method_exists($val, "__toString")) + ) { + $replacements['{' . $key . '}'] = $val; + continue; + } + + if (is_object($val)) { + $replacements['{' . $key . '}'] = '[object ' . get_class($val) . ']'; + continue; + } + + $replacements['{' . $key . '}'] = '[' . gettype($val) . ']'; + } + + $event['message'] = strtr($event['message'], $replacements); + return $event; + } +} + diff --git a/src/Processor/ReferenceId.php b/src/Processor/ReferenceId.php new file mode 100644 index 0000000..2920092 --- /dev/null +++ b/src/Processor/ReferenceId.php @@ -0,0 +1,41 @@ +getIdentifier(); + + return $event; + } + + public function setReferenceId($identifier): static + { + $this->identifier = $identifier; + + return $this; + } + + public function getReferenceId(): string + { + return $this->getIdentifier(); + } +} + diff --git a/src/Processor/RequestId.php b/src/Processor/RequestId.php new file mode 100644 index 0000000..4734a0f --- /dev/null +++ b/src/Processor/RequestId.php @@ -0,0 +1,52 @@ +getIdentifier(); + return $event; + } + + /** + * Provide unique identifier for a request + */ + protected function getIdentifier(): string + { + if ($this->identifier) { + return $this->identifier; + } + + $identifier = (string) $_SERVER['REQUEST_TIME_FLOAT']; + + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $identifier .= $_SERVER['HTTP_X_FORWARDED_FOR']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $identifier .= $_SERVER['REMOTE_ADDR']; + } + + $this->identifier = md5($identifier); + + return $this->identifier; + } +} + diff --git a/src/Writer/AbstractWriter.php b/src/Writer/AbstractWriter.php new file mode 100644 index 0000000..19846dd --- /dev/null +++ b/src/Writer/AbstractWriter.php @@ -0,0 +1,266 @@ +setFilterPluginManager($options['filter_manager']); + } + + if (isset($options['formatter_manager'])) { + $this->setFormatterPluginManager($options['formatter_manager']); + } + + if (isset($options['filters'])) { + $filters = $options['filters']; + if (is_int($filters) || is_string($filters) || $filters instanceof FilterInterface) { + $this->addFilter($filters); + } elseif (is_array($filters)) { + foreach ($filters as $filter) { + if (is_int($filter) || is_string($filter) || $filter instanceof FilterInterface) { + $this->addFilter($filter); + } elseif (is_array($filter)) { + if (! isset($filter['name'])) { + throw new InvalidArgumentException( + 'Options must contain a name for the filter' + ); + } + $filterOptions = $filter['options'] ?? null; + $this->addFilter($filter['name'], $filterOptions); + } + } + } + } + + if (isset($options['formatter'])) { + $formatter = $options['formatter']; + if (is_string($formatter) || $formatter instanceof FormatterInterface) { + $this->setFormatter($formatter); + } elseif (is_array($formatter)) { + if (! isset($formatter['name'])) { + throw new InvalidArgumentException('Options must contain a name for the formatter'); + } + $formatterOptions = $formatter['options'] ?? null; + $this->setFormatter($formatter['name'], $formatterOptions); + } + } + } + } + + /** + * Add a filter specific to this writer. + */ + public function addFilter(int|string|FilterInterface $filter, ?array $options = null): WriterInterface + { + if (is_int($filter)) { + $filter = new Priority($filter); + } + + if (is_string($filter)) { + $filter = $this->filterPlugin($filter, $options); + } + + if (! $filter instanceof FilterInterface) { + throw new InvalidArgumentException(sprintf( + 'Filter must implement %s\Filter\FilterInterface; received "%s"', + __NAMESPACE__, + is_object($filter) ? get_class($filter) : gettype($filter) + )); + } + + $this->filters[] = $filter; + return $this; + } + + public function getFilterPluginManager(): FilterPluginManager + { + if (null === $this->filterPlugins) { + $this->setFilterPluginManager(new FilterPluginManager(new ServiceManager())); + } + return $this->filterPlugins; + } + + public function setFilterPluginManager($plugins): static + { + if (is_string($plugins)) { + $plugins = new $plugins(); + } + if (! $plugins instanceof FilterPluginManager) { + throw new InvalidArgumentException(sprintf( + 'Writer plugin manager must extend %s; received %s', + FilterPluginManager::class, + is_object($plugins) ? get_class($plugins) : gettype($plugins) + )); + } + + $this->filterPlugins = $plugins; + return $this; + } + + public function filterPlugin($name, ?array $options = null) + { + return $this->getFilterPluginManager()->get($name, $options); + } + + public function getFormatterPluginManager(): FormatterPluginManager + { + if (null === $this->formatterPlugins) { + $this->setFormatterPluginManager(new FormatterPluginManager(new ServiceManager())); + } + return $this->formatterPlugins; + } + + public function setFormatterPluginManager($plugins): static + { + if (is_string($plugins)) { + $plugins = new $plugins(); + } + if (! $plugins instanceof FormatterPluginManager) { + throw new InvalidArgumentException( + sprintf( + 'Writer plugin manager must extend %s; received %s', + FormatterPluginManager::class, + is_object($plugins) ? get_class($plugins) : gettype($plugins) + ) + ); + } + + $this->formatterPlugins = $plugins; + return $this; + } + + public function formatterPlugin($name, ?array $options = null) + { + return $this->getFormatterPluginManager()->get($name, $options); + } + + /** + * Log a message to this writer. + * @throws ErrorException + */ + public function write(array $event): void + { + foreach ($this->filters as $filter) { + if (! $filter->filter($event)) { + return; + } + } + + $errorHandlerStarted = false; + + if ($this->convertWriteErrorsToExceptions && ! ErrorHandler::started()) { + ErrorHandler::start($this->errorsToExceptionsConversionLevel); + $errorHandlerStarted = true; + } + + try { + $this->doWrite($event); + } catch (Exception $e) { + if ($errorHandlerStarted) { + ErrorHandler::stop(); + } + throw $e; + } + + if ($errorHandlerStarted) { + $error = ErrorHandler::stop(); + if ($error) { + throw new RuntimeException("Unable to write", 0, $error); + } + } + } + + public function setFormatter(FormatterInterface|string $formatter, ?array $options = null): WriterInterface + { + if (is_string($formatter)) { + $formatter = $this->formatterPlugin($formatter, $options); + } + + if (! $formatter instanceof FormatterInterface) { + throw new InvalidArgumentException(sprintf( + 'Formatter must implement %s\Formatter\FormatterInterface; received "%s"', + __NAMESPACE__, + is_object($formatter) ? get_class($formatter) : gettype($formatter) + )); + } + + $this->formatter = $formatter; + return $this; + } + + protected function getFormatter(): FormatterInterface + { + return $this->formatter; + } + + protected function hasFormatter(): bool + { + return $this->formatter instanceof FormatterInterface; + } + + public function setConvertWriteErrorsToExceptions(bool $convertErrors): void + { + $this->convertWriteErrorsToExceptions = $convertErrors; + } + + /** + * Perform shutdown activities such as closing open resources + */ + public function shutdown() + { + } + + /** + * Write a message to the log + */ + abstract protected function doWrite(array $event): void; +} + diff --git a/src/Writer/Noop.php b/src/Writer/Noop.php new file mode 100644 index 0000000..701bb3f --- /dev/null +++ b/src/Writer/Noop.php @@ -0,0 +1,15 @@ +stream = $streamOrUrl; + } else { + ErrorHandler::start(); + if (isset($filePermissions) && ! file_exists($streamOrUrl) && is_writable(dirname($streamOrUrl))) { + touch($streamOrUrl); + chmod($streamOrUrl, $filePermissions); + } + $this->stream = fopen($streamOrUrl, $mode, false); + $error = ErrorHandler::stop(); + } + + if (! $this->stream) { + throw new RuntimeException(sprintf( + '"%s" cannot be opened with mode "%s"', + $streamOrUrl, + $mode + ), 0, $error); + } + + if (null !== $logSeparator) { + $this->setLogSeparator($logSeparator); + } + + if ($this->formatter === null) { + $this->formatter = new Simple(); + } + } + + /** + * Write a message to the log. + */ + protected function doWrite(array $event): void + { + $line = $this->formatter->format($event) . $this->logSeparator; + fwrite($this->stream, $line); + } + + /** + * Set log separator string + */ + public function setLogSeparator(string $logSeparator): static + { + $this->logSeparator = $logSeparator; + return $this; + } + + /** + * Get log separator string + */ + public function getLogSeparator(): string + { + return $this->logSeparator; + } + + /** + * Close the stream resource. + */ + public function shutdown(): void + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + } +} diff --git a/src/Writer/WriterInterface.php b/src/Writer/WriterInterface.php new file mode 100644 index 0000000..6621fc5 --- /dev/null +++ b/src/Writer/WriterInterface.php @@ -0,0 +1,31 @@ + Date: Tue, 27 Aug 2024 11:47:31 +0300 Subject: [PATCH 2/7] fix tests, phpcs and psalm --- src/Factory/FilterPluginManagerFactory.php | 2 +- src/Factory/FormatterPluginManagerFactory.php | 2 +- src/Factory/LoggerAbstractServiceFactory.php | 6 ++ src/Factory/ProcessorPluginManagerFactory.php | 2 +- src/Factory/WriterFactory.php | 55 ++------------- src/Factory/WriterPluginManagerFactory.php | 2 +- src/Filter/Priority.php | 12 +++- src/Filter/Regex.php | 15 ++-- src/Filter/SuppressFilter.php | 10 ++- src/Filter/Validator.php | 12 +++- src/Formatter/Base.php | 10 +-- src/Formatter/FormatterInterface.php | 5 +- src/Formatter/Json.php | 4 +- src/Formatter/Simple.php | 13 +++- src/Logger.php | 70 +++++++++++++------ src/LoggerServiceFactory.php | 16 ++++- src/Manager/FilterPluginManager.php | 14 +++- src/Manager/FormatterPluginManager.php | 21 ++++-- src/Manager/ProcessorPluginManager.php | 18 ++++- src/Manager/WriterPluginManager.php | 24 +++++-- src/Processor/Backtrace.php | 10 ++- src/Processor/PsrPlaceholder.php | 10 ++- src/Processor/ReferenceId.php | 3 +- src/Processor/RequestId.php | 3 +- src/Writer/AbstractWriter.php | 38 ++++++---- src/Writer/Stream.php | 23 ++++-- test/ConfigProviderTest.php | 10 +-- .../FilterPluginManagerFactoryTest.php | 2 +- .../FormatterPluginManagerFactoryTest.php | 2 +- .../LoggerAbstractServiceFactoryTest.php | 4 +- .../ProcessorPluginManagerFactoryTest.php | 2 +- .../WriterPluginManagerFactoryTest.php | 2 +- 32 files changed, 273 insertions(+), 149 deletions(-) diff --git a/src/Factory/FilterPluginManagerFactory.php b/src/Factory/FilterPluginManagerFactory.php index b412cbf..1edba16 100644 --- a/src/Factory/FilterPluginManagerFactory.php +++ b/src/Factory/FilterPluginManagerFactory.php @@ -4,8 +4,8 @@ namespace Dot\Log\Factory; -use Exception; use Dot\Log\Manager\FilterPluginManager; +use Exception; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/src/Factory/FormatterPluginManagerFactory.php b/src/Factory/FormatterPluginManagerFactory.php index 5d321b6..1754a31 100644 --- a/src/Factory/FormatterPluginManagerFactory.php +++ b/src/Factory/FormatterPluginManagerFactory.php @@ -4,8 +4,8 @@ namespace Dot\Log\Factory; -use Exception; use Dot\Log\Manager\FormatterPluginManager; +use Exception; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/src/Factory/LoggerAbstractServiceFactory.php b/src/Factory/LoggerAbstractServiceFactory.php index 5e09d77..4355a74 100644 --- a/src/Factory/LoggerAbstractServiceFactory.php +++ b/src/Factory/LoggerAbstractServiceFactory.php @@ -33,6 +33,7 @@ public function __construct(string $configKey = 'dot_log') } /** + * @param string $requestedName * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ @@ -54,6 +55,11 @@ public function canCreate(ContainerInterface $container, $requestedName): bool return isset($config[$parts[1]]); } + /** + * @param string $requestedName + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): Logger { $parts = explode('.', $requestedName); diff --git a/src/Factory/ProcessorPluginManagerFactory.php b/src/Factory/ProcessorPluginManagerFactory.php index 006ea7b..fd3cbd8 100644 --- a/src/Factory/ProcessorPluginManagerFactory.php +++ b/src/Factory/ProcessorPluginManagerFactory.php @@ -4,8 +4,8 @@ namespace Dot\Log\Factory; -use Exception; use Dot\Log\Manager\ProcessorPluginManager; +use Exception; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/src/Factory/WriterFactory.php b/src/Factory/WriterFactory.php index 9ebb970..a3421e3 100644 --- a/src/Factory/WriterFactory.php +++ b/src/Factory/WriterFactory.php @@ -4,12 +4,12 @@ namespace Dot\Log\Factory; -use Laminas\ServiceManager\Exception\InvalidServiceException; use Laminas\ServiceManager\Factory\FactoryInterface; -use Laminas\ServiceManager\ServiceLocatorInterface; -use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use function is_array; +use function is_string; + class WriterFactory implements FactoryInterface { /** @@ -24,6 +24,9 @@ public function __construct(?array $creationOptions = null) } } + /** + * @param string $requestedName + */ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): object { $options = (array) $options; @@ -42,8 +45,7 @@ private function populateOptions( ContainerInterface $container, string $name, string $defaultService - ): array - { + ): array { if (isset($options[$name]) && is_string($options[$name])) { $options[$name] = $container->get($options[$name]); return $options; @@ -57,49 +59,6 @@ private function populateOptions( return $options; } - /** - * Create an instance of the named service. - * - * First, it checks if `$canonicalName` resolves to a class, and, if so, uses - * that value to proxy to `__invoke()`. - * - * Next, if `$requestedName` is non-empty and resolves to a class, this - * method uses that value to proxy to `__invoke()`. - * - * Finally, if the above each fail, it raises an exception. - * - * The approach above is performed as version 2 has two distinct behaviors - * under which factories are invoked: - * - * - If an alias was used, $canonicalName is the resolved name, and - * $requestedName is the service name requested, in which case $canonicalName - * is likely the qualified class name; - * - Otherwise, $canonicalName is the normalized name, and $requestedName - * is the original service name requested (typically the qualified class name). - * - * @throws ContainerExceptionInterface - */ - public function createService( - ServiceLocatorInterface $serviceLocator, - ?string $canonicalName = null, - ?string $requestedName = null - ): object - { - if (is_string($canonicalName) && class_exists($canonicalName)) { - return $this($serviceLocator->getServiceLocator(), $canonicalName, $this->creationOptions); - } - - if (is_string($requestedName) && class_exists($requestedName)) { - return $this($serviceLocator->getServiceLocator(), $requestedName, $this->creationOptions); - } - - throw new InvalidServiceException(sprintf( - '%s requires that the requested name is provided on invocation; ' - . 'please update your tests or consuming container', - self::class - )); - } - public function setCreationOptions(array $creationOptions): void { $this->creationOptions = $creationOptions; diff --git a/src/Factory/WriterPluginManagerFactory.php b/src/Factory/WriterPluginManagerFactory.php index fcec022..ac06921 100644 --- a/src/Factory/WriterPluginManagerFactory.php +++ b/src/Factory/WriterPluginManagerFactory.php @@ -4,8 +4,8 @@ namespace Dot\Log\Factory; -use Exception; use Dot\Log\Manager\WriterPluginManager; +use Exception; use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; diff --git a/src/Filter/Priority.php b/src/Filter/Priority.php index 34c3b3c..26550ab 100644 --- a/src/Filter/Priority.php +++ b/src/Filter/Priority.php @@ -7,6 +7,14 @@ use Dot\Log\Exception\InvalidArgumentException; use Traversable; +use function ctype_digit; +use function gettype; +use function is_array; +use function is_int; +use function iterator_to_array; +use function sprintf; +use function version_compare; + class Priority implements FilterInterface { protected int $priority; @@ -17,7 +25,7 @@ class Priority implements FilterInterface * Filter logging by $priority. By default, it will accept any log * event whose priority value is less than or equal to $priority. */ - public function __construct($priority, $operator = null) + public function __construct(iterable|int $priority, ?string $operator = null) { if ($priority instanceof Traversable) { $priority = iterator_to_array($priority); @@ -40,7 +48,7 @@ public function __construct($priority, $operator = null) /** * Returns TRUE to accept the message, FALSE to block it. */ - public function filter(array $event): bool|int + public function filter(array $event): null|bool|int { return version_compare((string) $event['priority'], (string) $this->priority, $this->operator); } diff --git a/src/Filter/Regex.php b/src/Filter/Regex.php index fafbfeb..d96f4a8 100644 --- a/src/Filter/Regex.php +++ b/src/Filter/Regex.php @@ -9,6 +9,12 @@ use Laminas\Stdlib\ErrorHandler; use Traversable; +use function is_array; +use function iterator_to_array; +use function preg_match; +use function sprintf; +use function var_export; + class Regex implements FilterInterface { /** @@ -18,17 +24,16 @@ class Regex implements FilterInterface /** * Filter out any log messages not matching the pattern + * * @throws ErrorException */ - public function __construct($regex) + public function __construct(iterable $regex) { if ($regex instanceof Traversable) { $regex = iterator_to_array($regex); } - if (is_array($regex)) { - $regex = $regex['regex'] ?? null; - } - ErrorHandler::start(E_WARNING); + $regex = $regex['regex'] ?? null; + ErrorHandler::start(); $result = preg_match($regex, ''); $error = ErrorHandler::stop(); if ($result === false) { diff --git a/src/Filter/SuppressFilter.php b/src/Filter/SuppressFilter.php index 895f2f3..51380bd 100644 --- a/src/Filter/SuppressFilter.php +++ b/src/Filter/SuppressFilter.php @@ -7,6 +7,12 @@ use Dot\Log\Exception\InvalidArgumentException; use Traversable; +use function gettype; +use function is_array; +use function is_bool; +use function iterator_to_array; +use function sprintf; + class SuppressFilter implements FilterInterface { protected bool $accept = true; @@ -14,7 +20,7 @@ class SuppressFilter implements FilterInterface /** * This is a simple boolean filter. */ - public function __construct($suppress = false) + public function __construct(bool|iterable $suppress = false) { if ($suppress instanceof Traversable) { $suppress = iterator_to_array($suppress); @@ -39,7 +45,7 @@ public function __construct($suppress = false) */ public function suppress(bool $suppress): void { - $this->accept = !$suppress; + $this->accept = ! $suppress; } /** diff --git a/src/Filter/Validator.php b/src/Filter/Validator.php index 4de1695..30f6ff3 100644 --- a/src/Filter/Validator.php +++ b/src/Filter/Validator.php @@ -8,6 +8,12 @@ use Laminas\Validator\ValidatorInterface as LaminasValidator; use Traversable; +use function gettype; +use function is_array; +use function is_object; +use function iterator_to_array; +use function sprintf; + class Validator implements FilterInterface { /** @@ -18,7 +24,7 @@ class Validator implements FilterInterface /** * Filter out any log messages not matching the validator */ - public function __construct($validator) + public function __construct(iterable|LaminasValidator $validator) { if ($validator instanceof Traversable && ! $validator instanceof LaminasValidator) { $validator = iterator_to_array($validator); @@ -29,7 +35,7 @@ public function __construct($validator) if (! $validator instanceof LaminasValidator) { throw new InvalidArgumentException(sprintf( 'Parameter of type %s is invalid; must implement Laminas\Validator\ValidatorInterface', - is_object($validator) ? get_class($validator) : gettype($validator) + is_object($validator) ? $validator::class : gettype($validator) )); } $this->validator = $validator; @@ -38,7 +44,7 @@ public function __construct($validator) /** * Returns TRUE to accept the message, FALSE to block it. */ - public function filter(array $event) + public function filter(array $event): bool { return $this->validator->isValid($event['message']); } diff --git a/src/Formatter/Base.php b/src/Formatter/Base.php index b650eba..bce0f91 100644 --- a/src/Formatter/Base.php +++ b/src/Formatter/Base.php @@ -8,7 +8,6 @@ use Traversable; use function defined; -use function get_class; use function get_resource_type; use function gettype; use function is_array; @@ -35,7 +34,7 @@ class Base implements FormatterInterface /** * @see http://php.net/manual/en/function.date.php */ - public function __construct(string|iterable $dateTimeFormat = null) + public function __construct(string|iterable|null $dateTimeFormat = null) { if ($dateTimeFormat instanceof Traversable) { $dateTimeFormat = iterator_to_array($dateTimeFormat); @@ -52,8 +51,11 @@ public function __construct(string|iterable $dateTimeFormat = null) /** * Formats data to be written by the writer. + * + * @psalm-suppress InvalidReturnType + * @psalm-suppress InvalidReturnStatement */ - public function format($event): array|string + public function format(iterable $event): iterable|string { foreach ($event as $key => $value) { // Keep extra as an array @@ -96,7 +98,7 @@ protected function normalize(mixed $value): mixed } elseif (is_array($value)) { $value = @json_encode($value, $jsonFlags); } elseif (is_object($value) && ! method_exists($value, '__toString')) { - $value = sprintf('object(%s) %s', get_class($value), @json_encode($value)); + $value = sprintf('object(%s) %s', $value::class, @json_encode($value)); } elseif (is_resource($value)) { $value = sprintf('resource(%s)', get_resource_type($value)); } elseif (! is_object($value)) { diff --git a/src/Formatter/FormatterInterface.php b/src/Formatter/FormatterInterface.php index 8d90a8f..bff4111 100644 --- a/src/Formatter/FormatterInterface.php +++ b/src/Formatter/FormatterInterface.php @@ -15,10 +15,11 @@ interface FormatterInterface /** * Formats data into a single line to be written by the writer. + * * @return string|array Either a formatted line to write to the log, or the * updated event information to provide to the writer. */ - public function format($event): array|string; + public function format(iterable $event): iterable|string; /** * Get the format specifier for DateTime objects @@ -30,5 +31,5 @@ public function getDateTimeFormat(); * * @see http://php.net/manual/en/function.date.php */ - public function setDateTimeFormat($dateTimeFormat); + public function setDateTimeFormat(string $dateTimeFormat); } diff --git a/src/Formatter/Json.php b/src/Formatter/Json.php index c0a819f..02b4418 100644 --- a/src/Formatter/Json.php +++ b/src/Formatter/Json.php @@ -24,8 +24,10 @@ class Json implements FormatterInterface /** * Formats data into a single line to be written by the writer. + * + * @psalm-suppress InvalidArrayAccess */ - public function format($event): string + public function format(iterable $event): string { if (isset($event['timestamp']) && $event['timestamp'] instanceof DateTime) { $event['timestamp'] = $event['timestamp']->format($this->getDateTimeFormat()); diff --git a/src/Formatter/Simple.php b/src/Formatter/Simple.php index 73645d4..b026de7 100644 --- a/src/Formatter/Simple.php +++ b/src/Formatter/Simple.php @@ -7,6 +7,15 @@ use Dot\Log\Exception\InvalidArgumentException; use Traversable; +use function array_key_exists; +use function count; +use function is_array; +use function is_string; +use function iterator_to_array; +use function rtrim; +use function str_replace; +use function strpos; + class Simple extends Base { public const DEFAULT_FORMAT = '%timestamp% %priorityName% (%priority%): %message% %extra%'; @@ -16,7 +25,7 @@ class Simple extends Base */ protected string $format; - public function __construct($format = null, $dateTimeFormat = null) + public function __construct(iterable|string|null $format = null, string|iterable|null $dateTimeFormat = null) { if ($format instanceof Traversable) { $format = iterator_to_array($format); @@ -39,7 +48,7 @@ public function __construct($format = null, $dateTimeFormat = null) /** * Formats data into a single line to be written by the writer. */ - public function format($event): array|string + public function format(iterable $event): array|string { $output = $this->format; diff --git a/src/Logger.php b/src/Logger.php index e114b28..78bd6e5 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -19,6 +19,39 @@ use Laminas\Stdlib\SplPriorityQueue; use Traversable; +use function array_reverse; +use function count; +use function error_get_last; +use function error_reporting; +use function in_array; +use function is_array; +use function is_object; +use function is_string; +use function method_exists; +use function register_shutdown_function; +use function restore_error_handler; +use function restore_exception_handler; +use function set_error_handler; +use function set_exception_handler; +use function sprintf; +use function var_export; + +use const E_COMPILE_ERROR; +use const E_COMPILE_WARNING; +use const E_CORE_ERROR; +use const E_CORE_WARNING; +use const E_DEPRECATED; +use const E_ERROR; +use const E_NOTICE; +use const E_PARSE; +use const E_RECOVERABLE_ERROR; +use const E_STRICT; +use const E_USER_DEPRECATED; +use const E_USER_ERROR; +use const E_USER_NOTICE; +use const E_USER_WARNING; +use const E_WARNING; + class Logger implements LoggerInterface { /** @@ -89,9 +122,9 @@ class Logger implements LoggerInterface protected SplPriorityQueue $processors; - protected WriterPluginManager $writerPlugins; + protected ?WriterPluginManager $writerPlugins; - protected ProcessorPluginManager $processorPlugins; + protected ?ProcessorPluginManager $processorPlugins; /** * Constructor @@ -101,7 +134,7 @@ class Logger implements LoggerInterface * - exceptionhandler: if true register this logger as exceptionhandler * - errorhandler: if true register this logger as errorhandler */ - public function __construct(iterable $options = null) + public function __construct(?iterable $options = null) { $this->writers = new SplPriorityQueue(); $this->processors = new SplPriorityQueue(); @@ -114,12 +147,6 @@ public function __construct(iterable $options = null) return; } - if (! is_array($options)) { - throw new InvalidArgumentException( - 'Options must be an array or an object implementing \Traversable ' - ); - } - // Inject writer plugin manager, if available if ( isset($options['writer_plugin_manager']) @@ -188,7 +215,7 @@ public function __destruct() } } - public function getWriterPluginManager(): WriterPluginManager + public function getWriterPluginManager(): ?WriterPluginManager { if (null === $this->writerPlugins) { $this->setWriterPluginManager(new WriterPluginManager(new ServiceManager())); @@ -221,7 +248,7 @@ public function addWriter(WriterInterface|string $writer, int $priority = 1, ?ar throw new InvalidArgumentException(sprintf( 'Writer must implement %s\Writer\WriterInterface; received "%s"', __NAMESPACE__, - is_object($writer) ? get_class($writer) : gettype($writer) + $writer::class )); } $this->writers->insert($writer, $priority); @@ -247,7 +274,7 @@ public function setWriters(SplPriorityQueue $writers): static return $this; } - public function getProcessorPluginManager(): ProcessorPluginManager + public function getProcessorPluginManager(): ?ProcessorPluginManager { if (null === $this->processorPlugins) { $this->setProcessorPluginManager(new ProcessorPluginManager(new ServiceManager())); @@ -255,7 +282,7 @@ public function getProcessorPluginManager(): ProcessorPluginManager return $this->processorPlugins; } - public function setProcessorPluginManager($plugins): static + public function setProcessorPluginManager(string|ProcessorPluginManager $plugins): static { if (is_string($plugins)) { $plugins = new $plugins(); @@ -264,7 +291,7 @@ public function setProcessorPluginManager($plugins): static throw new InvalidArgumentException(sprintf( 'processor plugin manager must extend %s\ProcessorPluginManager; received %s', __NAMESPACE__, - is_object($plugins) ? get_class($plugins) : gettype($plugins) + $plugins::class )); } @@ -277,14 +304,17 @@ public function processorPlugin(string $name, ?array $options = null): Processor return $this->getProcessorPluginManager()->get($name, $options); } - public function addProcessor(ProcessorInterface|string $processor, int $priority = 1, ?array $options = null): static - { + public function addProcessor( + ProcessorInterface|string $processor, + int $priority = 1, + ?array $options = null + ): static { if (is_string($processor)) { $processor = $this->processorPlugin($processor, $options); } elseif (! $processor instanceof Processor\ProcessorInterface) { throw new InvalidArgumentException(sprintf( 'Processor must implement Laminas\Log\ProcessorInterface; received "%s"', - is_object($processor) ? get_class($processor) : gettype($processor) + $processor::class )); } $this->processors->insert($processor, $priority); @@ -299,7 +329,7 @@ public function getProcessors(): SplPriorityQueue public function log(int $priority, mixed $message, iterable $extra = []): static { - if (! is_int($priority) || ($priority < 0) || ($priority >= count($this->priorities))) { + if (($priority < 0) || ($priority >= count($this->priorities))) { throw new InvalidArgumentException(sprintf( '$priority must be an integer >= 0 and < %d; received %s', count($this->priorities), @@ -514,9 +544,6 @@ public static function registerExceptionHandler(Logger $logger): bool 'line' => $exception->getLine(), 'trace' => $exception->getTrace(), ]; - if (isset($exception->xdebug_message)) { - $extra['xdebug'] = $exception->xdebug_message; - } $logMessages[] = [ 'priority' => $priority, @@ -541,4 +568,3 @@ public static function unregisterExceptionHandler(): void static::$registeredExceptionHandler = false; } } - diff --git a/src/LoggerServiceFactory.php b/src/LoggerServiceFactory.php index f5d5de1..51bc7cd 100644 --- a/src/LoggerServiceFactory.php +++ b/src/LoggerServiceFactory.php @@ -8,10 +8,24 @@ use Dot\Log\Exception\InvalidArgumentException; use Laminas\ServiceManager\AbstractPluginManager; use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; + +use function gettype; +use function is_array; +use function is_iterable; +use function is_object; +use function is_string; +use function iterator_to_array; class LoggerServiceFactory implements FactoryInterface { + /** + * @param string $requestedName + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): Logger { // Configure the logger @@ -70,7 +84,7 @@ protected function processConfig(array &$config, ContainerInterface $services): foreach ($config['writers'] as $writerConfig) { if (! is_array($writerConfig) && ! $writerConfig instanceof ArrayAccess) { - $type = is_object($writerConfig) ? get_class($writerConfig) : gettype($writerConfig); + $type = is_object($writerConfig) ? $writerConfig::class : gettype($writerConfig); throw new InvalidArgumentException( 'config log.writers[] must contain array or ArrayAccess, ' . $type . ' provided' ); diff --git a/src/Manager/FilterPluginManager.php b/src/Manager/FilterPluginManager.php index 931f1d8..27c90fd 100644 --- a/src/Manager/FilterPluginManager.php +++ b/src/Manager/FilterPluginManager.php @@ -13,13 +13,17 @@ use Laminas\ServiceManager\Exception\InvalidServiceException; use Laminas\ServiceManager\Factory\InvokableFactory; -use function get_class; use function gettype; use function is_object; use function sprintf; +/** + * @template F of FilterPluginManager + * @extends AbstractPluginManager + */ class FilterPluginManager extends AbstractPluginManager { + /** @var string[] */ protected $aliases = [ 'priority' => Priority::class, 'regex' => Regex::class, @@ -28,6 +32,7 @@ class FilterPluginManager extends AbstractPluginManager 'validator' => Validator::class, ]; + /** @var string[]|callable[] */ protected $factories = [ Priority::class => InvokableFactory::class, Regex::class => InvokableFactory::class, @@ -35,10 +40,13 @@ class FilterPluginManager extends AbstractPluginManager Validator::class => InvokableFactory::class, ]; + /** @var ?string */ protected $instanceOf = FilterInterface::class; /** * Allow many filters of the same type + * + * @var bool */ protected $sharedByDefault = false; @@ -47,14 +55,14 @@ class FilterPluginManager extends AbstractPluginManager * * Validates against `$instanceOf`. */ - public function validate($instance): void + public function validate(mixed $instance): void { if (! $instance instanceof $this->instanceOf) { throw new InvalidServiceException(sprintf( '%s can only create instances of %s; %s is invalid', static::class, $this->instanceOf, - is_object($instance) ? get_class($instance) : gettype($instance) + is_object($instance) ? $instance::class : gettype($instance) )); } } diff --git a/src/Manager/FormatterPluginManager.php b/src/Manager/FormatterPluginManager.php index 2083f9f..f94a795 100644 --- a/src/Manager/FormatterPluginManager.php +++ b/src/Manager/FormatterPluginManager.php @@ -10,20 +10,33 @@ use Laminas\ServiceManager\Exception\InvalidServiceException; use Laminas\ServiceManager\Factory\InvokableFactory; +use function gettype; +use function is_object; +use function sprintf; + +/** + * @template F of FormatterPluginManager + * @extends AbstractPluginManager + */ class FormatterPluginManager extends AbstractPluginManager { + /** @var string[] */ protected $aliases = [ - 'simple' => Simple::class, + 'simple' => Simple::class, ]; + /** @var string[]|callable[] */ protected $factories = [ - Simple::class => InvokableFactory::class, + Simple::class => InvokableFactory::class, ]; + /** @var ?string */ protected $instanceOf = FormatterInterface::class; /** * Allow many formatters of the same type + * + * @var bool */ protected $sharedByDefault = false; @@ -32,14 +45,14 @@ class FormatterPluginManager extends AbstractPluginManager * * Validates against `$instanceOf`. */ - public function validate($instance): void + public function validate(mixed $instance): void { if (! $instance instanceof $this->instanceOf) { throw new InvalidServiceException(sprintf( '%s can only create instances of %s; %s is invalid', static::class, $this->instanceOf, - is_object($instance) ? get_class($instance) : gettype($instance) + is_object($instance) ? $instance::class : gettype($instance) )); } } diff --git a/src/Manager/ProcessorPluginManager.php b/src/Manager/ProcessorPluginManager.php index c91834c..0eab386 100644 --- a/src/Manager/ProcessorPluginManager.php +++ b/src/Manager/ProcessorPluginManager.php @@ -13,8 +13,17 @@ use Laminas\ServiceManager\Exception\InvalidServiceException; use Laminas\ServiceManager\Factory\InvokableFactory; +use function gettype; +use function is_object; +use function sprintf; + +/** + * @template P of ProcessorPluginManager + * @extends AbstractPluginManager

+ */ class ProcessorPluginManager extends AbstractPluginManager { + /** @var string[] */ protected $aliases = [ 'backtrace' => Backtrace::class, 'psrplaceholder' => PsrPlaceholder::class, @@ -22,6 +31,7 @@ class ProcessorPluginManager extends AbstractPluginManager 'requestid' => RequestId::class, ]; + /** @var string[]|callable[] */ protected $factories = [ Backtrace::class => InvokableFactory::class, PsrPlaceholder::class => InvokableFactory::class, @@ -29,10 +39,13 @@ class ProcessorPluginManager extends AbstractPluginManager RequestId::class => InvokableFactory::class, ]; + /** @var ?string */ protected $instanceOf = ProcessorInterface::class; /** * Allow many processors of the same type + * + * @var bool */ protected $sharedByDefault = false; @@ -41,16 +54,15 @@ class ProcessorPluginManager extends AbstractPluginManager * * Validates against `$instanceOf`. */ - public function validate($instance): void + public function validate(mixed $instance): void { if (! $instance instanceof $this->instanceOf) { throw new InvalidServiceException(sprintf( '%s can only create instances of %s; %s is invalid', static::class, $this->instanceOf, - is_object($instance) ? get_class($instance) : gettype($instance) + is_object($instance) ? $instance::class : gettype($instance) )); } } } - diff --git a/src/Manager/WriterPluginManager.php b/src/Manager/WriterPluginManager.php index a1be1dc..adeb236 100644 --- a/src/Manager/WriterPluginManager.php +++ b/src/Manager/WriterPluginManager.php @@ -11,11 +11,20 @@ use Laminas\ServiceManager\AbstractPluginManager; use Laminas\ServiceManager\Exception\InvalidServiceException; +use function gettype; +use function is_object; +use function sprintf; + +/** + * @template W of WriterPluginManager + * @extends AbstractPluginManager + */ class WriterPluginManager extends AbstractPluginManager { + /** @var string[] */ protected $aliases = [ - 'noop' => Noop::class, - 'stream' => Stream::class, + 'noop' => Noop::class, + 'stream' => Stream::class, // The following are for backwards compatibility only; users // should update their code to use the noop writer instead. @@ -23,15 +32,19 @@ class WriterPluginManager extends AbstractPluginManager 'laminaslogwriternull' => Noop::class, ]; + /** @var string[]|callable[] */ protected $factories = [ - Noop::class => WriterFactory::class, - Stream::class => WriterFactory::class, + Noop::class => WriterFactory::class, + Stream::class => WriterFactory::class, ]; + /** @var ?string */ protected $instanceOf = WriterInterface::class; /** * Allow many writers of the same type + * + * @var bool */ protected $sharedByDefault = false; @@ -47,9 +60,8 @@ public function validate(mixed $instance): void '%s can only create instances of %s; %s is invalid', static::class, $this->instanceOf, - is_object($instance) ? get_class($instance) : gettype($instance) + is_object($instance) ? $instance::class : gettype($instance) )); } } } - diff --git a/src/Processor/Backtrace.php b/src/Processor/Backtrace.php index b239446..ee5c2ca 100644 --- a/src/Processor/Backtrace.php +++ b/src/Processor/Backtrace.php @@ -4,6 +4,13 @@ namespace Dot\Log\Processor; +use function array_merge; +use function array_shift; +use function debug_backtrace; +use function strpos; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; + class Backtrace implements ProcessorInterface { /** @@ -77,7 +84,7 @@ protected function getBacktrace(): array /** * Determine whether the current frame in the backtrace should be ignored based on the class name */ - protected function shouldIgnoreFrame($class): bool + protected function shouldIgnoreFrame(string $class): bool { foreach ($this->ignoredNamespaces as $ignoredNamespace) { if (false !== strpos($class, $ignoredNamespace)) { @@ -88,4 +95,3 @@ protected function shouldIgnoreFrame($class): bool return false; } } - diff --git a/src/Processor/PsrPlaceholder.php b/src/Processor/PsrPlaceholder.php index 67c98d3..709a481 100644 --- a/src/Processor/PsrPlaceholder.php +++ b/src/Processor/PsrPlaceholder.php @@ -4,6 +4,13 @@ namespace Dot\Log\Processor; +use function gettype; +use function is_object; +use function is_scalar; +use function method_exists; +use function strpos; +use function strtr; + class PsrPlaceholder implements ProcessorInterface { public function process(array $event): array @@ -24,7 +31,7 @@ public function process(array $event): array } if (is_object($val)) { - $replacements['{' . $key . '}'] = '[object ' . get_class($val) . ']'; + $replacements['{' . $key . '}'] = '[object ' . $val::class . ']'; continue; } @@ -35,4 +42,3 @@ public function process(array $event): array return $event; } } - diff --git a/src/Processor/ReferenceId.php b/src/Processor/ReferenceId.php index 2920092..b008d2a 100644 --- a/src/Processor/ReferenceId.php +++ b/src/Processor/ReferenceId.php @@ -26,7 +26,7 @@ public function process(array $event): array return $event; } - public function setReferenceId($identifier): static + public function setReferenceId(string $identifier): static { $this->identifier = $identifier; @@ -38,4 +38,3 @@ public function getReferenceId(): string return $this->getIdentifier(); } } - diff --git a/src/Processor/RequestId.php b/src/Processor/RequestId.php index 4734a0f..d5a0701 100644 --- a/src/Processor/RequestId.php +++ b/src/Processor/RequestId.php @@ -4,6 +4,8 @@ namespace Dot\Log\Processor; +use function md5; + class RequestId implements ProcessorInterface { protected string $identifier; @@ -49,4 +51,3 @@ protected function getIdentifier(): string return $this->identifier; } } - diff --git a/src/Writer/AbstractWriter.php b/src/Writer/AbstractWriter.php index 19846dd..1c94f92 100644 --- a/src/Writer/AbstractWriter.php +++ b/src/Writer/AbstractWriter.php @@ -17,11 +17,21 @@ use Laminas\Stdlib\ErrorHandler; use Traversable; +use function gettype; +use function is_array; +use function is_int; +use function is_object; +use function is_string; +use function iterator_to_array; +use function sprintf; + +use const E_WARNING; + abstract class AbstractWriter implements WriterInterface { - protected FilterPluginManager $filterPlugins; + protected ?FilterPluginManager $filterPlugins; - protected FormatterPluginManager $formatterPlugins; + protected ?FormatterPluginManager $formatterPlugins; protected array $filters = []; @@ -44,7 +54,7 @@ abstract class AbstractWriter implements WriterInterface * - filters: array of filters to add to this filter * - formatter: formatter for this writer */ - public function __construct(iterable $options = null) + public function __construct(?iterable $options = null) { if ($options instanceof Traversable) { $options = iterator_to_array($options); @@ -112,7 +122,7 @@ public function addFilter(int|string|FilterInterface $filter, ?array $options = throw new InvalidArgumentException(sprintf( 'Filter must implement %s\Filter\FilterInterface; received "%s"', __NAMESPACE__, - is_object($filter) ? get_class($filter) : gettype($filter) + is_object($filter) ? $filter::class : gettype($filter) )); } @@ -120,7 +130,7 @@ public function addFilter(int|string|FilterInterface $filter, ?array $options = return $this; } - public function getFilterPluginManager(): FilterPluginManager + public function getFilterPluginManager(): ?FilterPluginManager { if (null === $this->filterPlugins) { $this->setFilterPluginManager(new FilterPluginManager(new ServiceManager())); @@ -128,7 +138,7 @@ public function getFilterPluginManager(): FilterPluginManager return $this->filterPlugins; } - public function setFilterPluginManager($plugins): static + public function setFilterPluginManager(string|FilterPluginManager $plugins): static { if (is_string($plugins)) { $plugins = new $plugins(); @@ -137,7 +147,7 @@ public function setFilterPluginManager($plugins): static throw new InvalidArgumentException(sprintf( 'Writer plugin manager must extend %s; received %s', FilterPluginManager::class, - is_object($plugins) ? get_class($plugins) : gettype($plugins) + $plugins::class )); } @@ -145,12 +155,12 @@ public function setFilterPluginManager($plugins): static return $this; } - public function filterPlugin($name, ?array $options = null) + public function filterPlugin(string $name, ?array $options = null): mixed { return $this->getFilterPluginManager()->get($name, $options); } - public function getFormatterPluginManager(): FormatterPluginManager + public function getFormatterPluginManager(): ?FormatterPluginManager { if (null === $this->formatterPlugins) { $this->setFormatterPluginManager(new FormatterPluginManager(new ServiceManager())); @@ -158,7 +168,7 @@ public function getFormatterPluginManager(): FormatterPluginManager return $this->formatterPlugins; } - public function setFormatterPluginManager($plugins): static + public function setFormatterPluginManager(string|FormatterPluginManager $plugins): static { if (is_string($plugins)) { $plugins = new $plugins(); @@ -168,7 +178,7 @@ public function setFormatterPluginManager($plugins): static sprintf( 'Writer plugin manager must extend %s; received %s', FormatterPluginManager::class, - is_object($plugins) ? get_class($plugins) : gettype($plugins) + $plugins::class ) ); } @@ -177,13 +187,14 @@ public function setFormatterPluginManager($plugins): static return $this; } - public function formatterPlugin($name, ?array $options = null) + public function formatterPlugin(string $name, ?array $options = null): mixed { return $this->getFormatterPluginManager()->get($name, $options); } /** * Log a message to this writer. + * * @throws ErrorException */ public function write(array $event): void @@ -228,7 +239,7 @@ public function setFormatter(FormatterInterface|string $formatter, ?array $optio throw new InvalidArgumentException(sprintf( 'Formatter must implement %s\Formatter\FormatterInterface; received "%s"', __NAMESPACE__, - is_object($formatter) ? get_class($formatter) : gettype($formatter) + is_object($formatter) ? $formatter::class : gettype($formatter) )); } @@ -263,4 +274,3 @@ public function shutdown() */ abstract protected function doWrite(array $event): void; } - diff --git a/src/Writer/Stream.php b/src/Writer/Stream.php index 98da72c..f5c5cb4 100644 --- a/src/Writer/Stream.php +++ b/src/Writer/Stream.php @@ -6,11 +6,28 @@ use Dot\Log\Exception\InvalidArgumentException; use Dot\Log\Exception\RuntimeException; -use Dot\Log\Formatter\Simple; use ErrorException; use Laminas\Stdlib\ErrorHandler; use Traversable; +use function chmod; +use function dirname; +use function fclose; +use function file_exists; +use function fopen; +use function fwrite; +use function get_resource_type; +use function gettype; +use function is_array; +use function is_resource; +use function is_string; +use function is_writable; +use function iterator_to_array; +use function sprintf; +use function touch; + +use const PHP_EOL; + class Stream extends AbstractWriter { /** @@ -93,10 +110,6 @@ public function __construct( if (null !== $logSeparator) { $this->setLogSeparator($logSeparator); } - - if ($this->formatter === null) { - $this->formatter = new Simple(); - } } /** diff --git a/test/ConfigProviderTest.php b/test/ConfigProviderTest.php index eb9f084..a6fabf6 100644 --- a/test/ConfigProviderTest.php +++ b/test/ConfigProviderTest.php @@ -5,11 +5,11 @@ namespace DotTest\Log; use Dot\Log\ConfigProvider; -use Laminas\Log\FilterPluginManager; -use Laminas\Log\FormatterPluginManager; -use Laminas\Log\Logger; -use Laminas\Log\ProcessorPluginManager; -use Laminas\Log\WriterPluginManager; +use Dot\Log\Logger; +use Dot\Log\Manager\FilterPluginManager; +use Dot\Log\Manager\FormatterPluginManager; +use Dot\Log\Manager\ProcessorPluginManager; +use Dot\Log\Manager\WriterPluginManager; use PHPUnit\Framework\TestCase; class ConfigProviderTest extends TestCase diff --git a/test/Factory/FilterPluginManagerFactoryTest.php b/test/Factory/FilterPluginManagerFactoryTest.php index 317ed2a..c153ca9 100644 --- a/test/Factory/FilterPluginManagerFactoryTest.php +++ b/test/Factory/FilterPluginManagerFactoryTest.php @@ -5,7 +5,7 @@ namespace DotTest\Log\Factory; use Dot\Log\Factory\FilterPluginManagerFactory; -use Laminas\Log\FilterPluginManager; +use Dot\Log\Manager\FilterPluginManager; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/test/Factory/FormatterPluginManagerFactoryTest.php b/test/Factory/FormatterPluginManagerFactoryTest.php index 293aadd..e66445b 100644 --- a/test/Factory/FormatterPluginManagerFactoryTest.php +++ b/test/Factory/FormatterPluginManagerFactoryTest.php @@ -5,7 +5,7 @@ namespace DotTest\Log\Factory; use Dot\Log\Factory\FormatterPluginManagerFactory; -use Laminas\Log\FormatterPluginManager; +use Dot\Log\Manager\FormatterPluginManager; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/test/Factory/LoggerAbstractServiceFactoryTest.php b/test/Factory/LoggerAbstractServiceFactoryTest.php index b41f98d..8f0a05e 100644 --- a/test/Factory/LoggerAbstractServiceFactoryTest.php +++ b/test/Factory/LoggerAbstractServiceFactoryTest.php @@ -5,7 +5,7 @@ namespace DotTest\Log\Factory; use Dot\Log\Factory\LoggerAbstractServiceFactory; -use Laminas\Log\Logger; +use Dot\Log\Logger; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -42,7 +42,7 @@ public function testWillInstantiate() { $this->container ->method('has') - ->will($this->onConsecutiveCalls(true, false)); + ->willReturn(true, false, false); $this->container->expects($this->once()) ->method('get') diff --git a/test/Factory/ProcessorPluginManagerFactoryTest.php b/test/Factory/ProcessorPluginManagerFactoryTest.php index 76dd8f4..ee1aeb4 100644 --- a/test/Factory/ProcessorPluginManagerFactoryTest.php +++ b/test/Factory/ProcessorPluginManagerFactoryTest.php @@ -5,7 +5,7 @@ namespace DotTest\Log\Factory; use Dot\Log\Factory\ProcessorPluginManagerFactory; -use Laminas\Log\ProcessorPluginManager; +use Dot\Log\Manager\ProcessorPluginManager; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/test/Factory/WriterPluginManagerFactoryTest.php b/test/Factory/WriterPluginManagerFactoryTest.php index 0322188..f830bbb 100644 --- a/test/Factory/WriterPluginManagerFactoryTest.php +++ b/test/Factory/WriterPluginManagerFactoryTest.php @@ -5,7 +5,7 @@ namespace DotTest\Log\Factory; use Dot\Log\Factory\WriterPluginManagerFactory; -use Laminas\Log\WriterPluginManager; +use Dot\Log\Manager\WriterPluginManager; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; From 50bac84b39aaf400b44c91322ed35dfac64dbe3c Mon Sep 17 00:00:00 2001 From: Sergiu Date: Tue, 27 Aug 2024 11:59:39 +0300 Subject: [PATCH 3/7] updated Logger-psalm return statement error --- src/Logger.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Logger.php b/src/Logger.php index 78bd6e5..aa7af95 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -231,6 +231,8 @@ public function setWriterPluginManager(WriterPluginManager $writerPlugins): stat /** * Get writer instance + * + * @psalm-suppress InvalidReturnStatement */ public function writerPlugin(string $name, ?array $options = null): WriterInterface { @@ -299,6 +301,9 @@ public function setProcessorPluginManager(string|ProcessorPluginManager $plugins return $this; } + /** + * @psalm-suppress InvalidReturnStatement + */ public function processorPlugin(string $name, ?array $options = null): ProcessorInterface { return $this->getProcessorPluginManager()->get($name, $options); From a795c4dc95cb4f02710af8767d3fcafba8516998 Mon Sep 17 00:00:00 2001 From: Sergiu Date: Tue, 27 Aug 2024 12:10:37 +0300 Subject: [PATCH 4/7] updated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa974d1..ba0a721 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## dot-log badges ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-log) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-log/3.4.4) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-log/4.0.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-log)](https://github.com/dotkernel/dot-log/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-log)](https://github.com/dotkernel/dot-log/network) From 3cf57c61edbfd5f73f7dead6a2c2323b90c64081 Mon Sep 17 00:00:00 2001 From: Sergiu Date: Wed, 28 Aug 2024 10:25:46 +0300 Subject: [PATCH 5/7] updated docs --- docs/book/v3/configuring-writer.md | 2 +- docs/book/v3/example-with-formatter.md | 6 +- docs/book/v3/filtering-log-messages.md | 12 +-- docs/book/v3/usage.md | 2 +- docs/book/v4/adding-config-provider.md | 10 +++ docs/book/v4/configuring-writer.md | 46 ++++++++++ docs/book/v4/example-with-formatter.md | 41 +++++++++ docs/book/v4/filtering-log-messages.md | 97 ++++++++++++++++++++++ docs/book/v4/formatting-messages.md | 14 ++++ docs/book/v4/grouping-log-files-by-date.md | 12 +++ docs/book/v4/overview.md | 5 ++ docs/book/v4/usage.md | 26 ++++++ mkdocs.yml | 14 +++- 13 files changed, 274 insertions(+), 13 deletions(-) create mode 100644 docs/book/v4/adding-config-provider.md create mode 100644 docs/book/v4/configuring-writer.md create mode 100644 docs/book/v4/example-with-formatter.md create mode 100644 docs/book/v4/filtering-log-messages.md create mode 100644 docs/book/v4/formatting-messages.md create mode 100644 docs/book/v4/grouping-log-files-by-date.md create mode 100644 docs/book/v4/overview.md create mode 100644 docs/book/v4/usage.md diff --git a/docs/book/v3/configuring-writer.md b/docs/book/v3/configuring-writer.md index 3531f0e..a9ed716 100644 --- a/docs/book/v3/configuring-writer.md +++ b/docs/book/v3/configuring-writer.md @@ -18,7 +18,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Dot\Log\Manager\Logger::ALERT, // this is equal to 1 + 'priority' => \Laminas\Log\Logger::ALERT, // this is equal to 1 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', ], diff --git a/docs/book/v3/example-with-formatter.md b/docs/book/v3/example-with-formatter.md index de6460b..ed8b278 100644 --- a/docs/book/v3/example-with-formatter.md +++ b/docs/book/v3/example-with-formatter.md @@ -15,7 +15,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'priority' => \Laminas\Log\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', // explicitly log all messages @@ -24,12 +24,12 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '>=', - 'priority' => \Dot\Log\Manager\Logger::EMERG, + 'priority' => \Laminas\Log\Logger::EMERG, ], ], ], 'formatter' => [ - 'name' => \Dot\Log\Manager\Formatter\Json::class, + 'name' => \Laminas\Log\Formatter\Json::class, ], ], ], diff --git a/docs/book/v3/filtering-log-messages.md b/docs/book/v3/filtering-log-messages.md index da1aac7..32c185f 100644 --- a/docs/book/v3/filtering-log-messages.md +++ b/docs/book/v3/filtering-log-messages.md @@ -31,7 +31,7 @@ return [ 'writers' => [ 'FileWriter' => [ 'name' => 'FileWriter', - 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'priority' => \Laminas\Log\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/dk.log', 'filters' => [ @@ -39,7 +39,7 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '>=', - 'priority' => \Dot\Log\Manager\Logger::EMERG, + 'priority' => \Laminas\Log\Logger::EMERG, ] ], ], @@ -48,7 +48,7 @@ return [ // Only warnings 'OnlyWarningsWriter' => [ 'name' => 'stream', - 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'priority' => \Laminas\Log\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/warnings_only.log', 'filters' => [ @@ -56,7 +56,7 @@ return [ 'name' => 'priority', 'options' => [ 'operator' => '==', - 'priority' => \Dot\Log\Manager\Logger::WARN, + 'priority' => \Laminas\Log\Logger::WARN, ], ], ], @@ -65,7 +65,7 @@ return [ // Warnings and more important messages 'WarningOrHigherWriter' => [ 'name' => 'stream', - 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'priority' => \Laminas\Log\Logger::ALERT, 'options' => [ 'stream' => __DIR__ . '/../../log/important_messages.log', 'filters' => [ @@ -75,7 +75,7 @@ return [ // note, the smaller the priority, the more important is the message // 0 - emergency, 1 - alert, 2- error, 3 - warn etc. 'operator' => '<=', - 'priority' => \Dot\Log\Manager\Logger::WARN, + 'priority' => \Laminas\Log\Logger::WARN, ], ], ], diff --git a/docs/book/v3/usage.md b/docs/book/v3/usage.md index e479b9c..fea781e 100644 --- a/docs/book/v3/usage.md +++ b/docs/book/v3/usage.md @@ -5,7 +5,7 @@ Basic usage of the logger is illustrated below. The messages are written to see which logs are written and which are not written. ```php -use Dot\Log\Manager\Logger; +use Laminas\Log\Logger; ``` ... diff --git a/docs/book/v4/adding-config-provider.md b/docs/book/v4/adding-config-provider.md new file mode 100644 index 0000000..e591844 --- /dev/null +++ b/docs/book/v4/adding-config-provider.md @@ -0,0 +1,10 @@ +# Adding The Config Provider + +* In `config/config.php` add an entry for the config provider `\Dot\Log\ConfigProvider::class` + * Make sure it is added before with the Application-Specific components, eg.: + * `\Frontend\App\ConfigProvider.php` + * `\Admin\App\ConfigProvider::class` + * `\MyProject\ConfigProvider::class` etc. +* Add the logger configuration in an autoload config file, e.g. you can create `config/autoload/logger.global.php`. Follow the `Configuring the writer(s)` chapter for a simple working example. + +Note: `Dot\Log\ConfigProvider` has an abstract factory `LoggerAbstractServiceFactory::class` which corresponds to the alias, not the class name. Instead of requesting `Laminas\Log\Logger::class` from the container, use `dot-log.my_logger` (or just `my_logger` if using laminas-log). diff --git a/docs/book/v4/configuring-writer.md b/docs/book/v4/configuring-writer.md new file mode 100644 index 0000000..3531f0e --- /dev/null +++ b/docs/book/v4/configuring-writer.md @@ -0,0 +1,46 @@ +# Configuring the writer(s) + +Loggers must have at least one writer. + +A writer is an object that inherits from `Laminas\Log\Writer\AbstractWriter`. A writer's responsibility is to record log data to a storage backend. (from laminas-log's writer documentation) + +## Writing to a file (stream) + +You can separate logs into multiple files using writers and filters. For example *warnings.log*, *errors.log*, *all_messages.log*. + +The following is the simplest example to write all log messages to `/log/dk.log` + +```php +return [ + 'dot_log' => [ + 'loggers' => [ + 'my_logger' => [ + 'writers' => [ + 'FileWriter' => [ + 'name' => 'FileWriter', + 'priority' => \Dot\Log\Manager\Logger::ALERT, // this is equal to 1 + 'options' => [ + 'stream' => __DIR__ . '/../../log/dk.log', + ], + ], + ], + ] + ], + ], +]; +``` + +* The `FileWriter` key is optional, otherwise the writers array would be enumerative instead of associative. +* The `name` key is a developer-provided name for that writer, the writer name key is **mandatory**. + +The `priority` key does not affect the errors that are written. It is a way to organize writers, for example: + +* 1 - FILE +* 2 - SQL +* 3 - E-mail + +The most important things to write in the file, the sql or e-mail are usually fails because the servers can be external and offline, but the file is on the same server. + +The `priority` key is optional. + +The key `stream` is required only if writing into streams/files. diff --git a/docs/book/v4/example-with-formatter.md b/docs/book/v4/example-with-formatter.md new file mode 100644 index 0000000..de6460b --- /dev/null +++ b/docs/book/v4/example-with-formatter.md @@ -0,0 +1,41 @@ +# Example with formatter + +* The log is used through `dot-log` +* The logger name is `my_logger` +* It writes to file: `log/dk.log` +* It is configured to explicitly write all messages +* The messages are formatted as JSON + +```php + [ + 'loggers' => [ + 'my_logger' => [ + 'writers' => [ + 'FileWriter' => [ + 'name' => 'FileWriter', + 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'options' => [ + 'stream' => __DIR__ . '/../../log/dk.log', + // explicitly log all messages + 'filters' => [ + 'allMessages' => [ + 'name' => 'priority', + 'options' => [ + 'operator' => '>=', + 'priority' => \Dot\Log\Manager\Logger::EMERG, + ], + ], + ], + 'formatter' => [ + 'name' => \Dot\Log\Manager\Formatter\Json::class, + ], + ], + ], + ], + ], + ], + ], +]; +``` diff --git a/docs/book/v4/filtering-log-messages.md b/docs/book/v4/filtering-log-messages.md new file mode 100644 index 0000000..da1aac7 --- /dev/null +++ b/docs/book/v4/filtering-log-messages.md @@ -0,0 +1,97 @@ +# Filtering log messages + +The following conforms to the `PSR-3: Logger Interface` document. + +The log levels are in order of priority/importance: + +* emergency (0) +* alert (1) +* critical (2) +* error (3) +* warning (4) +* notice (5) +* info (6) +* debug (7) + +Although the plain Logger in `laminas-log` is not fully compatible with PSR-3, it provides a way to log all of these message types. + +The following example has three file writers using filters: + +* First Example: `FileWriter` - All messages are logged in `/log/dk.log` +* Second Example: `OnlyWarningsWriter` - Only warnings are logged in `/log/warnings.log` +* Third Example: `WarningOrHigherWriter` - All important messages (`warnings` or critical) are logged in `/log/important_messages.log` + +```php + [ + 'loggers' => [ + 'my_logger' => [ + 'writers' => [ + 'FileWriter' => [ + 'name' => 'FileWriter', + 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'options' => [ + 'stream' => __DIR__ . '/../../log/dk.log', + 'filters' => [ + 'allMessages' => [ + 'name' => 'priority', + 'options' => [ + 'operator' => '>=', + 'priority' => \Dot\Log\Manager\Logger::EMERG, + ] + ], + ], + ], + ], + // Only warnings + 'OnlyWarningsWriter' => [ + 'name' => 'stream', + 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'options' => [ + 'stream' => __DIR__ . '/../../log/warnings_only.log', + 'filters' => [ + 'warningOnly' => [ + 'name' => 'priority', + 'options' => [ + 'operator' => '==', + 'priority' => \Dot\Log\Manager\Logger::WARN, + ], + ], + ], + ], + ], + // Warnings and more important messages + 'WarningOrHigherWriter' => [ + 'name' => 'stream', + 'priority' => \Dot\Log\Manager\Logger::ALERT, + 'options' => [ + 'stream' => __DIR__ . '/../../log/important_messages.log', + 'filters' => [ + 'importantMessages' => [ + 'name' => 'priority', + 'options' => [ + // note, the smaller the priority, the more important is the message + // 0 - emergency, 1 - alert, 2- error, 3 - warn etc. + 'operator' => '<=', + 'priority' => \Dot\Log\Manager\Logger::WARN, + ], + ], + ], + ], + ], + ], + ], + ], + ], +]; +``` + +As in the writer configuration, the developer can optionally use keys for associating the filters with a name. + +IMPORTANT NOTE: the operator for more important messages is `<=`, this is because the number representation is smaller for a more important message type. + +The filter added on the first writer is equivalent to not setting a filter, but it was added to illustrate the usage of the operator to explicitly allow all messages. + +More examples on filters: https://docs.laminas.dev/laminas-log/filters/ diff --git a/docs/book/v4/formatting-messages.md b/docs/book/v4/formatting-messages.md new file mode 100644 index 0000000..c0b028a --- /dev/null +++ b/docs/book/v4/formatting-messages.md @@ -0,0 +1,14 @@ +# Formatting Messages + +When using `dot-log` or `laminas-log`, the logged value is not limited to a string. Arrays can be logged as well. For better readability, these arrays can be serialized. Laminas Log provides String, XML, JSON and FirePHP formatting. + +The formatter accepts following parameters: + +* name - the formatter class (it must implement `Laminas\Log\Formatter\FormatterInterface`) +* options - passed to the formatter constructor if required + +The following snippet formats the message as JSON data: + + 'formatter' => [ + 'name' => \Laminas\Log\Formatter\Json::class, + ], diff --git a/docs/book/v4/grouping-log-files-by-date.md b/docs/book/v4/grouping-log-files-by-date.md new file mode 100644 index 0000000..7437b4e --- /dev/null +++ b/docs/book/v4/grouping-log-files-by-date.md @@ -0,0 +1,12 @@ +# Grouping log files by date + +By default, logs will be written to the same file: `log/dk.log`. + +Optionally, you can use date format specifiers wrapped between curly braces in your FileWriter's `stream` option to automatically group your logs by day, week, month, year etc. + +Examples: + +* `log/dk-{Y}-{m}-{d}.log` will create a new log file each day (eg: log/dk-2021-01-01.log) +* `log/dk-{Y}-{W}.log` will create a new log file each week (eg: log/dk-2021-10.log) + +The full list of format specifiers is available [here](https://www.php.net/manual/en/datetime.format.php). diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md new file mode 100644 index 0000000..975a06d --- /dev/null +++ b/docs/book/v4/overview.md @@ -0,0 +1,5 @@ +# Overview + +> dot-log is a wrapper on top of [laminas-log](https://github.com/laminas/laminas-log) +> +> ![OSS Lifecycle](https://img.shields.io/osslifecycle/laminas/laminas-log) diff --git a/docs/book/v4/usage.md b/docs/book/v4/usage.md new file mode 100644 index 0000000..e479b9c --- /dev/null +++ b/docs/book/v4/usage.md @@ -0,0 +1,26 @@ +# Usage + +Basic usage of the logger is illustrated below. + +The messages are written to see which logs are written and which are not written. + +```php +use Dot\Log\Manager\Logger; +``` + +... + +```php +$logger = $container->get('dot-log.my_logger'); + +/** @var Logger $logger */ +$logger->emerg('0 EMERG'); +$logger->alert('1 ALERT'); +$logger->crit('2 CRITICAL'); +$logger->err('3 ERR'); +$logger->warn('4 WARN'); +$logger->notice('5 NOTICE'); +$logger->info('6 INF'); +$logger->debug('7 debug'); +$logger->log(Logger::NOTICE, 'NOTICE from log()'); +``` diff --git a/mkdocs.yml b/mkdocs.yml index a395ddc..49551bc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,11 +2,21 @@ docs_dir: docs/book site_dir: docs/html extra: project: Packages - current_version: v3 + current_version: v4 versions: + - v4 - v3 nav: - Home: index.md + - v4: + - "Overview": v4/overview.md + - "Adding The Config Provider": v4/adding-config-provider.md + - "Configuring the writer(s)": v4/configuring-writer.md + - "Grouping log files by date": v4/grouping-log-files-by-date.md + - "Filtering log messages": v4/filtering-log-messages.md + - "Formatting Messages": v4/formatting-messages.md + - "Example with formatter": v4/example-with-formatter.md + - "Usage": v4/usage.md - v3: - "Overview": v3/overview.md - "Adding The Config Provider": v3/adding-config-provider.md @@ -17,7 +27,7 @@ nav: - "Example with formatter": v3/example-with-formatter.md - "Usage": v3/usage.md site_name: dot-log -site_description: "DotKernel log component extending and customizing laminas-log" +site_description: "DotKernel log component" repo_url: "https://github.com/dotkernel/dot-log" plugins: - search From cd68e12b79054716d9d2a4ac042f527247278fbb Mon Sep 17 00:00:00 2001 From: Sergiu Date: Wed, 28 Aug 2024 11:35:05 +0300 Subject: [PATCH 6/7] updated README.md --- README.md | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index ba0a721..a5619cf 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,5 @@ # dot-log -> [!IMPORTANT] -> dot-log is a wrapper on top of [laminas/laminas-log](https://github.com/laminas/laminas-log) -> -> ![Dynamic JSON Badge](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.github.com%2Frepos%2Flaminas%2Flaminas-log%2Fproperties%2Fvalues&query=%24%5B%3F(%40.property_name%3D%3D%22maintenance-mode%22)%5D.value&label=Maintenance%20Mode) - ## dot-log badges ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-log) @@ -29,13 +24,13 @@ * Open the `Dot\Log\ConfigProvider` * In the dependencies section you will see an abstract factory (`LoggerAbstractServiceFactory::class`) * This class responds to "selectors" instead of class names - Instead of requesting the `Laminas\Log\Logger::class` from the container, `dot-log.my_logger` should be requested (or just `my_logger` if using `laminas-log`) + Instead of requesting the `Dot\Log\Logger::class` from the container, `dot-log.my_logger` should be requested ## Configuring the writer(s) Loggers must have at least one writer. -A writer is an object that inherits from `Laminas\Log\Writer\AbstractWriter`. A writer's responsibility is to record log data to a storage backend. (from laminas-log's writer documentation) +A writer is an object that inherits from `Dot\Log\Writer\AbstractWriter`. A writer's responsibility is to record log data to a storage backend. ### Writing to a file (stream) @@ -67,12 +62,7 @@ return [ * The `FileWriter` key is optional, otherwise the writers array would be enumerative instead of associative. * The writer name key is a developer-provided name for that writer, the writer name key is **mandatory**. -The writer priority key is not affecting the errors that are written, it is a way to organize writers, for example: - -1 - FILE -2 - SQL -3 - E-mail -It is the most important to write in the file, the sql or e-mail are more probably fail because the servers can be external and offline, the file is on the same server. +The writer priority key is not affecting the errors that are written, it is a way to organize writers. The writer priority key is optional. @@ -95,7 +85,7 @@ As per PSR-3 document. The log levels are: emergency (0), alert (1), critical (2), error (3), warn (4), notice (5), info (6), debug (7) (in order of priority/importance) -Although the plain Logger in Laminas Log is not fully compatible with PSR-3, it provides a way to log all of these message types. +Although the plain Logger in Dot Log is not fully compatible with PSR-3, it provides a way to log all of these message types. The following example has three file writers using filters: @@ -178,19 +168,17 @@ The filter added on the first writer is equal to not setting a filter, but it wa It was added opposite to the others just to demonstrate the other operator is also an option. -More examples on filters: https://docs.laminas.dev/laminas-log/filters/ - ## Formatting Messages -When using `dot-log` or `laminas-log`, the logged value is not limited to a string. Arrays can be logged as well. +When using `dot-log`, the logged value is not limited to a string. Arrays can be logged as well. For a better readability, these arrays can be serialized. -Laminas Log provides String formatting, XML, JSON and FirePHP formatting. +Dot Log provides String formatting and JSON formatting. The formatter accepts following parameters: -name - the formatter class (it must implement `Laminas\Log\Formatter\FormatterInterface`) +name - the formatter class (it must implement `Dot\Log\Formatter\FormatterInterface`) options - options to pass to the formatter constructor if required The following formats the message as JSON data: @@ -272,10 +260,4 @@ $logger->debug('7 debug'); $logger->log(Logger::NOTICE, 'NOTICE from log()'); ``` -Sources: - -* https://docs.laminas.dev/laminas-log/ -* https://docs.laminas.dev/laminas-log/writers/ -* https://docs.laminas.dev/laminas-log/filters/ - Extracted from [this article](https://www.dotkernel.com/dotkernel/logging-with-dot-log-in-mezzio-and-dotkernel) From d943bb41cce65d461b9f6d456334537f3bb30e01 Mon Sep 17 00:00:00 2001 From: Sergiu Date: Wed, 28 Aug 2024 12:08:40 +0300 Subject: [PATCH 7/7] updated docs --- docs/book/v4/adding-config-provider.md | 2 +- docs/book/v4/configuring-writer.md | 10 ++-------- docs/book/v4/filtering-log-messages.md | 4 +--- docs/book/v4/formatting-messages.md | 6 +++--- docs/book/v4/overview.md | 4 +--- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/docs/book/v4/adding-config-provider.md b/docs/book/v4/adding-config-provider.md index e591844..2e3ef2b 100644 --- a/docs/book/v4/adding-config-provider.md +++ b/docs/book/v4/adding-config-provider.md @@ -7,4 +7,4 @@ * `\MyProject\ConfigProvider::class` etc. * Add the logger configuration in an autoload config file, e.g. you can create `config/autoload/logger.global.php`. Follow the `Configuring the writer(s)` chapter for a simple working example. -Note: `Dot\Log\ConfigProvider` has an abstract factory `LoggerAbstractServiceFactory::class` which corresponds to the alias, not the class name. Instead of requesting `Laminas\Log\Logger::class` from the container, use `dot-log.my_logger` (or just `my_logger` if using laminas-log). +Note: `Dot\Log\ConfigProvider` has an abstract factory `LoggerAbstractServiceFactory::class` which corresponds to the alias, not the class name. Instead of requesting `Dot\Log\Logger::class` from the container, use `dot-log.my_logger`. diff --git a/docs/book/v4/configuring-writer.md b/docs/book/v4/configuring-writer.md index 3531f0e..cf4b9a3 100644 --- a/docs/book/v4/configuring-writer.md +++ b/docs/book/v4/configuring-writer.md @@ -2,7 +2,7 @@ Loggers must have at least one writer. -A writer is an object that inherits from `Laminas\Log\Writer\AbstractWriter`. A writer's responsibility is to record log data to a storage backend. (from laminas-log's writer documentation) +A writer is an object that inherits from `Dot\Log\Writer\AbstractWriter`. A writer's responsibility is to record log data to a storage backend. ## Writing to a file (stream) @@ -33,13 +33,7 @@ return [ * The `FileWriter` key is optional, otherwise the writers array would be enumerative instead of associative. * The `name` key is a developer-provided name for that writer, the writer name key is **mandatory**. -The `priority` key does not affect the errors that are written. It is a way to organize writers, for example: - -* 1 - FILE -* 2 - SQL -* 3 - E-mail - -The most important things to write in the file, the sql or e-mail are usually fails because the servers can be external and offline, but the file is on the same server. +The `priority` key does not affect the errors that are written. It is a way to organize writers. The `priority` key is optional. diff --git a/docs/book/v4/filtering-log-messages.md b/docs/book/v4/filtering-log-messages.md index da1aac7..914edf4 100644 --- a/docs/book/v4/filtering-log-messages.md +++ b/docs/book/v4/filtering-log-messages.md @@ -13,7 +13,7 @@ The log levels are in order of priority/importance: * info (6) * debug (7) -Although the plain Logger in `laminas-log` is not fully compatible with PSR-3, it provides a way to log all of these message types. +Although the plain Logger in `dot-log` is not fully compatible with PSR-3, it provides a way to log all of these message types. The following example has three file writers using filters: @@ -93,5 +93,3 @@ As in the writer configuration, the developer can optionally use keys for associ IMPORTANT NOTE: the operator for more important messages is `<=`, this is because the number representation is smaller for a more important message type. The filter added on the first writer is equivalent to not setting a filter, but it was added to illustrate the usage of the operator to explicitly allow all messages. - -More examples on filters: https://docs.laminas.dev/laminas-log/filters/ diff --git a/docs/book/v4/formatting-messages.md b/docs/book/v4/formatting-messages.md index c0b028a..4604825 100644 --- a/docs/book/v4/formatting-messages.md +++ b/docs/book/v4/formatting-messages.md @@ -1,14 +1,14 @@ # Formatting Messages -When using `dot-log` or `laminas-log`, the logged value is not limited to a string. Arrays can be logged as well. For better readability, these arrays can be serialized. Laminas Log provides String, XML, JSON and FirePHP formatting. +When using `dot-log`, the logged value is not limited to a string. Arrays can be logged as well. For better readability, these arrays can be serialized. Dot Log provides String and JSON formatting. The formatter accepts following parameters: -* name - the formatter class (it must implement `Laminas\Log\Formatter\FormatterInterface`) +* name - the formatter class (it must implement `Dot\Log\Formatter\FormatterInterface`) * options - passed to the formatter constructor if required The following snippet formats the message as JSON data: 'formatter' => [ - 'name' => \Laminas\Log\Formatter\Json::class, + 'name' => \Dot\Log\Formatter\Json::class, ], diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md index 975a06d..89f6888 100644 --- a/docs/book/v4/overview.md +++ b/docs/book/v4/overview.md @@ -1,5 +1,3 @@ # Overview -> dot-log is a wrapper on top of [laminas-log](https://github.com/laminas/laminas-log) -> -> ![OSS Lifecycle](https://img.shields.io/osslifecycle/laminas/laminas-log) +Robust, composite logger with filtering, formatting, and PSR-3 support.