diff --git a/composer.json b/composer.json index 23598cf8c..212d2749b 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "ext-json": "*", "ext-mongodb": "^1.17.0", "jean85/pretty-package-versions": "^2.0.1", + "psr/log": "^1.1.4|^2|^3", "symfony/polyfill-php73": "^1.27", "symfony/polyfill-php80": "^1.27", "symfony/polyfill-php81": "^1.27" @@ -22,7 +23,6 @@ "require-dev": { "doctrine/coding-standard": "^11.1", "phpbench/phpbench": "^1.2", - "psr/log": "^1.1.4|^2|^3", "rector/rector": "^0.16.0", "squizlabs/php_codesniffer": "^3.7", "symfony/phpunit-bridge": "^5.2", diff --git a/src/PsrLogAdapter.php b/src/PsrLogAdapter.php index 18e0cea89..8b390e79b 100644 --- a/src/PsrLogAdapter.php +++ b/src/PsrLogAdapter.php @@ -29,12 +29,36 @@ final class PsrLogAdapter implements LogSubscriber { + /** @internal */ + public const LEVEL_EMERGENCY = 0; + public const LEVEL_ALERT = 1; + public const LEVEL_CRITICAL = 2; + public const LEVEL_ERROR = 3; + public const LEVEL_WARN = 4; + public const LEVEL_NOTICE = 5; + public const LEVEL_INFO = 6; + public const LEVEL_DEBUG = 7; + public const LEVEL_TRACE = 8; + private static ?self $instance = null; /** @psalm-var SplObjectStorage */ private SplObjectStorage $loggers; - private const PSR_LEVELS = [ + private const SPEC_TO_PSR = [ + self::LEVEL_EMERGENCY => LogLevel::EMERGENCY, + self::LEVEL_ALERT => LogLevel::ALERT, + self::LEVEL_CRITICAL => LogLevel::CRITICAL, + self::LEVEL_ERROR => LogLevel::ERROR, + self::LEVEL_WARN => LogLevel::WARNING, + self::LEVEL_NOTICE => LogLevel::NOTICE, + self::LEVEL_INFO => LogLevel::INFO, + self::LEVEL_DEBUG => LogLevel::DEBUG, + // PSR does not define a "trace" level, so map it to "debug" + self::LEVEL_TRACE => LogLevel::DEBUG, + ]; + + private const MONGOC_TO_PSR = [ LogSubscriber::LEVEL_ERROR => LogLevel::ERROR, /* libmongoc considers "critical" less severe than "error" so map it to * "error" in the PSR logger. */ @@ -65,26 +89,26 @@ public static function removeLogger(LoggerInterface $logger): void } /** - * Pipes a log message from PHPC to all registered PSR loggers. + * Forwards a log message from libmongoc/PHPC to all registered PSR loggers. * * @internal * @see LogSubscriber::log() */ - public function log(int $level, string $domain, string $message): void + public function log(int $mongocLevel, string $domain, string $message): void { - if (! isset(self::PSR_LEVELS[$level])) { + if (! isset(self::MONGOC_TO_PSR[$mongocLevel])) { throw new UnexpectedValueException(sprintf( 'Expected level to be >= %d and <= %d, %d given for domain "%s" and message: %s', LogSubscriber::LEVEL_ERROR, LogSubscriber::LEVEL_DEBUG, - $level, + $mongocLevel, $domain, $message, )); } $instance = self::getInstance(); - $psrLevel = self::PSR_LEVELS[$level]; + $psrLevel = self::MONGOC_TO_PSR[$mongocLevel]; $context = ['domain' => $domain]; foreach ($instance->loggers as $logger) { @@ -97,9 +121,26 @@ public function log(int $level, string $domain, string $message): void * * @internal */ - public static function writeLog(int $level, string $domain, string $message): void + public static function writeLog(string $specLevel, string $domain, string $message): void { - self::getInstance()->log($level, $domain, $message); + if (! isset(self::SPEC_TO_PSR[$specLevel])) { + throw new UnexpectedValueException(sprintf( + 'Expected level to be >= %d and <= %d, %d given for domain "%s" and message: %s', + self::LEVEL_EMERGENCY, + self::LEVEL_TRACE, + $specLevel, + $domain, + $message, + )); + } + + $instance = self::getInstance(); + $psrLevel = self::SPEC_TO_PSR[$specLevel]; + $context = ['domain' => $domain]; + + foreach ($instance->loggers as $logger) { + $logger->log($psrLevel, $message, $context); + } } private function __construct() diff --git a/tests/PsrLogAdapterTest.php b/tests/PsrLogAdapterTest.php index ef0ba35c1..587421214 100644 --- a/tests/PsrLogAdapterTest.php +++ b/tests/PsrLogAdapterTest.php @@ -11,6 +11,7 @@ use Psr\Log\LogLevel; use function func_get_args; +use function MongoDB\Driver\Monitoring\mongoc_log; use function sprintf; class PsrLogAdapterTest extends BaseTestCase @@ -20,6 +21,8 @@ class PsrLogAdapterTest extends BaseTestCase public function setUp(): void { $this->logger = $this->createTestPsrLogger(); + + PsrLogAdapter::addLogger($this->logger); } public function tearDown(): void @@ -27,40 +30,67 @@ public function tearDown(): void PsrLogAdapter::removeLogger($this->logger); } + public function testLog(): void + { + /* This uses PHPC's internal mongoc_log() function to write messages + * directly to libmongoc. Those messages are then relayed to + * PsrLogAdapter and forwarded to each registered PSR logger. + * + * Note: it's not possible to test PsrLogAdapter::log() with an invalid + * level since mongoc_log() already validates its level parameter. */ + mongoc_log(LogSubscriber::LEVEL_ERROR, 'domain1', 'error'); + mongoc_log(LogSubscriber::LEVEL_CRITICAL, 'domain2', 'critical'); + mongoc_log(LogSubscriber::LEVEL_WARNING, 'domain3', 'warning'); + mongoc_log(LogSubscriber::LEVEL_MESSAGE, 'domain4', 'message'); + mongoc_log(LogSubscriber::LEVEL_INFO, 'domain5', 'info'); + mongoc_log(LogSubscriber::LEVEL_DEBUG, 'domain6', 'debug'); + + $expectedLogs = [ + [LogLevel::ERROR, 'error', ['domain' => 'domain1']], + [LogLevel::ERROR, 'critical', ['domain' => 'domain2']], + [LogLevel::WARNING, 'warning', ['domain' => 'domain3']], + [LogLevel::NOTICE, 'message', ['domain' => 'domain4']], + [LogLevel::INFO, 'info', ['domain' => 'domain5']], + [LogLevel::DEBUG, 'debug', ['domain' => 'domain6']], + ]; + + $this->assertSame($this->logger->logs, $expectedLogs); + } + /** * @testWith [-1] - * [6] + * [9] */ public function testWriteLogWithInvalidLevel(int $level): void { $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage(sprintf('Expected level to be >= 0 and <= 5, %d given for domain "domain" and message: message', $level)); + $this->expectExceptionMessage(sprintf('Expected level to be >= 0 and <= 8, %d given for domain "domain" and message: message', $level)); PsrLogAdapter::writeLog($level, 'domain', 'message'); } - public function testWriteLogMapsDriverLogLevelsToPsrLogLevels(): void + public function testWriteLog(): void { - PsrLogAdapter::addLogger($this->logger); - - $domain = 'domain'; - - PsrLogAdapter::writeLog(LogSubscriber::LEVEL_ERROR, $domain, 'error'); - PsrLogAdapter::writeLog(LogSubscriber::LEVEL_CRITICAL, $domain, 'critical'); - PsrLogAdapter::writeLog(LogSubscriber::LEVEL_WARNING, $domain, 'warning'); - PsrLogAdapter::writeLog(LogSubscriber::LEVEL_MESSAGE, $domain, 'message'); - PsrLogAdapter::writeLog(LogSubscriber::LEVEL_INFO, $domain, 'info'); - PsrLogAdapter::writeLog(LogSubscriber::LEVEL_DEBUG, $domain, 'debug'); - - $context = ['domain' => $domain]; + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_EMERGENCY, 'domain1', 'emergency'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_ALERT, 'domain2', 'alert'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_CRITICAL, 'domain3', 'critical'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_ERROR, 'domain4', 'error'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_WARN, 'domain5', 'warn'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_NOTICE, 'domain6', 'notice'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_INFO, 'domain7', 'info'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_DEBUG, 'domain8', 'debug'); + PsrLogAdapter::writeLog(PsrLogAdapter::LEVEL_TRACE, 'domain9', 'trace'); $expectedLogs = [ - [LogLevel::ERROR, 'error', $context], - [LogLevel::ERROR, 'critical', $context], - [LogLevel::WARNING, 'warning', $context], - [LogLevel::NOTICE, 'message', $context], - [LogLevel::INFO, 'info', $context], - [LogLevel::DEBUG, 'debug', $context], + [LogLevel::EMERGENCY, 'emergency', ['domain' => 'domain1']], + [LogLevel::ALERT, 'alert', ['domain' => 'domain2']], + [LogLevel::CRITICAL, 'critical', ['domain' => 'domain3']], + [LogLevel::ERROR, 'error', ['domain' => 'domain4']], + [LogLevel::WARNING, 'warn', ['domain' => 'domain5']], + [LogLevel::NOTICE, 'notice', ['domain' => 'domain6']], + [LogLevel::INFO, 'info', ['domain' => 'domain7']], + [LogLevel::DEBUG, 'debug', ['domain' => 'domain8']], + [LogLevel::DEBUG, 'trace', ['domain' => 'domain9']], ]; $this->assertSame($this->logger->logs, $expectedLogs);