diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dbf009..5423cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - initial implementation - can create a custom Logger instance rotating or static - combined logger (OXID and configured stream) + - can combine with special handlers (buffered, onErrorOnly, deduplicated) diff --git a/README.md b/README.md index daf4899..b9d5503 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,36 @@ composer require d3/logger-factory ## Usage ``` -$loggerFactory = loggerFactory::create(); -$logger = $loggerFactory->getFileLogger('myPluginLogger', 'plugin_requests.log', Logger::DEBUG, 5); +use D3\LoggerFactory\LoggerFactory; + +$loggerFactory = LoggerFactory::create(); +$logger = $loggerFactory->getFileLogger( + 'myPluginLogger', + 'plugin_requests.log', + Logger::DEBUG, + 5 +); +``` + +### Use of the special handler + +``` +use D3\LoggerFactory\LoggerFactory; + +$loggerFactory = LoggerFactory::create(); +$logger = $loggerFactory->getFileLogger( + 'myPluginLogger', + 'plugin_requests.log', + Logger::DEBUG, + 5, + [ + LoggerFactory::SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY, // simple default implementation + LoggerFactory::SPECIAL_HANDLERS_BUFFERING, + LoggerFactory::SPECIAL_HANDLERS_MAKE_UNIQUE => [ // advanced custom implementation + LoggerFactory::MAKEUNIQUE_OPTION_LEVEL => Logger::INFO + ], + ] +); ``` ## Licence of this software (Logger factory) [MIT] diff --git a/src/LoggerFactory.php b/src/LoggerFactory.php index 2408858..0b252ef 100644 --- a/src/LoggerFactory.php +++ b/src/LoggerFactory.php @@ -18,8 +18,10 @@ declare(strict_types=1); namespace D3\LoggerFactory; use Exception; +use Monolog\Handler\AbstractHandler; use Monolog\Handler\AbstractProcessingHandler; use Monolog\Handler\BufferHandler; +use Monolog\Handler\DeduplicationHandler; use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; use Monolog\Handler\FingersCrossedHandler; use Monolog\Handler\RotatingFileHandler; @@ -32,6 +34,15 @@ class LoggerFactory { public const SPECIAL_HANDLERS_BUFFERING = 'buffering'; public const SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY = 'logOnErrorOnly'; + public const SPECIAL_HANDLERS_MAKE_UNIQUE = 'makeUnique'; + + public const BUFFERING_OPTION_LIMIT = 'bufferLimit'; + public const BUFFERING_OPTION_LEVEL = 'loglevel'; + + public const LOGONERRORONLY_LEVEL = 'activationLevel'; + + public const MAKEUNIQUE_OPTION_LEVEL = 'loglevel'; + public const MAKEUNIQUE_OPTION_TIME = 'time'; public static function create(): LoggerFactory { @@ -117,25 +128,54 @@ class LoggerFactory public function applySpecialHandlers( AbstractProcessingHandler $handler, array $specialHandlers = [] - ): AbstractProcessingHandler + ): AbstractHandler { if (in_array(self::SPECIAL_HANDLERS_BUFFERING, $specialHandlers, true)) { - $handler = $this->setLogItemsOnErrorOnly($handler); + $handler = $this->setBuffering($handler); + } elseif (in_array(self::SPECIAL_HANDLERS_BUFFERING, array_keys($specialHandlers), true)) { + $options = $specialHandlers[self::SPECIAL_HANDLERS_BUFFERING]; + $handler = $this->setBuffering( + $handler, + $options[self::BUFFERING_OPTION_LIMIT] ?? 0, + $options[self::BUFFERING_OPTION_LEVEL] ?? Logger::DEBUG + ); } + if (in_array(self::SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY, $specialHandlers, true)) { $handler = $this->setLogItemsOnErrorOnly($handler); + } elseif (in_array(self::SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY, array_keys($specialHandlers), true)) { + $options = $specialHandlers[self::SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY]; + $handler = $this->setLogItemsOnErrorOnly( + $handler, + $options[self::LOGONERRORONLY_LEVEL] ?? Logger::ERROR + ); + } + + if (in_array(self::SPECIAL_HANDLERS_MAKE_UNIQUE, $specialHandlers, true)) { + $handler = $this->makeUnique($handler); + } elseif (in_array(self::SPECIAL_HANDLERS_MAKE_UNIQUE, array_keys($specialHandlers), true)) { + $options = $specialHandlers[self::SPECIAL_HANDLERS_MAKE_UNIQUE]; + $handler = $this->makeUnique( + $handler, + $options[self::MAKEUNIQUE_OPTION_LEVEL] ?? Logger::ERROR, + $options[self::MAKEUNIQUE_OPTION_TIME] ?? 60 + ); } return $handler; } - public function setBuffering(AbstractProcessingHandler $handler): BufferHandler + public function setBuffering( + AbstractHandler $handler, + int $bufferLimit = 0, + int $loglevel = Logger::DEBUG + ): BufferHandler { - return new BufferHandler($handler); + return new BufferHandler($handler, $bufferLimit, $loglevel); } public function setLogItemsOnErrorOnly( - AbstractProcessingHandler $handler, + AbstractHandler $handler, int $activationLevel = Logger::ERROR ): FingersCrossedHandler { @@ -144,4 +184,13 @@ class LoggerFactory new ErrorLevelActivationStrategy($activationLevel) ); } + + public function makeUnique( + AbstractHandler $handler, + int $deduplicationLevel = Logger::ERROR, + int $time = 60 + ): DeduplicationHandler + { + return new DeduplicationHandler($handler, null, $deduplicationLevel, $time); + } } \ No newline at end of file diff --git a/tests/LoggerFactoryTest.php b/tests/LoggerFactoryTest.php index a673741..b30e555 100644 --- a/tests/LoggerFactoryTest.php +++ b/tests/LoggerFactoryTest.php @@ -4,6 +4,9 @@ namespace D3\LoggerFactory\tests; use D3\LoggerFactory\LoggerFactory; use Generator; +use Monolog\Handler\BufferHandler; +use Monolog\Handler\DeduplicationHandler; +use Monolog\Handler\FingersCrossedHandler; use Monolog\Handler\RotatingFileHandler; use Monolog\Handler\StreamHandler; use Monolog\Logger; @@ -34,7 +37,10 @@ class LoggerFactoryTest extends ApiTestCase */ public function testGetFileLogger(int $logLevel, ?int $maxFiles, string $expectedHandlerClass): void { - $sut = LoggerFactory::create(); + $sut = $this->getMockBuilder(LoggerFactory::class) + ->onlyMethods(['applySpecialHandlers']) + ->getMock(); + $sut->expects($this->once())->method('applySpecialHandlers')->willReturnArgument(0); /** @var Logger|MockObject $logger */ $logger = $this->callMethod( @@ -63,7 +69,10 @@ class LoggerFactoryTest extends ApiTestCase */ public function testGetCombinedOxidAndFileLoggerWithoutOxid(): void { - $sut = LoggerFactory::create(); + $sut = $this->getMockBuilder(LoggerFactory::class) + ->onlyMethods(['applySpecialHandlers']) + ->getMock(); + $sut->expects($this->never())->method('applySpecialHandlers')->willReturnArgument(0); $this->expectException(RuntimeException::class); @@ -102,11 +111,14 @@ class LoggerFactoryTest extends ApiTestCase * @throws ReflectionException * @covers \D3\LoggerFactory\LoggerFactory::getCombinedOxidAndFileLogger */ - public function testAddCombinedOxidAndFileLoggerInOxid(): void + public function testGetCombinedOxidAndFileLoggerInOxid(): void { require_once __DIR__.'/Helpers/classAliases.php'; - $sut = LoggerFactory::create(); + $sut = $this->getMockBuilder(LoggerFactory::class) + ->onlyMethods(['applySpecialHandlers']) + ->getMock(); + $sut->expects($this->exactly(2))->method('applySpecialHandlers')->willReturnArgument(0); $logger = $this->callMethod( $sut, @@ -138,4 +150,117 @@ class LoggerFactoryTest extends ApiTestCase ) ); } + + /** + * @test + * @covers \D3\LoggerFactory\LoggerFactory::applySpecialHandlers + * @throws ReflectionException + * @dataProvider applySpecialHandlersDataProvider + */ + public function testApplySpecialHandlers(array $options, string $expectedClass): void + { + $sut = LoggerFactory::create(); + + $handler = $this->getMockBuilder(StreamHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + $expectedClass, + $this->callMethod( + $sut, + 'applySpecialHandlers', + [$handler, $options] + ) + ); + } + + public static function applySpecialHandlersDataProvider(): Generator + { + yield 'empty config' => [[], StreamHandler::class]; + yield 'simple buffering' => [[LoggerFactory::SPECIAL_HANDLERS_BUFFERING], BufferHandler::class]; + yield 'advanced buffering' => [ + [LoggerFactory::SPECIAL_HANDLERS_BUFFERING => [LoggerFactory::BUFFERING_OPTION_LIMIT => 10]], + BufferHandler::class + ]; + yield 'simple logOnErrorOnly' => [[LoggerFactory::SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY], FingersCrossedHandler::class]; + yield 'advanced logOnErrorOnly' => [ + [LoggerFactory::SPECIAL_HANDLERS_LOG_ON_ERROR_ONLY => [LoggerFactory::LOGONERRORONLY_LEVEL => Logger::DEBUG]], + FingersCrossedHandler::class + ]; + yield 'simple deduplicate' => [[LoggerFactory::SPECIAL_HANDLERS_MAKE_UNIQUE], DeduplicationHandler::class]; + yield 'advanced deduplicate' => [ + [LoggerFactory::SPECIAL_HANDLERS_MAKE_UNIQUE => [LoggerFactory::MAKEUNIQUE_OPTION_TIME => 30]], + DeduplicationHandler::class + ]; + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\LoggerFactory\LoggerFactory::setBuffering + */ + public function testSetBuffering(): void + { + $sut = LoggerFactory::create(); + + $handler = $this->getMockBuilder(StreamHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + BufferHandler::class, + $this->callMethod( + $sut, + 'setBuffering', + [$handler] + ) + ); + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\LoggerFactory\LoggerFactory::setLogItemsOnErrorOnly + */ + public function testSetLogItemsOnErrorOnly(): void + { + $sut = LoggerFactory::create(); + + $handler = $this->getMockBuilder(StreamHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + FingersCrossedHandler::class, + $this->callMethod( + $sut, + 'setLogItemsOnErrorOnly', + [$handler] + ) + ); + } + + /** + * @test + * @throws ReflectionException + * @covers \D3\LoggerFactory\LoggerFactory::makeUnique + */ + public function testMakeUnique(): void + { + $sut = LoggerFactory::create(); + + $handler = $this->getMockBuilder(StreamHandler::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertInstanceOf( + DeduplicationHandler::class, + $this->callMethod( + $sut, + 'makeUnique', + [$handler] + ) + ); + } } \ No newline at end of file