From da7ddcf33d6a15f887398705b8df3dc465c654f5 Mon Sep 17 00:00:00 2001 From: Eric Durand-Tremblay Date: Thu, 27 Apr 2023 14:47:44 -0400 Subject: [PATCH 1/2] Add LoggerInterface and LogLocatorLogger --- src/LogLocator.php | 39 ++++++++------------- src/LogLocatorLogger.php | 76 ++++++++++++++++++++++++++++++++++++++++ src/Logger.php | 25 +++++++------ src/LoggerDecorator.php | 38 ++++++++++++++------ src/LoggerInterface.php | 17 +++++++++ tests/LoggerTest.php | 62 ++++++++++++++++++-------------- 6 files changed, 184 insertions(+), 73 deletions(-) create mode 100644 src/LogLocatorLogger.php create mode 100644 src/LoggerInterface.php diff --git a/src/LogLocator.php b/src/LogLocator.php index 4268d183..2e07519f 100644 --- a/src/LogLocator.php +++ b/src/LogLocator.php @@ -3,58 +3,47 @@ namespace Kronos\Log; use Kronos\Log\Writer\TriggerError; +use Psr\Log\LoggerInterface as PsrLoggerInterface; class LogLocator { + private static ?LoggerInterface $logger = null; - /** - * @var \Psr\Log\LoggerInterface - */ - private static $logger; - - /** - * @param \Psr\Log\LoggerInterface $logger - * @param bool $force - */ - public static function setLogger(\Psr\Log\LoggerInterface $logger, $force = false) + public static function setLogger(LoggerInterface|PsrLoggerInterface $logger, bool $force = false): void { + if (!$logger instanceof LoggerInterface) { + $logger = new LoggerDecorator($logger); + } + if (!self::isLoggerSet() || $force) { self::$logger = $logger; } } - /** - * @return bool - */ - public static function isLoggerSet() + public static function isLoggerSet(): bool { - return isset(self::$logger); + return self::$logger !== null; } - /** - * @return \Psr\Log\LoggerInterface - */ - public static function getLogger() + public static function getLogger(): LoggerInterface { if (!self::isLoggerSet()) { self::setLogger(self::createDefaultLogger()); } + /** @psalm-var LoggerInterface self::$logger */ return self::$logger; } - public static function unsetLogger() + public static function unsetLogger(): void { self::$logger = null; } - /** - * @return Logger - */ - public static function createDefaultLogger() + public static function createDefaultLogger(): Logger { $logger = new Logger(); $logger->addWriter(new TriggerError()); return $logger; } -} \ No newline at end of file +} diff --git a/src/LogLocatorLogger.php b/src/LogLocatorLogger.php new file mode 100644 index 00000000..40d61731 --- /dev/null +++ b/src/LogLocatorLogger.php @@ -0,0 +1,76 @@ +emergency($message, $context); + } + + public function alert(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->alert($message, $context); + } + + public function critical(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->critical($message, $context); + } + + public function error(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->error($message, $context); + } + + public function warning(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->warning($message, $context); + } + + public function notice(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->notice($message, $context); + } + + public function info(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->info($message, $context); + } + + public function debug(string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->debug($message, $context); + } + + public function log($level, string|Stringable $message, array $context = []): void + { + LogLocator::getLogger()->log($level, $message, $context); + } + + public function exception(string $message, Throwable $exception, array $context = array()): void + { + LogLocator::getLogger()->exception($message, $exception, $context); + } + + public function addContext(string $key, mixed $value): void + { + LogLocator::getLogger()->addContext($key, $value); + } + + public function addContextArray(array $context): void + { + LogLocator::getLogger()->addContextArray($context); + } +} diff --git a/src/Logger.php b/src/Logger.php index 2bb3eaf7..b6293102 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -2,9 +2,9 @@ namespace Kronos\Log; -use Kronos\Log\Writer\Console; +use Throwable; -class Logger extends \Psr\Log\AbstractLogger +class Logger extends \Psr\Log\AbstractLogger implements LoggerInterface { const EXCEPTION_CONTEXT = 'exception'; @@ -20,26 +20,22 @@ class Logger extends \Psr\Log\AbstractLogger /** * @param WriterInterface $writer */ - public function addWriter(WriterInterface $writer) + public function addWriter(WriterInterface $writer): void { $this->writers[] = $writer; } - /** - * @param $key String - * @param $value Mixed - */ - public function addContext($key, $value) + public function addContext(string $key, mixed $value): void { $this->context[$key] = $value; } - public function addContextArray(array $context) + public function addContextArray(array $context): void { $this->context = array_merge($this->context, $context); } - public function setWriterCanLog($writer_name, $can_log = true) + public function setWriterCanLog($writer_name, $can_log = true): void { /** @var class-string $writerClassName */ $writerClassName = self::WRITER_PATH . ucfirst($writer_name); @@ -62,4 +58,13 @@ public function log($level, $message, array $context = array()): void } } } + + /** + * Log Error with exception context + */ + public function exception(string $message, Throwable $exception, array $context = array()): void + { + $context[self::EXCEPTION_CONTEXT] = $exception; + $this->error($message, $context); + } } diff --git a/src/LoggerDecorator.php b/src/LoggerDecorator.php index 0e126ef2..e13ef3bb 100644 --- a/src/LoggerDecorator.php +++ b/src/LoggerDecorator.php @@ -2,27 +2,23 @@ namespace Kronos\Log; -use Psr\Log\LoggerInterface; +use Psr\Log\LoggerInterface as PsrLoggerInterface; use Psr\Log\LogLevel; +use Throwable; class LoggerDecorator implements LoggerInterface { - /** - * @var string - */ - private $level = LogLevel::DEBUG; - /** - * @var LoggerInterface - */ - private $delegate; + private string $level = LogLevel::DEBUG; + + private PsrLoggerInterface $delegate; public function __construct( - LoggerInterface $delegate + LoggerInterface|PsrLoggerInterface $delegate ) { $this->delegate = $delegate; } - public function setLevel(string $level) + public function setLevel(string $level): void { $this->level = $level; } @@ -73,4 +69,24 @@ public function log($level, $message, array $context = array()): void $this->delegate->log($level, $message, $context); } } + + public function addContext(string $key, mixed $value): void + { + if($this->delegate instanceof LoggerInterface) { + $this->delegate->addContext($key, $value); + } + } + + public function addContextArray(array $context): void + { + if($this->delegate instanceof LoggerInterface) { + $this->delegate->addContextArray($context); + } + } + + public function exception(string $message, Throwable $exception, array $context = array()): void + { + $context[Logger::EXCEPTION_CONTEXT] = $exception; + $this->error($message, $context); + } } diff --git a/src/LoggerInterface.php b/src/LoggerInterface.php new file mode 100644 index 00000000..260432cf --- /dev/null +++ b/src/LoggerInterface.php @@ -0,0 +1,17 @@ +logger->addWriter($this->writer); } - public function test_LoggerWithWriter_Log_ShouldAskWriteCanLogLevel() + public function test_LoggerWithWriter_Log_ShouldAskWriteCanLogLevel(): void { $this->writer ->expects(self::once()) @@ -47,7 +39,7 @@ public function test_LoggerWithWriter_Log_ShouldAskWriteCanLogLevel() $this->logger->log(self::ANY_LOG_LEVEL, self::A_MESSAGE); } - public function test_LoggerWithWriterThatCanLog_Log_ShouldTellWriterToLog() + public function test_LoggerWithWriterThatCanLog_Log_ShouldTellWriterToLog(): void { $this->givenWriterCanLog(); $this->expectsWriterLogToBeCalledWith(self::ANY_LOG_LEVEL, self::A_MESSAGE, @@ -56,7 +48,7 @@ public function test_LoggerWithWriterThatCanLog_Log_ShouldTellWriterToLog() $this->logger->log(self::ANY_LOG_LEVEL, self::A_MESSAGE, [self::A_CONTEXT_KEY => self::A_CONTEXT_VALUE]); } - public function test_LoggerWithWritterThatCannotLog_Log_ShouldNotCallLogOnWriter() + public function test_LoggerWithWritterThatCannotLog_Log_ShouldNotCallLogOnWriter(): void { $this->writer->method('canLogLevel')->willReturn(false); $this->writer->expects(self::never())->method('log'); @@ -64,7 +56,7 @@ public function test_LoggerWithWritterThatCannotLog_Log_ShouldNotCallLogOnWriter $this->logger->log(self::ANY_LOG_LEVEL, self::A_MESSAGE); } - public function test_LoggerWithContextAndWriter_Log_ShouldAddGivenContext() + public function test_LoggerWithContextAndWriter_Log_ShouldAddGivenContext(): void { $this->givenWriterCanLog(); $this->logger->addContext(self::ANOTHER_CONTEXT_KEY, self::ANOTHER_CONTEXT_VALUE); @@ -80,7 +72,7 @@ public function test_LoggerWithContextAndWriter_Log_ShouldAddGivenContext() $this->logger->log(self::ANY_LOG_LEVEL, self::A_MESSAGE, [self::A_CONTEXT_KEY => self::A_CONTEXT_VALUE]); } - public function test_LoggerContextArrayAndWriter_Log_ShouldAddGivenContext() + public function test_LoggerContextArrayAndWriter_Log_ShouldAddGivenContext(): void { $this->givenWriterCanLog(); $this->logger->addContextArray([self::ANOTHER_CONTEXT_KEY => self::ANOTHER_CONTEXT_VALUE]); @@ -96,7 +88,7 @@ public function test_LoggerContextArrayAndWriter_Log_ShouldAddGivenContext() $this->logger->log(self::ANY_LOG_LEVEL, self::A_MESSAGE, [self::A_CONTEXT_KEY => self::A_CONTEXT_VALUE]); } - public function test_WriterThrowException_Log_ShouldCatchExceptionAndTriggerError() + public function test_WriterThrowException_Log_ShouldCatchExceptionAndTriggerError(): void { $errorHandled = 0; $handledTriggedError = false; @@ -123,12 +115,28 @@ public function test_WriterThrowException_Log_ShouldCatchExceptionAndTriggerErro } } - private function givenWriterCanLog() + public function test_exception_logExceptionWithContext(): void + { + $this->givenWriterCanLog(); + $exception = new \RuntimeException("Eception message"); + + $this->expectsWriterLogToBeCalledWith( + LogLevel::ERROR, + self::A_MESSAGE, + [ + Logger::EXCEPTION_CONTEXT => $exception + ] + ); + + $this->logger->exception(self::A_MESSAGE, $exception); + } + + private function givenWriterCanLog(): void { $this->writer->method('canLogLevel')->willReturn(true); } - private function expectsWriterLogToBeCalledWith($level, $message, $context) + private function expectsWriterLogToBeCalledWith($level, $message, $context): void { $this->writer ->expects(self::once()) From 2c3a267fae15ab93640aaa6fe414eea9488de079 Mon Sep 17 00:00:00 2001 From: Eric Durand-Tremblay Date: Thu, 27 Apr 2023 15:06:17 -0400 Subject: [PATCH 2/2] test --- tests/LoggerDecoratorTest.php | 92 +++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/tests/LoggerDecoratorTest.php b/tests/LoggerDecoratorTest.php index 95859353..f010b45a 100644 --- a/tests/LoggerDecoratorTest.php +++ b/tests/LoggerDecoratorTest.php @@ -1,37 +1,31 @@ delegate = $this->createMock(LoggerInterface::class); - $this->loggerDecorator = new LoggerDecorator($this->delegate); - } public function test_shouldLogWhenMessageLevelIsHigherThanLogger(): void { - $this->loggerDecorator->setLevel(LogLevel::INFO); + $decorator = $this->givenDecoratorForPsrLoggerInterface(); + $decorator->setLevel(LogLevel::INFO); + $this->delegate ->expects($this->once()) ->method('log') ->with(LogLevel::WARNING, 'a message'); - $this->loggerDecorator->log(LogLevel::WARNING, 'a message'); + $decorator->log(LogLevel::WARNING, 'a message'); } /** @@ -39,10 +33,64 @@ public function test_shouldLogWhenMessageLevelIsHigherThanLogger(): void */ public function test_shouldNotLogWhenLoggerLevelIsHigherThanMessage($loggerLevel, $levelOfMessage): void { - $this->loggerDecorator->setLevel($loggerLevel); + $decorator = $this->givenDecoratorForPsrLoggerInterface(); + $decorator->setLevel($loggerLevel); + $this->delegate->expects($this->never())->method('log'); - $this->loggerDecorator->log($levelOfMessage, 'a message'); + $decorator->log($levelOfMessage, 'a message'); + } + + public function test_loggerInterface_addContext_addContextToDelegate(): void + { + $decorator = $this->givenDecoratorForLoggerInterface(); + + $this->delegate->expects(self::once()) + ->method('addContext') + ->with("key", "value"); + + $decorator->addContext("key", "value"); + } + + public function test_psrLoggerInterface_addContext_addContextToDelegate(): void + { + $decorator = $this->givenDecoratorForPsrLoggerInterface(); + + $decorator->addContext("key", "value"); + + self::assertTrue(true, "Did not call non-existing method"); + } + + public function test_loggerInterface_addContextArray_addContextToDelegate(): void + { + $decorator = $this->givenDecoratorForLoggerInterface(); + + $this->delegate->expects(self::once()) + ->method('addContextArray') + ->with(["key" => "value"]); + + $decorator->addContextArray(["key" => "value"]); + } + + public function test_psrLoggerInterface_addContextArray_addContextToDelegate(): void + { + $decorator = $this->givenDecoratorForPsrLoggerInterface(); + + $decorator->addContextArray(["key" => "value"]); + + self::assertTrue(true, "Did not call non-existing method"); + } + + public function test_loggerInterface_exception_logErrorWithExceptionContext(): void + { + $decorator = $this->givenDecoratorForLoggerInterface(); + $exception = new \RuntimeException("Eception message"); + + $this->delegate->expects(self::once()) + ->method('log') + ->with(LogLevel::ERROR, "Message", [Logger::EXCEPTION_CONTEXT => $exception]); + + $decorator->exception("Message", $exception); } public static function provideLowerLogLevels(): array @@ -56,4 +104,16 @@ public static function provideLowerLogLevels(): array [LogLevel::EMERGENCY, LogLevel::CRITICAL] ]; } + + private function givenDecoratorForLoggerInterface(): LoggerDecorator + { + $this->delegate = $this->createMock(LoggerInterface::class); + return new LoggerDecorator($this->delegate); + } + + private function givenDecoratorForPsrLoggerInterface(): LoggerDecorator + { + $this->delegate = $this->createMock(PsrLoggerInterface::class); + return new LoggerDecorator($this->delegate); + } }