Skip to content

Commit

Permalink
Use driver spec levels in PsrLogAdapter::writeLog()
Browse files Browse the repository at this point in the history
This also refactors writeLog() to call PSR loggers directly instead of forwarding to LogSubscriber::log().
  • Loading branch information
jmikola committed Sep 21, 2023
1 parent 4370f91 commit c293d6e
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 30 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
"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"
},
"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",
Expand Down
57 changes: 49 additions & 8 deletions src/PsrLogAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<LoggerInterface, null> */
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. */
Expand Down Expand Up @@ -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) {
Expand All @@ -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()
Expand Down
72 changes: 51 additions & 21 deletions tests/PsrLogAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,47 +21,76 @@ class PsrLogAdapterTest extends BaseTestCase
public function setUp(): void
{
$this->logger = $this->createTestPsrLogger();

PsrLogAdapter::addLogger($this->logger);
}

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);
Expand Down

0 comments on commit c293d6e

Please sign in to comment.