diff --git a/README.md b/README.md index d041e2d..9f622c0 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,289 @@ [![Latest Stable Version](https://img.shields.io/github/v/release/phoole/logger)](https://packagist.org/packages/phoole/logger) [![License](https://img.shields.io/github/license/phoole/logger)]() -Simple & Slim PSR-3 Logger for PHP +[PSR-3]: http://www.php-fig.org/psr/psr-3/ "PSR-3: Logger Interface" + +Simple & Slim [PSR-3][PSR-3] Logger for PHP. It helps to **'DELIVER CERTAIN MESSAGES TO SOMEWHERE'** + +Installation +--- +Install via the `composer` utility. + +``` +composer require "phoole/logger" +``` + +or add the following lines to your `composer.json` + +```json +{ + "require": { + "phoole/logger": "1.*" + } +} +``` + +Usage +--- + +Create the logger instance with a channel id, + +```php +use Psr\Log\LogLevel; +use Phoole\Logger\Logger; +use Phoole\Logger\Entry\MemoryInfo; +use Phoole\Logger\Handler\SyslogHandler; +use Phoole\Logger\Handler\TerminalHandler; + +// with channel id +$logger = new Logger('MyApp'); + +// log every warning to syslog +$logger->addHandler( + LogLevel::WARNING, + new SyslogHandler() +); + +// log to terminal for MemoryInfo entry +$logger->addHandler( + LogLevel::INFO, + new TerminalHandler(), + MemoryInfo::class // handle this log object only +); + +// log a text message +$logger->warning('a warning message'); + +// log memory usage +$logger->info(new MemoryInfo()); +``` + +Concepts +--- + +- **Log entry** + + *A log entry* is a message in the form of an object. It solves the problem + of **'WHAT TO BE SENT OUT'**. It has a message template, and some processors + to process its context. + + For example, `Entry\MemoryInfo` is a predefined log entry with a message + template of `{memory_used}M memory used , peak usage is {memory_peak}M` + and one `Processor\MemoryProcessor` processor. + + ```php + // with predefined template and processor + $logger->warning(new MemoryInfo()); + + // use new template + $logger->warning(new MemoryInfo('Peak memory usage is {memory_peak}M')); + ``` + + `Entry\LogEntry` is the log entry prototype used whenever text message is + to be logged + + ```php + // using LogEntry + $logger->info('test only'); + ``` + + To define your own log entry, + + ```php + use Phoole\Logger\Entry\LogEntry; + + class MyMessage extends LogEntry + { + // message template + protected $message = 'your {template}'; + } + + // add handler + $logger->addHandler( + 'warning', // level + function(LogEntry $entry) { // a handler + echo (string) $entry; + }, + MyMessage::class // handle this type of message only + ); + + // output: 'your wow' + $logger->error(new MyMessage(), ['template' => 'wow']); + ``` + +- **Processor** + + *Processors* are associated with log entry classes. They solve the problem of + **'WHAT EXTRA INFO TO SENT OUT'**. They will inject information into entries' + context. Processors are `callable(LogEntryInterface $entry)`, + + ```php + use Phoole\Logger\Processor\ProcessorAbstract; + + // closure + $processor1 = function(LogEntry $entry) { + }; + + // invokable object + $processor2 = new class() { + public function __invoke(LogEntry $entry) + { + } + } + + // extends + class Processor3 extends ProcessorAbstract + { + protected function updateContext(array $context): array + { + $context['bingo'] = 'wow'; + return $context; + } + } + ``` + + Processors are attached to log entries either in the entry class definition + as follows, + + ```php + class MyMessage extends LogEntry + { + // message template + protected $message = 'your {template}'; + + // define processors for this class + protected static function classProcessors(): array + { + return [ + function(LogEntry $entry) { + $context = $entry->getContext(); + $context['template'] = 'wow'; + $entry->setContext($context); + }, + new myProcessor(), + ]; + } + } + ``` + + or during the handler attachment + + ```php + use Phoole\Logger\Handler\SyslogHandler; + + // will also add 'Processor1' and 'Processor2' to 'MyMessage' class + $logger->addHandler( + 'info', + new SyslogHandler(), + MyMessage::addProcessor( + new Processor1(), + new Processor2(), + ... + ) + ); + ``` + +- **Handler** + + *Handlers* solve the problem of **'WHERE TO SEND MESSAGE'**. They take a + log entry object and send it to somewhere. + + Handlers takes the form of `callable(LogEntryInterface $entry)` as follows, + + ```php + use Phoole\Logger\Handler\HandlerAbstract; + + $handler1 = function(LogEntry $entry) { + echo (string) $entry; + } + + $handler2 = new class() { + public function __invoke(LogEntry $entry) + { + } + } + + class Handler3 extends HandlerAbstract + { + protected function write(LogEntryInterface $entry) + { + echo $this->>getFormatter()->format($entry); + } + } + ``` + + Handlers are added to the `$logger` with specific log level and type of + log message they are going to handle (default is `LogEntryInterface`). + + ```php + $logger->addHandler( + LogLevel::WARNING, + new TerminalHandler(), + LogEntryInterface::class // this is the default anyway + ); + ``` + +- **Formatter** + + *Formatters* solve the problem of **'HOW MESSAGE WILL BE PRESENTED''**. + Each handler of the type `Handler\HandlerAbstract` may have formatter + specified during its initiation. + + ```php + use Phoole\Logger\Handler\TerminalHandler; + use Phoole\Logger\Formatter\AnsiFormatter; + + // use ANSI Color formatter + $handler = new TerminalHandler(new AnsiFormatter()); + + // add handler handles 'ConsoleMessage' ONLY + $logger->addHandler('debug', $handler, ConsoleMessage::class); + + // log to console + $logger->info(new ConsoleMessage('exited with error.')); + + // this will goes handlers handling 'LogEntry' + $logger->info('exited with error'); + ``` + +APIs +--- + +- `LoggerInterface` related + + See [PSR-3][PSR-3] for standard related APIs. + +- `Phoole\Logger\Logger` related + + - `__construct(string $channel)` + + Create the logger with a channel id. + + - `addHandler(string $level, callable $handler, string $entryClass, int $priority = 50): $this` + + Add one handler to specified channel with the priority. + +- `Phoole\Logger\Entry\LogEntry` related + + - `static function addProcessor(callable ...$callables): string` + + This method will returns called class name. + +Testing +--- + +```bash +$ composer test +``` + +Dependencies +--- + +- PHP >= 7.2.0 + +- phoole/base 1.* + +License +--- + +- [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/composer.json b/composer.json index 3f9c414..da3a081 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "phoole/logger", "type": "library", "license": "Apache-2.0", - "version": "1.0.5", + "version": "1.1.0", "description": "Simple & slim PSR-3 logger for PHP", "keywords": [ "phoole", diff --git a/src/Entry/LogEntry.php b/src/Entry/LogEntry.php index bde5f24..18a5350 100644 --- a/src/Entry/LogEntry.php +++ b/src/Entry/LogEntry.php @@ -7,23 +7,27 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Entry; +use Psr\Log\LogLevel; use Psr\Log\InvalidArgumentException; -use Phoole\Logger\Processor\ProcessorInterface; +use Phoole\Logger\Processor\ProcessorAwareTrait; /** - * Log message + * Log message prototype * * @package Phoole\Logger */ class LogEntry implements LogEntryInterface { use LogLevelTrait; + use ProcessorAwareTrait; /** + * message template + * * @var string */ protected $message = 'log message'; @@ -31,7 +35,7 @@ class LogEntry implements LogEntryInterface /** * @var string */ - protected $level = 'info'; + protected $level = LogLevel::INFO; /** * @var array @@ -48,19 +52,26 @@ public function __construct(string $message = '', array $context = []) $this->message = $message; } $this->context = $context; - foreach ($this->getProcessors() as $processorClass) { - /** @var ProcessorInterface $processor */ - $processor = new $processorClass(); - $processor->process($this); - } } /** * {@inheritDoc} */ - public function getProcessors(): array + public function getMessage(): string + { + return $this->message; + } + + /** + * {@inheritDoc} + */ + public function setLevel(string $level) { - return array(); + if (!isset($this->convert[$level])) { + throw new InvalidArgumentException("unknown log level"); + } + $this->level = $level; + return $this; } /** @@ -74,20 +85,26 @@ public function getLevel(): string /** * {@inheritDoc} */ - public function setLevel(string $level) + public function setContext(array $context) { - if (!isset($this->convert[$level])) { - throw new InvalidArgumentException("unknown log level"); - } - $this->level = $level; + $this->context = $context; return $this; } + /** + * {@inheritDoc} + */ + public function getContext(): array + { + return $this->context; + } + /** * {@inheritDoc} */ public function __toString(): string { + $this->process(); return $this->interpolate($this->getMessage(), $this->getContext()); } @@ -110,29 +127,4 @@ protected function interpolate(string $message, array $context): string } return strtr($message, $replace); } - - /** - * {@inheritDoc} - */ - public function getMessage(): string - { - return $this->message; - } - - /** - * {@inheritDoc} - */ - public function getContext(): array - { - return $this->context; - } - - /** - * {@inheritDoc} - */ - public function setContext(array $context) - { - $this->context = $context; - return $this; - } } \ No newline at end of file diff --git a/src/Entry/LogEntryInterface.php b/src/Entry/LogEntryInterface.php index 03e7801..66abc30 100644 --- a/src/Entry/LogEntryInterface.php +++ b/src/Entry/LogEntryInterface.php @@ -7,40 +7,27 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Entry; use Psr\Log\InvalidArgumentException; +use Phoole\Logger\Processor\ProcessorAwareInterface; /** * Log message * * @package Phoole\Logger */ -interface LogEntryInterface +interface LogEntryInterface extends ProcessorAwareInterface { /** - * Get the text message + * Get the text message template (raw message) * * @return string */ public function getMessage(): string; - /** - * Get the level - * - * @return string - */ - public function getLevel(): string; - - /** - * Get the context - * - * @return array - */ - public function getContext(): array; - /** * @param string $level * @return $this @@ -48,6 +35,13 @@ public function getContext(): array; */ public function setLevel(string $level); + /** + * Get the level + * + * @return string + */ + public function getLevel(): string; + /** * Set context * @@ -57,11 +51,11 @@ public function setLevel(string $level); public function setContext(array $context); /** - * array of processor classname + * Get the context * - * @return string[] + * @return array */ - public function getProcessors(): array; + public function getContext(): array; /** * @return string diff --git a/src/Entry/LogLevelTrait.php b/src/Entry/LogLevelTrait.php index 18ea1fb..e87bf95 100644 --- a/src/Entry/LogLevelTrait.php +++ b/src/Entry/LogLevelTrait.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Entry; diff --git a/src/Entry/SystemLog.php b/src/Entry/MemoryInfo.php similarity index 60% rename from src/Entry/SystemLog.php rename to src/Entry/MemoryInfo.php index 07614f1..76ea1b1 100644 --- a/src/Entry/SystemLog.php +++ b/src/Entry/MemoryInfo.php @@ -7,27 +7,33 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Entry; use Phoole\Logger\Processor\MemoryProcessor; /** - * Log system related message. + * A log entry with predefined message template to log memory usage + * * ```php + * // initiate log with app id * $log = new Logger('MyApp'); + * + * // add handler to this MemoryInfo * $log->addHandler( - * new LogfileHandler('system.log'), * LogLevel::INFO, - * SystemLog::class + * new LogfileHandler('system.log'), + * MemoryInfo::class * ); - * $log->info(new SystemLog()); + * + * // use it + * $log->info(new MemoryInfo()); * ``` * * @package Phoole\Logger */ -class SystemLog extends LogEntry +class MemoryInfo extends LogEntry { /** * default message template @@ -39,10 +45,10 @@ class SystemLog extends LogEntry /** * {@inheritDoc} */ - public function getProcessors(): array + protected static function classProcessors(): array { - return array_merge( - parent::getProcessors(), [MemoryProcessor::class] - ); + return [ + new MemoryProcessor() + ]; } } \ No newline at end of file diff --git a/src/Formatter/AnsiFormatter.php b/src/Formatter/AnsiFormatter.php index c924172..9d45c5c 100644 --- a/src/Formatter/AnsiFormatter.php +++ b/src/Formatter/AnsiFormatter.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Formatter; diff --git a/src/Formatter/DefaultFormatter.php b/src/Formatter/DefaultFormatter.php index 054bae5..6683c8f 100644 --- a/src/Formatter/DefaultFormatter.php +++ b/src/Formatter/DefaultFormatter.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Formatter; @@ -25,13 +25,15 @@ class DefaultFormatter implements FormatterInterface */ public function format(LogEntryInterface $entry): string { - $context = $entry->getContext(); $mesg = ''; + + // channel name + $context = $entry->getContext(); if (isset($context['__channel'])) { $mesg .= '[' . strtoupper($context['__channel']) . '] '; } - $mesg .= $entry . $this->getEol(); - return $mesg; + + return $mesg . \strtoupper($entry->getLevel()) . ': ' . $entry . $this->getEol(); } /** diff --git a/src/Formatter/FormatterAwareInterface.php b/src/Formatter/FormatterAwareInterface.php index 1370d73..f1a9e05 100644 --- a/src/Formatter/FormatterAwareInterface.php +++ b/src/Formatter/FormatterAwareInterface.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Formatter; @@ -20,7 +20,7 @@ interface FormatterAwareInterface { /** * @param FormatterInterface - * @return void + * @return $this */ public function setFormatter(FormatterInterface $formatter); diff --git a/src/Formatter/FormatterAwareTrait.php b/src/Formatter/FormatterAwareTrait.php index a97dcaf..560bc7d 100644 --- a/src/Formatter/FormatterAwareTrait.php +++ b/src/Formatter/FormatterAwareTrait.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Formatter; @@ -38,5 +38,6 @@ public function getFormatter(): FormatterInterface public function setFormatter(FormatterInterface $formatter) { $this->formatter = $formatter; + return $this; } } \ No newline at end of file diff --git a/src/Formatter/FormatterInterface.php b/src/Formatter/FormatterInterface.php index 5bbc98e..82019e8 100644 --- a/src/Formatter/FormatterInterface.php +++ b/src/Formatter/FormatterInterface.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Formatter; diff --git a/src/Handler/EchoHandler.php b/src/Handler/EchoHandler.php index 34fb750..52f902c 100644 --- a/src/Handler/EchoHandler.php +++ b/src/Handler/EchoHandler.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; diff --git a/src/Handler/HandlerAbstract.php b/src/Handler/HandlerAbstract.php index fe23387..6cf4cdc 100644 --- a/src/Handler/HandlerAbstract.php +++ b/src/Handler/HandlerAbstract.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; @@ -22,7 +22,7 @@ * * @package Phoole\Logger */ -abstract class HandlerAbstract implements HandlerInterface, FormatterAwareInterface +abstract class HandlerAbstract implements FormatterAwareInterface { use FormatterAwareTrait; @@ -43,31 +43,31 @@ public function __destruct() } /** - * Close the handler + * @param LogEntryInterface $entry */ - protected function close() + public function __invoke(LogEntryInterface $entry) { + if ($this->isHandling($entry)) { + $this->write($entry); + } } /** - * {@inheritDoc} + * Close the handler if wanted */ - public function handle(LogEntryInterface $entry): LogEntryInterface + protected function close() { - if ($this->isHandling()) { - $this->write($entry); - } - return $entry; } /** * Is this handler handling this log ? * + * @param LogEntryInterface $entry * @return bool */ - protected function isHandling(): bool + protected function isHandling(LogEntryInterface $entry): bool { - return TRUE; + return $entry ? TRUE : TRUE; } /** diff --git a/src/Handler/HandlerAwareInterface.php b/src/Handler/HandlerAwareInterface.php index 0b33f49..aaa9455 100644 --- a/src/Handler/HandlerAwareInterface.php +++ b/src/Handler/HandlerAwareInterface.php @@ -7,12 +7,10 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; -use Traversable; -use LogicException; use Phoole\Logger\Entry\LogEntryInterface; /** @@ -25,25 +23,17 @@ interface HandlerAwareInterface /** * Add a handler * - * @param HandlerInterface $handler - * @param string $level level to handle - * @param string $entryClass the log entry class/interface to handle - * @param int $priority handling priority + * @param string $level level to handle + * @param callable $handler + * @param string|object $entryClass the log entry class/interface to handle + * @param int $priority handling priority * @return $this - * @throws LogicException if entry class unknown etc. + * @throws \InvalidArgumentException if entry class or handler not right */ public function addHandler( - HandlerInterface $handler, string $level, - string $entryClass = LogEntryInterface::class, + callable $handler, + $entryClass = LogEntryInterface::class, int $priority = 50 ); - - /** - * Get handlers handling $level and $entry type - * - * @param LogEntryInterface $entry - * @return Traversable - */ - public function getHandlers(LogEntryInterface $entry): Traversable; } \ No newline at end of file diff --git a/src/Handler/HandlerAwareTrait.php b/src/Handler/HandlerAwareTrait.php index 9452727..f78ba04 100644 --- a/src/Handler/HandlerAwareTrait.php +++ b/src/Handler/HandlerAwareTrait.php @@ -7,14 +7,14 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; -use Traversable; use Phoole\Logger\Entry\LogLevelTrait; use Phoole\Base\Queue\UniquePriorityQueue; use Phoole\Logger\Entry\LogEntryInterface; +use Phoole\Logger\Processor\VerifyCallableTrait; /** * HandlerAwareTrait @@ -25,45 +25,94 @@ trait HandlerAwareTrait { use LogLevelTrait; + use VerifyCallableTrait; /** * queue for the handlers * - * @var HandlerInterface[][] + * @var UniquePriorityQueue[][] */ protected $handlers = []; + /** + * @var array + */ + protected $handlerCache = []; + /** * {@inheritDoc} */ public function addHandler( - HandlerInterface $handler, string $level, - string $entryClass = LogEntryInterface::class, + callable $handler, + $entryClass = LogEntryInterface::class, int $priority = 50 ) { - if (!isset($this->handlers[$level][$entryClass])) { - $this->handlers[$level][$entryClass] = new UniquePriorityQueue(); - } - /** @var UniquePriorityQueue $q */ - $q = $this->handlers[$level][$entryClass]; + // verify parameters + $requiredClass = self::verifyCallable($handler, LogEntryInterface::class); + $entryClass = $this->checkEntry($entryClass, $requiredClass); + + // add handler + $q = $this->handlers[$level][$entryClass] ?? new UniquePriorityQueue(); $q->insert($handler, $priority); + $this->handlers[$level][$entryClass] = $q; + + // clear cache + $this->handlerCache = []; + return $this; } /** - * {@inheritDoc} + * Get handlers handling $level and $entry type + * + * @param LogEntryInterface $entry + * @return \Traversable */ - public function getHandlers(LogEntryInterface $entry): Traversable + protected function getHandlers(LogEntryInterface $entry): \Traversable { - $queue = new UniquePriorityQueue(); + // check cache $level = $entry->getLevel(); + if (isset($this->handlerCache[$level][\get_class($entry)])) { + return $this->handlerCache[$level][\get_class($entry)]; + } + + // find matching handlers + $queue = $this->findHandlers($entry, $level); + + // update cache + $this->handlerCache[$level][\get_class($entry)] = $queue; + + return $queue; + } + + /** + * @param string|object $entryClass + * @param string $requiredClass + * @return string + * @throws \InvalidArgumentException if not valid message entry + */ + protected function checkEntry($entryClass, string $requiredClass): string + { + if (!\is_a($entryClass, $requiredClass, TRUE)) { + throw new \InvalidArgumentException("not a valid entry " . $requiredClass); + } + return \is_string($entryClass) ? $entryClass : \get_class($entryClass); + } + + /** + * @param LogEntryInterface $entry + * @param string $level + * @return UniquePriorityQueue + */ + protected function findHandlers(LogEntryInterface $entry, string $level): UniquePriorityQueue + { + $queue = new UniquePriorityQueue(); foreach ($this->handlers as $l => $qs) { - // match level if (!$this->matchLevel($level, $l)) { continue; } - // match class + /** @var UniquePriorityQueue $q */ foreach ($qs as $class => $q) { if (is_a($entry, $class)) { diff --git a/src/Handler/HandlerInterface.php b/src/Handler/HandlerInterface.php deleted file mode 100644 index 138d45f..0000000 --- a/src/Handler/HandlerInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -checkPath($path); if (file_exists($path)) { @@ -55,7 +52,7 @@ public function __construct( * Check file path * * @param string $path - * @throws LogicException if directory failure etc. + * @throws \LogicException if directory failure etc. */ protected function checkPath(string $path) { @@ -65,10 +62,10 @@ protected function checkPath(string $path) mkdir($dir, 0777, TRUE); } if (!is_dir($dir) || !is_writable($dir)) { - throw new Exception("unable to write to $path"); + throw new \Exception("unable to write to $path"); } - } catch (Throwable $e) { - throw new LogicException($e->getMessage()); + } catch (\Throwable $e) { + throw new \LogicException($e->getMessage()); } } diff --git a/src/Handler/StreamHandler.php b/src/Handler/StreamHandler.php index 7c29d37..9323d44 100644 --- a/src/Handler/StreamHandler.php +++ b/src/Handler/StreamHandler.php @@ -7,11 +7,10 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; -use LogicException; use Phoole\Logger\Entry\LogEntryInterface; use Phoole\Logger\Formatter\FormatterInterface; @@ -40,7 +39,7 @@ public function __construct($stream, ?FormatterInterface $formatter = NULL) * * @param string|resource $path * @return resource - * @throws LogicException if open failure + * @throws \LogicException if open failure */ protected function openStream($path) { @@ -53,7 +52,7 @@ protected function openStream($path) if (is_resource($path)) { return $path; } - throw new LogicException("failed to open stream"); + throw new \LogicException("failed to open stream"); } /** @@ -63,6 +62,7 @@ protected function close() { if ($this->stream) { fclose($this->stream); + $this->stream = FALSE; } } diff --git a/src/Handler/SyslogHandler.php b/src/Handler/SyslogHandler.php index 29bec85..d0de510 100644 --- a/src/Handler/SyslogHandler.php +++ b/src/Handler/SyslogHandler.php @@ -7,24 +7,13 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; -use LogicException; use Psr\Log\LogLevel; use Phoole\Logger\Entry\LogEntryInterface; use Phoole\Logger\Formatter\FormatterInterface; -use const LOG_PID; -use const LOG_ERR; -use const LOG_USER; -use const LOG_CRIT; -use const LOG_INFO; -use const LOG_EMERG; -use const LOG_ALERT; -use const LOG_DEBUG; -use const LOG_NOTICE; -use const LOG_WARNING; /** * log to syslog on UNIX type system @@ -54,14 +43,14 @@ class SyslogHandler extends HandlerAbstract * @access protected */ protected $priorities = [ - LogLevel::DEBUG => LOG_DEBUG, - LogLevel::INFO => LOG_INFO, - LogLevel::NOTICE => LOG_NOTICE, - LogLevel::WARNING => LOG_WARNING, - LogLevel::ERROR => LOG_ERR, - LogLevel::CRITICAL => LOG_CRIT, - LogLevel::ALERT => LOG_ALERT, - LogLevel::EMERGENCY => LOG_EMERG, + LogLevel::DEBUG => \LOG_DEBUG, + LogLevel::INFO => \LOG_INFO, + LogLevel::NOTICE => \LOG_NOTICE, + LogLevel::WARNING => \LOG_WARNING, + LogLevel::ERROR => \LOG_ERR, + LogLevel::CRITICAL => \LOG_CRIT, + LogLevel::ALERT => \LOG_ALERT, + LogLevel::EMERGENCY => \LOG_EMERG, ]; /** @@ -70,8 +59,8 @@ class SyslogHandler extends HandlerAbstract * @param FormatterInterface $formatter */ public function __construct( - int $facility = LOG_USER, - int $logOpts = LOG_PID, + int $facility = \LOG_USER, + int $logOpts = \LOG_PID, ?FormatterInterface $formatter = NULL ) { $this->facility = $facility; @@ -87,7 +76,7 @@ protected function write(LogEntryInterface $entry) $context = $entry->getContext(); $ident = $context['__channel'] ?? 'LOG'; if (!openlog($ident, $this->logopts, $this->facility)) { - throw new LogicException("openlog() failed"); + throw new \LogicException("openlog() failed"); } syslog( $this->priorities[$entry->getLevel()], diff --git a/src/Handler/TerminalHandler.php b/src/Handler/TerminalHandler.php index fcddf61..a21ce0c 100644 --- a/src/Handler/TerminalHandler.php +++ b/src/Handler/TerminalHandler.php @@ -7,12 +7,12 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Handler; -use LogicException; use Phoole\Logger\Formatter\AnsiFormatter; +use Phoole\Logger\Entry\LogEntryInterface; use Phoole\Logger\Formatter\FormatterInterface; /** @@ -31,7 +31,7 @@ public function __construct( ?FormatterInterface $formatter = NULL ) { if (!in_array($stream, ['php://stderr', 'php://stdout'])) { - throw new LogicException("unknown stream"); + throw new \LogicException("unknown stream"); } parent::__construct($stream, $formatter ?? new AnsiFormatter()); } @@ -39,7 +39,7 @@ public function __construct( /** * {@inheritDoc} */ - protected function isHandling(): bool + protected function isHandling(LogEntryInterface $entry): bool { return 'cli' === php_sapi_name(); } diff --git a/src/Logger.php b/src/Logger.php index ab4274a..f8e1548 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger; @@ -15,7 +15,6 @@ use Psr\Log\LoggerInterface; use Phoole\Logger\Entry\LogEntry; use Phoole\Logger\Entry\LogEntryInterface; -use Phoole\Logger\Handler\HandlerInterface; use Phoole\Logger\Handler\HandlerAwareTrait; use Phoole\Logger\Handler\HandlerAwareInterface; @@ -35,6 +34,8 @@ class Logger implements LoggerInterface, HandlerAwareInterface protected $channel; /** + * Channel usually is APP ID + * * @param string $channel */ public function __construct(string $channel) @@ -47,12 +48,10 @@ public function __construct(string $channel) */ public function log($level, $message, array $context = array()) { - // init the log entry - $entry = $this->initEntry($message, $level, $context); + $entry = ($this->initEntry($message, $level, $context))->process(); - /** @var HandlerInterface $handler */ foreach ($this->getHandlers($entry) as $handler) { - $entry = $handler->handle($entry); + $handler($entry); } } @@ -61,14 +60,13 @@ public function log($level, $message, array $context = array()) * @param string $level * @param array $context * @return LogEntryInterface + * @throws \InvalidArgumentException if message not right */ protected function initEntry($message, string $level, array $context): LogEntryInterface { - if (is_object($message) && $message instanceof LogEntryInterface) { - $entry = $message; - } else { - $entry = new LogEntry($message); - } + $entry = $this->validate($message); + + // update channel name in context $this->setChannel($context); return $entry @@ -77,6 +75,25 @@ protected function initEntry($message, string $level, array $context): LogEntryI } /** + * @param $message + * @return LogEntryInterface + * @throws \InvalidArgumentException if message not right + */ + protected function validate($message): LogEntryInterface + { + if (is_string($message)) { + $entry = new LogEntry($message); + } elseif (is_object($message) && $message instanceof LogEntryInterface) { + $entry = $message; + } else { + throw new \InvalidArgumentException("not valid message " . (string) $message); + } + return $entry; + } + + /** + * Set channel name in context + * * @param array &$context */ protected function setChannel(array &$context) diff --git a/src/Processor/MemoryProcessor.php b/src/Processor/MemoryProcessor.php index 5432a84..369bb5c 100644 --- a/src/Processor/MemoryProcessor.php +++ b/src/Processor/MemoryProcessor.php @@ -7,12 +7,12 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Processor; /** - * MemoryProcessor + * Add system memory usage to log entry's context * * @package Phoole\Logger */ diff --git a/src/Processor/ProcessorAbstract.php b/src/Processor/ProcessorAbstract.php index 88aa732..1f0fe7b 100644 --- a/src/Processor/ProcessorAbstract.php +++ b/src/Processor/ProcessorAbstract.php @@ -7,7 +7,7 @@ * @package Phoole\Logger * @copyright Copyright (c) 2019 Hong Zhang */ -declare(strict_types = 1); +declare(strict_types=1); namespace Phoole\Logger\Processor; @@ -18,12 +18,14 @@ * * @package Phoole\Logger */ -abstract class ProcessorAbstract implements ProcessorInterface +abstract class ProcessorAbstract { /** - * {@inheritDoc} + * make it an invokable + * + * @param LogEntryInterface $entry */ - public function process(LogEntryInterface $entry) + public function __invoke(LogEntryInterface $entry) { $context = $entry->getContext(); $entry->setContext($this->updateContext($context)); @@ -32,6 +34,14 @@ public function process(LogEntryInterface $entry) /** * update info in the $context * + * ```php + * protected function updateContext(array $context): array + * { + * $context['bingo'] = 'wow'; + * return $context; + * } + * ``` + * * @param array $context * @return array */ diff --git a/src/Processor/ProcessorAwareInterface.php b/src/Processor/ProcessorAwareInterface.php new file mode 100644 index 0000000..f1f231b --- /dev/null +++ b/src/Processor/ProcessorAwareInterface.php @@ -0,0 +1,36 @@ +isProcessed) { + foreach (static::getProcessors() as $processor) { + $processor($this); + } + $this->isProcessed = TRUE; + } + return $this; + } + + /** + * Set processors for THIS class + * + * @param callable ...$callables + * @return string called class name + * @throws \LogicException if not valid processor found + */ + public static function addProcessor(callable ...$callables): string + { + static::initProcessors(); + + $class = \get_called_class(); + $queue = self::$processors[$class]; + + foreach ($callables as $callable) { + static::verifyCallable($callable); + $queue->insert($callable); + } + return $class; + } + + /** + * get related processors + * + * @return iterable + */ + protected static function getProcessors(): iterable + { + $queue = new UniquePriorityQueue(); + + /** @var string $class */ + foreach (static::getClassTree() as $class) { + if (\method_exists($class, 'initProcessors')) { + $class::initProcessors(); + } + $queue = $queue->combine(self::$processors[$class]); + } + return $queue; + } + + /** + * convert predefined processors from classProcessors() + */ + protected static function initProcessors() + { + $class = \get_called_class(); + if (!isset(self::$processors[$class])) { + self::$processors[$class] = new UniquePriorityQueue(); + foreach (static::classProcessors() as $processor) { + static::addProcessor($processor); + } + } + } + + /** + * Define processors for THIS class + * + * @return array + */ + protected static function classProcessors(): array + { + return []; + } + + /** + * @return array + */ + protected static function getClassTree(): array + { + $tree = []; + $class = \get_called_class(); + while ($class) { + $tree[] = $class; + $class = \get_parent_class($class); + } + return \array_reverse($tree); + } +} \ No newline at end of file diff --git a/src/Processor/ProcessorInterface.php b/src/Processor/ProcessorInterface.php deleted file mode 100644 index 0ef712e..0000000 --- a/src/Processor/ProcessorInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -getClass()->getName(), $className, TRUE) + ) { + throw new \InvalidArgumentException("non valid processor found"); + } + return $parameters[0]->getClass()->getName(); + } catch (\Throwable $e) { + throw new \LogicException($e->getMessage()); + } + } + + /** + * Get callable parameters + * + * @param callable $callable + * @return \ReflectionParameter[] + * @throws \InvalidArgumentException if something goes wrong + */ + protected static function getCallableParameters(callable $callable): array + { + try { + if (is_array($callable)) { // [class, method] + $reflector = new \ReflectionClass($callable[0]); + $method = $reflector->getMethod($callable[1]); + } elseif (is_string($callable) || $callable instanceof \Closure) { // function + $method = new \ReflectionFunction($callable); + } else { // __invokable + $reflector = new \ReflectionClass($callable); + $method = $reflector->getMethod('__invoke'); + } + } catch (\Throwable $e) { + throw new \InvalidArgumentException($e->getMessage()); + } + return $method->getParameters(); + } +} \ No newline at end of file diff --git a/tests/Entry/LogEntryTest.php b/tests/Entry/LogEntryTest.php index abf3489..4bafd3c 100644 --- a/tests/Entry/LogEntryTest.php +++ b/tests/Entry/LogEntryTest.php @@ -1,6 +1,6 @@ obj = new LogEntry(); + $this->ref = new \ReflectionClass(get_class($this->obj)); + } + + protected function tearDown(): void + { + $this->obj = $this->ref = NULL; + parent::tearDown(); + } + + protected function invokeMethod($methodName, array $parameters = array()) + { + $method = $this->ref->getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($this->obj, $parameters); + } + /** * @covers Phoole\Logger\Entry\LogEntry::__construct() */ public function testConstruct() { - $obj = new LogEntry('new {msg}', ['msg' => 'message']); + $obj = new LogEntry('new {msg} {msg}', ['msg' => 'message']); $this->assertEquals( - 'new message', + 'new message message', (string) $obj ); } @@ -64,14 +84,6 @@ public function testGetContext() $this->assertEquals($c, $this->obj->getContext()); } - /** - * @covers Phoole\Logger\Entry\LogEntry::getProcessors() - */ - public function testGetProcessors() - { - $this->assertEquals([], $this->obj->getProcessors()); - } - /** * @covers Phoole\Logger\Entry\LogEntry::__toString() */ @@ -96,24 +108,4 @@ public function testInterpolate() $this->invokeMethod('interpolate', [$message, $context]) ); } - - protected function invokeMethod($methodName, array $parameters = array()) - { - $method = $this->ref->getMethod($methodName); - $method->setAccessible(TRUE); - return $method->invokeArgs($this->obj, $parameters); - } - - protected function setUp(): void - { - parent::setUp(); - $this->obj = new LogEntry(); - $this->ref = new \ReflectionClass(get_class($this->obj)); - } - - protected function tearDown(): void - { - $this->obj = $this->ref = NULL; - parent::tearDown(); - } } \ No newline at end of file diff --git a/tests/Entry/MemoryInfoTest.php b/tests/Entry/MemoryInfoTest.php new file mode 100644 index 0000000..24769fa --- /dev/null +++ b/tests/Entry/MemoryInfoTest.php @@ -0,0 +1,52 @@ +obj = new MemoryInfo(); + $this->ref = new \ReflectionClass(get_class($this->obj)); + } + + protected function tearDown(): void + { + $this->obj = $this->ref = NULL; + parent::tearDown(); + } + + protected function invokeMethod(object $object, $methodName, array $parameters = array()) + { + $ref = new \ReflectionClass(get_class($object)); + $method = $ref->getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($object, $parameters); + } + + /** + * @covers Phoole\Logger\Entry\MemoryInfo::process() + */ + public function testProcess() + { + $this->obj->process(); + $queue = $this->invokeMethod($this->obj, 'getProcessors'); + $this->assertEquals(1, count($queue)); + + $context = $this->obj->getContext(); + $this->assertTrue(isset($context['memory_used'])); + + $this->expectOutputRegex('/peak usage/'); + echo (string) $this->obj; + } +} \ No newline at end of file diff --git a/tests/Entry/SystemLogTest.php b/tests/Entry/SystemLogTest.php deleted file mode 100644 index ffb22e9..0000000 --- a/tests/Entry/SystemLogTest.php +++ /dev/null @@ -1,49 +0,0 @@ -obj->getProcessors(); - $this->assertTrue(1 === count($a)); - $this->assertEquals( - MemoryProcessor::class, - $a[0] - ); - } - - protected function setUp(): void - { - parent::setUp(); - $this->obj = new SystemLog(); - $this->ref = new \ReflectionClass(get_class($this->obj)); - } - - protected function tearDown(): void - { - $this->obj = $this->ref = NULL; - parent::tearDown(); - } - - protected function invokeMethod($methodName, array $parameters = array()) - { - $method = $this->ref->getMethod($methodName); - $method->setAccessible(TRUE); - return $method->invokeArgs($this->obj, $parameters); - } -} \ No newline at end of file diff --git a/tests/Formatter/AnsiFormatterTest.php b/tests/Formatter/AnsiFormatterTest.php index 8f9ecd0..1d8e814 100644 --- a/tests/Formatter/AnsiFormatterTest.php +++ b/tests/Formatter/AnsiFormatterTest.php @@ -1,6 +1,6 @@ 'PHOOLE', 'wow' => 'bingo']); - $m->setLevel('error'); - $this->expectOutputRegex('/test bingo/'); - echo $this->obj->format($m); - } - protected function setUp(): void { parent::setUp(); @@ -44,4 +33,15 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Formatter\AnsiFormatter::format() + */ + public function testFormatter() + { + $m = new LogEntry('test {wow}', ['__channel' => 'PHOOLE', 'wow' => 'bingo']); + $m->setLevel('error'); + $this->expectOutputRegex('/test bingo/'); + echo $this->obj->format($m); + } } \ No newline at end of file diff --git a/tests/Formatter/DefaultFormatterTest.php b/tests/Formatter/DefaultFormatterTest.php index e2e0ff8..ec7a38c 100644 --- a/tests/Formatter/DefaultFormatterTest.php +++ b/tests/Formatter/DefaultFormatterTest.php @@ -1,6 +1,6 @@ 'PHOOLE', 'wow' => 'bingo']); - $s = $this->obj->format($m); - $this->assertEquals( - '[PHOOLE] test bingo', - trim($s) - ); - } - protected function setUp(): void { parent::setUp(); @@ -46,4 +33,17 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Formatter\DefaultFormatter::format() + */ + public function testFormatter() + { + $m = new LogEntry('test {wow}', ['__channel' => 'PHOOLE', 'wow' => 'bingo']); + $s = $this->obj->format($m); + $this->assertEquals( + '[PHOOLE] INFO: test bingo', + trim($s) + ); + } } \ No newline at end of file diff --git a/tests/Formatter/FormatterAwareTraitTest.php b/tests/Formatter/FormatterAwareTraitTest.php index 9573708..687c80f 100644 --- a/tests/Formatter/FormatterAwareTraitTest.php +++ b/tests/Formatter/FormatterAwareTraitTest.php @@ -1,6 +1,6 @@ obj->setFormatter($f); - $this->assertTrue($f === $this->obj->getFormatter()); - } - - /** - * @covers Phoole\Logger\Formatter\FormatterAwareTrait::getFormatter() - */ - public function testGetFormatter() - { - $this->expectExceptionMessage('null'); - $this->obj->getFormatter(); - } - protected function setUp(): void { parent::setUp(); @@ -58,4 +39,23 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Formatter\FormatterAwareTrait::setFormatter() + */ + public function testSetFormatter() + { + $f = new DefaultFormatter; + $this->obj->setFormatter($f); + $this->assertTrue($f === $this->obj->getFormatter()); + } + + /** + * @covers Phoole\Logger\Formatter\FormatterAwareTrait::getFormatter() + */ + public function testGetFormatter() + { + $this->expectExceptionMessage('null'); + $this->obj->getFormatter(); + } } \ No newline at end of file diff --git a/tests/Handler/EchoHandlerTest.php b/tests/Handler/EchoHandlerTest.php index 6a8e274..419cbe5 100644 --- a/tests/Handler/EchoHandlerTest.php +++ b/tests/Handler/EchoHandlerTest.php @@ -1,6 +1,6 @@ expectOutputRegex('/test/'); - $this->obj->handle($m); - } - protected function setUp(): void { parent::setUp(); @@ -43,4 +33,15 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Handler\EchoHandler::__invoke() + */ + public function testInvoke() + { + $m = new LogEntry('test'); + $this->expectOutputRegex('/test/'); + $handler = $this->obj; + $handler($m); + } } \ No newline at end of file diff --git a/tests/Handler/HandlerAbstractTest.php b/tests/Handler/HandlerAbstractTest.php index bfeb155..06bb05c 100644 --- a/tests/Handler/HandlerAbstractTest.php +++ b/tests/Handler/HandlerAbstractTest.php @@ -1,6 +1,6 @@ assertTrue($this->obj->getFormatter() instanceof DefaultFormatter); - - $obj = new myHandler(new AnsiFormatter()); - $this->assertTrue($obj->getFormatter() instanceof AnsiFormatter); - } - - /** - * @covers Phoole\Logger\Handler\HandlerAbstract::handle() - */ - public function testHandle() - { - $this->expectOutputString('test'); - $this->obj->handle(new LogEntry('test')); - } - protected function setUp(): void { parent::setUp(); @@ -64,4 +44,25 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Handler\HandlerAbstract::__construct() + */ + public function testConstruct() + { + $this->assertTrue($this->obj->getFormatter() instanceof DefaultFormatter); + + $obj = new myHandler(new AnsiFormatter()); + $this->assertTrue($obj->getFormatter() instanceof AnsiFormatter); + } + + /** + * @covers Phoole\Logger\Handler\HandlerAbstract::__invoke() + */ + public function testInvoke() + { + $this->expectOutputString('test'); + $handler = $this->obj; + $handler(new LogEntry('test')); + } } \ No newline at end of file diff --git a/tests/Handler/HandlerAwareTraitTest.php b/tests/Handler/HandlerAwareTraitTest.php index 75c8bae..a7b233d 100644 --- a/tests/Handler/HandlerAwareTraitTest.php +++ b/tests/Handler/HandlerAwareTraitTest.php @@ -1,13 +1,13 @@ file = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'handlerAware'; + $this->file2 = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'handlerAware2'; + $this->obj = new myHandlerAware(); + $this->ref = new \ReflectionClass(get_class($this->obj)); + } + + protected function tearDown(): void + { + if (\file_exists($this->file)) { + unlink($this->file); + } + if (\file_exists($this->file2)) { + unlink($this->file2); + } + $this->obj = $this->ref = NULL; + parent::tearDown(); + } + + protected function invokeMethod($methodName, array $parameters = array()) + { + $method = $this->ref->getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($this->obj, $parameters); + } + /** * @covers Phoole\Logger\Handler\HandlerAwareTrait::addHandler() */ public function testAddHandler() { $filelog = new LogfileHandler($this->file); - $this->obj->addHandler( - $filelog, - LogLevel::ERROR - ); + $this->obj->addHandler(LogLevel::ERROR, $filelog); $m = (new LogEntry())->setLevel(LogLevel::INFO); - $h = $this->obj->getHandlers($m); + $h = $this->invokeMethod('getHandlers', [$m]); $this->assertEquals(0, count($h)); $m = (new LogEntry())->setLevel(LogLevel::ERROR); - $h = $this->obj->getHandlers($m); + $h = $this->invokeMethod('getHandlers', [$m]); $this->assertEquals(1, count($h)); } @@ -55,47 +80,20 @@ public function testGetHandlers() $file = new LogfileHandler($this->file); $file2 = new LogfileHandler($this->file2); - $this->obj->addHandler( - $file, - LogLevel::INFO - ); + $this->obj->addHandler(LogLevel::INFO, $file); $this->obj->addHandler( - $file2, LogLevel::ERROR, - SystemLog::class + $file2, + MemoryInfo::class ); $m = (new LogEntry())->setLevel(LogLevel::ALERT); - $h = $this->obj->getHandlers($m); + $h = $this->invokeMethod('getHandlers', [$m]); $this->assertEquals(1, count($h)); - $m = (new SystemLog())->setLevel(LogLevel::ALERT); - $h = $this->obj->getHandlers($m); + $m = (new MemoryInfo())->setLevel(LogLevel::ALERT); + $h = $this->invokeMethod('getHandlers', [$m]); $this->assertEquals(2, count($h)); } - - protected function setUp(): void - { - parent::setUp(); - $this->file = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'handlerAware'; - $this->file2 = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'handlerAware2'; - $this->obj = new myHandlerAware(); - $this->ref = new \ReflectionClass(get_class($this->obj)); - } - - protected function tearDown(): void - { - //@unlink($this->file); - //@unlink($this->file2); - $this->obj = $this->ref = NULL; - parent::tearDown(); - } - - protected function invokeMethod($methodName, array $parameters = array()) - { - $method = $this->ref->getMethod($methodName); - $method->setAccessible(TRUE); - return $method->invokeArgs($this->obj, $parameters); - } } \ No newline at end of file diff --git a/tests/Handler/LogfileHandlerTest.php b/tests/Handler/LogfileHandlerTest.php index 8d4087a..af81288 100644 --- a/tests/Handler/LogfileHandlerTest.php +++ b/tests/Handler/LogfileHandlerTest.php @@ -1,6 +1,6 @@ file = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'logfileTest'; + $this->obj = new LogfileHandler($this->file); + $this->ref = new \ReflectionClass(get_class($this->obj)); + } + + protected function tearDown(): void + { + unlink($this->file); + $this->obj = $this->ref = NULL; + parent::tearDown(); + } + + protected function invokeMethod($methodName, array $parameters = array()) + { + $method = $this->ref->getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($this->obj, $parameters); + } + /** * @covers Phoole\Logger\Handler\LogfileHandler::__construct() */ @@ -40,38 +62,17 @@ public function testDoRotation() unlink($new); } - protected function invokeMethod($methodName, array $parameters = array()) - { - $method = $this->ref->getMethod($methodName); - $method->setAccessible(TRUE); - return $method->invokeArgs($this->obj, $parameters); - } - /** - * @covers Phoole\Logger\Handler\LogfileHandler::handle() + * @covers Phoole\Logger\Handler\LogfileHandler::__invoke() */ - public function testHandle() + public function testInvoke() { $m = new LogEntry('test'); - $this->obj->handle($m); + $handler = $this->obj; + $handler($m); $this->assertEquals( - 'test', + 'INFO: test', trim(file_get_contents($this->file)) ); } - - protected function setUp(): void - { - parent::setUp(); - $this->file = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'logfileTest'; - $this->obj = new LogfileHandler($this->file); - $this->ref = new \ReflectionClass(get_class($this->obj)); - } - - protected function tearDown(): void - { - unlink($this->file); - $this->obj = $this->ref = NULL; - parent::tearDown(); - } } \ No newline at end of file diff --git a/tests/Handler/StreamHandlerTest.php b/tests/Handler/StreamHandlerTest.php index 1b40ab7..62c69a2 100644 --- a/tests/Handler/StreamHandlerTest.php +++ b/tests/Handler/StreamHandlerTest.php @@ -1,6 +1,6 @@ assertTrue(file_exists($this->file)); - } - - /** - * @covers Phoole\Logger\Handler\StreamHandler::handle() - */ - public function testHandle() - { - $m = new LogEntry('test'); - $this->obj->handle($m); - $this->assertEquals( - 'test', - trim(file_get_contents($this->file)) - ); - } - protected function setUp(): void { parent::setUp(); @@ -58,4 +37,26 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Handler\StreamHandler::__construct() + */ + public function testConstruct() + { + $this->assertTrue(file_exists($this->file)); + } + + /** + * @covers Phoole\Logger\Handler\StreamHandler::__invoke() + */ + public function testInvoke() + { + $m = new LogEntry('test'); + $handler = $this->obj; + $handler($m); + $this->assertEquals( + 'INFO: test', + trim(file_get_contents($this->file)) + ); + } } \ No newline at end of file diff --git a/tests/Handler/TerminalHandlerTest.php b/tests/Handler/TerminalHandlerTest.php index b34f394..118994e 100644 --- a/tests/Handler/TerminalHandlerTest.php +++ b/tests/Handler/TerminalHandlerTest.php @@ -1,6 +1,6 @@ expectExceptionMessage('unknown stream'); - $obj2 = new TerminalHandler('test'); - } - protected function setUp(): void { parent::setUp(); @@ -42,4 +32,14 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Handler\TerminalHandler::__construct() + */ + public function testConstruct() + { + $obj1 = new TerminalHandler('php://stdout'); + $this->expectExceptionMessage('unknown stream'); + $obj2 = new TerminalHandler('test'); + } } \ No newline at end of file diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 01e9e5e..784276c 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -1,14 +1,20 @@ obj = new Logger('LOG'); + $this->ref = new \ReflectionClass(get_class($this->obj)); + } + + protected function tearDown(): void + { + $this->obj = $this->ref = NULL; + parent::tearDown(); + } + + protected function invokeMethod($methodName, array $parameters = array()) + { + $method = $this->ref->getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($this->obj, $parameters); + } + /** * @covers Phoole\Logger\Logger::log() */ public function testLog() { - $this->obj->addHandler( - new EchoHandler(), - LogLevel::INFO - ); - $this->expectOutputRegex('/test.*peak.*peak.*/s'); + $echoHandler = new EchoHandler(); - $this->obj->info('test'); + $this->obj->addHandler(LogLevel::INFO, $echoHandler); + $this->expectOutputRegex('/test.*peak.*/s'); - $this->obj->addHandler( - new EchoHandler(), - LogLevel::ALERT, - SystemLog::class - ); + $this->obj->info('test'); - $this->obj->alert(new SystemLog()); - $this->assertTrue(TRUE); + $this->obj->addHandler(LogLevel::ALERT, $echoHandler, MemoryInfo::class); + $this->obj->alert(new MemoryInfo()); } /** + * unknown log level + * * @covers Phoole\Logger\Logger::log() */ public function testLog2() { - $this->expectExceptionMessage('unknown log'); + $this->expectExceptionMessage('unknown log level'); $this->obj->log('test', 'test'); } - protected function setUp(): void + /** + * closure as handler + * + * @covers Phoole\Logger\Logger::log() + */ + public function testLog3() { - parent::setUp(); - $this->obj = new Logger('LOG'); - $this->ref = new \ReflectionClass(get_class($this->obj)); - } + // closure as handler + $handler = function(LogEntryInterface $entry) { + echo (string) $entry; + }; - protected function tearDown(): void - { - $this->obj = $this->ref = NULL; - parent::tearDown(); - } + // set class processor + $this->obj->addHandler( + 'warning', + $handler, + MyEntry::addProcessor(function(LogEntryInterface $entry) { + $context = $entry->getContext(); + $context['wow'] = 'bingo'; + $entry->setContext($context); + }) + ); - protected function invokeMethod($methodName, array $parameters = array()) - { - $method = $this->ref->getMethod($methodName); - $method->setAccessible(TRUE); - return $method->invokeArgs($this->obj, $parameters); + $this->expectOutputString('my is bingo'); + $this->obj->info('info is {wow}'); + $this->obj->error('error is {wow}'); + $this->obj->error(new MyEntry('my is {wow}')); } } \ No newline at end of file diff --git a/tests/Processor/MemoryProcessorTest.php b/tests/Processor/MemoryProcessorTest.php index 2d48c60..c100103 100644 --- a/tests/Processor/MemoryProcessorTest.php +++ b/tests/Processor/MemoryProcessorTest.php @@ -1,6 +1,6 @@ 'a']); - $this->obj->process($m); - $b = $m->getContext(); - $this->assertEquals(3, count($b)); - $this->assertTrue(isset($b['memory_used'])); - $this->assertTrue(isset($b['memory_peak'])); - } - protected function setUp(): void { parent::setUp(); @@ -46,4 +33,19 @@ protected function invokeMethod($methodName, array $parameters = array()) $method->setAccessible(TRUE); return $method->invokeArgs($this->obj, $parameters); } + + /** + * @covers Phoole\Logger\Processor\MemoryProcessor::process() + */ + public function testProcess() + { + $m = new LogEntry('test', ['a' => 'a']); + $callable = $this->obj; + $callable($m); + + $b = $m->getContext(); + $this->assertEquals(3, count($b)); + $this->assertTrue(isset($b['memory_used'])); + $this->assertTrue(isset($b['memory_peak'])); + } } \ No newline at end of file diff --git a/tests/Processor/ProcessorAbstractTest.php b/tests/Processor/ProcessorAbstractTest.php index 89d3520..ffea5da 100644 --- a/tests/Processor/ProcessorAbstractTest.php +++ b/tests/Processor/ProcessorAbstractTest.php @@ -1,6 +1,6 @@ invokeMethod('updateContext', [$a]); - $this->assertEquals( - ['test' => 'bingo'], - $b - ); + parent::setUp(); + $this->obj = new myProcessor(); + $this->ref = new \ReflectionClass(get_class($this->obj)); + } + + protected function tearDown(): void + { + $this->obj = $this->ref = NULL; + parent::tearDown(); } protected function invokeMethod($methodName, array $parameters = array()) @@ -44,28 +44,30 @@ protected function invokeMethod($methodName, array $parameters = array()) } /** - * @covers Phoole\Logger\Processor\ProcessorAbstract::process() + * @covers Phoole\Logger\Processor\ProcessorAbstract::__invoke() */ - public function testProcess() + public function testInvoke() { $m = new LogEntry('test', ['a' => 'a']); - $this->obj->process($m); + $callable = $this->obj; + $callable($m); + $this->assertEquals( ['a' => 'a', 'test' => 'bingo'], $m->getContext() ); } - protected function setUp(): void - { - parent::setUp(); - $this->obj = new myProcessor(); - $this->ref = new \ReflectionClass(get_class($this->obj)); - } - - protected function tearDown(): void + /** + * @covers Phoole\Logger\Processor\ProcessorAbstract::updateContext() + */ + public function testUpdateContext() { - $this->obj = $this->ref = NULL; - parent::tearDown(); + $a = []; + $b = $this->invokeMethod('updateContext', [$a]); + $this->assertEquals( + ['test' => 'bingo'], + $b + ); } } \ No newline at end of file diff --git a/tests/Processor/ProcessorAwareTraitTest.php b/tests/Processor/ProcessorAwareTraitTest.php new file mode 100644 index 0000000..d8f20e2 --- /dev/null +++ b/tests/Processor/ProcessorAwareTraitTest.php @@ -0,0 +1,156 @@ +getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($object, $parameters); + } + + protected function getPrivateProperty($obj, $propertyName) + { + $ref = new \ReflectionClass(get_class($obj)); + $property = $ref->getProperty($propertyName); + $property->setAccessible(TRUE); + return $property->getValue($obj); + } + + /** + * @covers Phoole\Logger\Entry\ProcessorAwareTrait::initProcessors() + */ + public function testInitProcessors() + { + $obj = new AA(); + $res = $this->getPrivateProperty($obj, 'processors'); + $this->assertEquals(0, count($res)); + + $this->invokeMethod($obj, 'initProcessors'); + $res = $this->getPrivateProperty($obj, 'processors'); + $this->assertEquals(1, count($res)); + + $obj = new A(); + $this->invokeMethod($obj, 'initProcessors'); + $res = $this->getPrivateProperty($obj, 'processors'); + $this->assertEquals(2, count($res)); + } + + /** + * @covers Phoole\Logger\Entry\ProcessorAwareTrait::process() + */ + public function testProcess() + { + $aa = new AA(); + + $this->expectOutputString('inAinAA'); + $aa->process(); + } + + /** + * @covers Phoole\Logger\Entry\ProcessorAwareTrait::getClassTree() + */ + public function testGetClassTree() + { + $obj = new AA(); + $this->assertEquals( + [ + A::class, + AA::class + ], + $this->invokeMethod($obj, 'getClassTree') + ); + } + + /** + * @covers Phoole\Logger\Entry\ProcessorAwareTrait::classProcessors() + */ + public function testClassProcessors() + { + $obj = new AA(); + $res = $this->invokeMethod($obj, 'classProcessors'); + $this->assertEquals(1, count($res)); + + $obj = new A(); + $res = $this->invokeMethod($obj, 'classProcessors'); + $this->assertEquals(1, count($res)); + } + + /** + * @covers Phoole\Logger\Entry\ProcessorAwareTrait::GetProcessors() + */ + public function testGetProcessors() + { + $obj = new AA(); + $res = $this->invokeMethod($obj, 'getProcessors'); + $this->assertEquals(2, count($res)); + } + + /** + * @covers Phoole\Logger\Entry\ProcessorAwareTrait::addProcessor() + */ + public function testAddProcessor() + { + $obj = new AA(); + $this->invokeMethod($obj, 'getProcessors'); + $res = $this->getPrivateProperty($obj, 'processors'); + $this->assertEquals(1, count($res[AA::class])); + + $this->invokeMethod( + $obj, 'addProcessor', [ + function(A $bingo) { + echo 'bingo'; + } + ] + ); + $res = $this->getPrivateProperty($obj, 'processors'); + $this->assertEquals(2, count($res[AA::class])); + } +} \ No newline at end of file diff --git a/tests/Processor/VerifyCallableTraitTest.php b/tests/Processor/VerifyCallableTraitTest.php new file mode 100644 index 0000000..dbb7c03 --- /dev/null +++ b/tests/Processor/VerifyCallableTraitTest.php @@ -0,0 +1,43 @@ +getMethod($methodName); + $method->setAccessible(TRUE); + return $method->invokeArgs($object, $parameters); + } + + /** + * @covers Phoole\Logger\Processor\VerifyCallableTrait::verifyCallable() + */ + public function testVerifyCallable() + { + $obj = new LogEntry(); + $callable = function(LogEntry $entry) { }; + $this->invokeMethod($obj, 'verifyCallable', [$callable, LogEntry::class]); + + $this->expectExceptionMessage('non valid processor'); + $callable = function() { }; + $this->invokeMethod($obj, 'verifyCallable', [$callable, LogEntry::class]); + } +} \ No newline at end of file