Skip to content

Commit c45bd92

Browse files
committed
Convert PsrLogAdapter to a singleton and revert Client changes
1 parent adb8552 commit c45bd92

File tree

4 files changed

+133
-54
lines changed

4 files changed

+133
-54
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"require-dev": {
2222
"doctrine/coding-standard": "^11.1",
23+
"psr/log": "^1.1.4",
2324
"rector/rector": "^0.16.0",
2425
"squizlabs/php_codesniffer": "^3.7",
2526
"symfony/phpunit-bridge": "^5.2",

src/Client.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,12 @@
3737
use MongoDB\Operation\ListDatabaseNames;
3838
use MongoDB\Operation\ListDatabases;
3939
use MongoDB\Operation\Watch;
40-
use Psr\Log\LoggerAwareInterface;
41-
use Psr\Log\LoggerInterface;
4240
use Throwable;
4341

4442
use function is_array;
4543
use function is_string;
4644

47-
class Client implements LoggerAwareInterface
45+
class Client
4846
{
4947
public const DEFAULT_URI = 'mongodb://127.0.0.1/';
5048

@@ -331,19 +329,6 @@ public function selectDatabase(string $databaseName, array $options = [])
331329
return new Database($this->manager, $databaseName, $options);
332330
}
333331

334-
public function setLogger(LoggerInterface $logger): void
335-
{
336-
/* TODO: lazily initialize a PsrLogAdapter for this client and register
337-
* it with PHPC. Consider what happens if multiple clients instances in
338-
* an app register the same PSR logger. That logger will likely receive
339-
* duplicate messages, since PHPC's own checks would only prevent the
340-
* same PsrLogAdapter instance from being registered multiple times.
341-
*
342-
* Perhaps we can work around this by making PsrLogAdapter a singleton
343-
* or utilize a static map of PSR loggers.
344-
*/
345-
}
346-
347332
/**
348333
* Start a new client session.
349334
*

src/PsrLogAdapter.php

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,56 +17,80 @@
1717

1818
namespace MongoDB;
1919

20-
use MongoDB\Driver\Logging\Logger as DriverLoggerInterface;
21-
use Psr\Log\LoggerInterface as PsrLoggerInterface;
20+
use MongoDB\Driver\Logging\Logger as DriverLogger;
21+
use Psr\Log\LoggerInterface as PsrLogger;
2222
use Psr\Log\LogLevel as PsrLogLevel;
23+
use SplObjectStorage;
2324

24-
class PsrLogAdapter implements DriverLoggerInterface
25+
use function MongoDB\Driver\Logging\addLogger;
26+
use function MongoDB\Driver\Logging\removeLogger;
27+
28+
final class PsrLogAdapter implements DriverLogger
2529
{
26-
private PsrLoggerInterface $psrLogger;
30+
private static ?self $instance = null;
31+
32+
/** @psalm-var SplObjectStorage<PsrLogger, null> */
33+
private SplObjectStorage $psrLoggers;
34+
35+
private const PSR_LEVELS = [
36+
DriverLogger::LEVEL_ERROR => PsrLogLevel::ERROR,
37+
/* libmongoc considers "critical" less severe than "error" so map it to
38+
* "error" in the PSR logger. */
39+
DriverLogger::LEVEL_CRITICAL => PsrLogLevel::ERROR,
40+
DriverLogger::LEVEL_WARNING => PsrLogLevel::WARNING,
41+
DriverLogger::LEVEL_MESSAGE => PsrLogLevel::NOTICE,
42+
DriverLogger::LEVEL_INFO => PsrLogLevel::INFO,
43+
DriverLogger::LEVEL_DEBUG => PsrLogLevel::DEBUG,
44+
/* PSR does not define a "trace" level, so map it to "debug" in the PSR
45+
* logger. That said, trace logging is very verbose and should not be
46+
* encouraged when using a PSR logger. */
47+
DriverLogger::LEVEL_TRACE => PsrLogLevel::DEBUG,
48+
];
49+
50+
public static function addLogger(PsrLogger $psrLogger): void
51+
{
52+
$instance = self::getInstance();
53+
54+
$instance->psrLoggers->attach($psrLogger);
55+
56+
addLogger($instance);
57+
}
2758

28-
public function __construct(PsrLoggerInterface $psrLogger)
59+
public static function getInstance(): self
2960
{
30-
$this->psrLogger = $psrLogger;
61+
if (self::$instance === null) {
62+
self::$instance = new self();
63+
}
64+
65+
return self::$instance;
3166
}
3267

3368
public function log(int $level, string $domain, string $message): void
3469
{
35-
$this->psrLogger->log(
36-
$this->driverLevelToPsrLevel($level),
37-
$domain . ': ' . $message,
38-
);
70+
$instance = self::getInstance();
71+
/* A default case is intentionally not handled since libmongoc and
72+
* PHPC should avoid emitting bogus levels; however, we can consider
73+
* throwing an UnexpectedValueException here. */
74+
$psrLevel = self::PSR_LEVELS[$level];
75+
$context = ['domain' => $domain];
76+
77+
foreach ($instance->psrLoggers as $psrLogger) {
78+
$psrLogger->log($psrLevel, $message, $context);
79+
}
3980
}
4081

41-
// TODO: Refactor this method as a private const array map on PsrLogAdapter
42-
private function driverLevelToPsrLevel(int $level): string
82+
public static function removeLogger(PsrLogger $psrLogger): void
4383
{
44-
switch ($level) {
45-
/* libmongoc considers "critical" less severe than "error" so route
46-
* both levels to "error" in the PSR logger. */
47-
case DriverLoggerInterface::LEVEL_ERROR:
48-
case DriverLoggerInterface::LEVEL_CRITICAL:
49-
return PsrLogLevel::ERROR;
50-
51-
case DriverLoggerInterface::LEVEL_WARNING:
52-
return PsrLogLevel::WARNING;
53-
54-
case DriverLoggerInterface::LEVEL_MESSAGE:
55-
return PsrLogLevel::NOTICE;
56-
57-
case DriverLoggerInterface::LEVEL_INFO:
58-
return PsrLogLevel::INFO;
59-
60-
/* PSR does not define a "trace" level, so route both levels to
61-
* "debug". That said, trace logging is very verbose and should not
62-
* be encouraged when using a PSR logger. */
63-
case DriverLoggerInterface::LEVEL_DEBUG:
64-
case DriverLoggerInterface::LEVEL_TRACE:
65-
return PsrLogLevel::DEBUG;
66-
67-
/* A default case is intentionally not handled since libmongoc and
68-
* PHPC should avoid emitting bogus levels; however, we can consider
69-
* throwing an UnexpectedValueException here. */
84+
$instance = self::getInstance();
85+
$instance->psrLoggers->detach($psrLogger);
86+
87+
if ($instance->psrLoggers->count() === 0) {
88+
removeLogger($instance);
7089
}
7190
}
91+
92+
private function __construct()
93+
{
94+
$this->psrLoggers = new SplObjectStorage();
95+
}
7296
}

tests/PsrLogAdapterTest.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace MongoDB\Tests;
4+
5+
use MongoDB\Driver\Logging\Logger as DriverLogger;
6+
use MongoDB\PsrLogAdapter;
7+
use PHPUnit\Framework\TestCase as BaseTestCase;
8+
use Psr\Log\AbstractLogger;
9+
use Psr\Log\LoggerInterface;
10+
use Psr\Log\LogLevel;
11+
12+
use function func_get_args;
13+
use function MongoDB\Driver\Logging\log;
14+
15+
class PsrLogAdapterTest extends BaseTestCase
16+
{
17+
private PsrLogAdapter $adapter;
18+
19+
private LoggerInterface $logger;
20+
21+
public function setUp(): void
22+
{
23+
$this->logger = $this->createTestPsrLogger();
24+
}
25+
26+
public function tearDown(): void
27+
{
28+
PsrLogAdapter::removeLogger($this->logger);
29+
}
30+
31+
public function testDriverLogLevelsAreMappedToPsrLogLevels(): void
32+
{
33+
PsrLogAdapter::addLogger($this->logger);
34+
35+
log(DriverLogger::LEVEL_ERROR, 'error');
36+
log(DriverLogger::LEVEL_CRITICAL, 'critical');
37+
log(DriverLogger::LEVEL_WARNING, 'warning');
38+
log(DriverLogger::LEVEL_MESSAGE, 'message');
39+
log(DriverLogger::LEVEL_INFO, 'info');
40+
log(DriverLogger::LEVEL_DEBUG, 'debug');
41+
log(DriverLogger::LEVEL_TRACE, 'trace');
42+
43+
$context = ['domain' => 'php'];
44+
45+
$expectedLogs = [
46+
[LogLevel::ERROR, 'error', $context],
47+
[LogLevel::ERROR, 'critical', $context],
48+
[LogLevel::WARNING, 'warning', $context],
49+
[LogLevel::NOTICE, 'message', $context],
50+
[LogLevel::INFO, 'info', $context],
51+
[LogLevel::DEBUG, 'debug', $context],
52+
[LogLevel::DEBUG, 'trace', $context],
53+
];
54+
55+
$this->assertSame($this->logger->logs, $expectedLogs);
56+
}
57+
58+
private function createTestPsrLogger(): LoggerInterface
59+
{
60+
return new class extends AbstractLogger {
61+
public $logs = [];
62+
63+
public function log($level, $message, array $context = []): void
64+
{
65+
$this->logs[] = func_get_args();
66+
}
67+
};
68+
}
69+
}

0 commit comments

Comments
 (0)