Skip to content

Commit 1acfab0

Browse files
authored
PHPLIB-1243: Adapter to pipe ext-mongodb logs to a PSR-3 logger (#1158)
Introduces an internal PsrLogAdapter class to connect PSR-3 loggers with PHPC. The internal class is exposed via addLogger and removeLogger functions. Bumps ext-mongodb dependency to 1.17-dev for PHPC-2180. Permit psr/log 1.x, 2.x, or 3.x for compatibility with PHP 7.4 and 8.0+.
1 parent 05c326f commit 1acfab0

File tree

9 files changed

+340
-14
lines changed

9 files changed

+340
-14
lines changed

.evergreen/config.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -766,13 +766,13 @@ axes:
766766
display_name: Driver Version
767767
values:
768768
- id: "oldest-supported"
769-
display_name: "PHPC 1.16.0"
769+
display_name: "PHPC 1.17.0"
770770
variables:
771-
EXTENSION_VERSION: "1.16.0"
771+
EXTENSION_BRANCH: "master"
772772
- id: "latest-stable"
773-
display_name: "PHPC (1.16.x)"
773+
display_name: "PHPC (1.17.x)"
774774
variables:
775-
EXTENSION_VERSION: "stable"
775+
EXTENSION_BRANCH: "master"
776776
- id: "latest-dev"
777777
display_name: "PHPC (1.17-dev)"
778778
variables:

.github/workflows/benchmark.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818

1919
env:
2020
PHP_VERSION: "8.2"
21-
DRIVER_VERSION: "stable"
21+
DRIVER_VERSION: "mongodb/mongo-php-driver@master"
2222

2323
jobs:
2424
psalm:

.github/workflows/coding-standards.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818

1919
env:
2020
PHP_VERSION: "8.2"
21-
DRIVER_VERSION: "stable"
21+
DRIVER_VERSION: "mongodb/mongo-php-driver@master"
2222

2323
jobs:
2424
phpcs:

.github/workflows/static-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ on:
1818

1919
env:
2020
PHP_VERSION: "8.2"
21-
DRIVER_VERSION: "stable"
21+
DRIVER_VERSION: "mongodb/mongo-php-driver@master"
2222

2323
jobs:
2424
psalm:

.github/workflows/tests.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,34 +34,34 @@ jobs:
3434
mongodb-version:
3535
- "4.4"
3636
driver-version:
37-
- "stable"
37+
- "mongodb/mongo-php-driver@master"
3838
topology:
3939
- "server"
4040
include:
4141
- os: "ubuntu-20.04"
4242
php-version: "8.0"
4343
mongodb-version: "6.0"
44-
driver-version: "stable"
44+
driver-version: "mongodb/mongo-php-driver@master"
4545
topology: "replica_set"
4646
- os: "ubuntu-20.04"
4747
php-version: "8.0"
4848
mongodb-version: "6.0"
49-
driver-version: "stable"
49+
driver-version: "mongodb/mongo-php-driver@master"
5050
topology: "sharded_cluster"
5151
- os: "ubuntu-20.04"
5252
php-version: "8.0"
5353
mongodb-version: "5.0"
54-
driver-version: "stable"
54+
driver-version: "mongodb/mongo-php-driver@master"
5555
topology: "server"
5656
- os: "ubuntu-20.04"
5757
php-version: "8.0"
5858
mongodb-version: "4.4"
59-
driver-version: "stable"
59+
driver-version: "mongodb/mongo-php-driver@master"
6060
topology: "replica_set"
6161
- os: "ubuntu-20.04"
6262
php-version: "8.0"
6363
mongodb-version: "4.4"
64-
driver-version: "stable"
64+
driver-version: "mongodb/mongo-php-driver@master"
6565
topology: "sharded_cluster"
6666

6767
steps:

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
"php": "^7.4 || ^8.0",
1414
"ext-hash": "*",
1515
"ext-json": "*",
16-
"ext-mongodb": "^1.16.0",
16+
"ext-mongodb": "^1.17.0",
1717
"jean85/pretty-package-versions": "^2.0.1",
18+
"psr/log": "^1.1.4|^2|^3",
1819
"symfony/polyfill-php80": "^1.27",
1920
"symfony/polyfill-php81": "^1.27"
2021
},

src/PsrLogAdapter.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
/*
3+
* Copyright 2023-present MongoDB, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace MongoDB;
19+
20+
use MongoDB\Driver\Monitoring\LogSubscriber;
21+
use MongoDB\Exception\UnexpectedValueException;
22+
use Psr\Log\LoggerInterface;
23+
use Psr\Log\LogLevel;
24+
use SplObjectStorage;
25+
26+
use function MongoDB\Driver\Monitoring\addSubscriber;
27+
use function MongoDB\Driver\Monitoring\removeSubscriber;
28+
use function sprintf;
29+
30+
/**
31+
* Integrates libmongoc/PHPC logging with one or more PSR-3 loggers.
32+
*
33+
* This class is internal and should not be utilized by applications. Logging
34+
* should be configured via the add_logger() and remove_logger() functions.
35+
*
36+
* @internal
37+
*/
38+
final class PsrLogAdapter implements LogSubscriber
39+
{
40+
public const EMERGENCY = 0;
41+
public const ALERT = 1;
42+
public const CRITICAL = 2;
43+
public const ERROR = 3;
44+
public const WARN = 4;
45+
public const NOTICE = 5;
46+
public const INFO = 6;
47+
public const DEBUG = 7;
48+
public const TRACE = 8;
49+
50+
private static ?self $instance = null;
51+
52+
/** @psalm-var SplObjectStorage<LoggerInterface, null> */
53+
private SplObjectStorage $loggers;
54+
55+
private const SPEC_TO_PSR = [
56+
self::EMERGENCY => LogLevel::EMERGENCY,
57+
self::ALERT => LogLevel::ALERT,
58+
self::CRITICAL => LogLevel::CRITICAL,
59+
self::ERROR => LogLevel::ERROR,
60+
self::WARN => LogLevel::WARNING,
61+
self::NOTICE => LogLevel::NOTICE,
62+
self::INFO => LogLevel::INFO,
63+
self::DEBUG => LogLevel::DEBUG,
64+
// PSR does not define a "trace" level, so map it to "debug"
65+
self::TRACE => LogLevel::DEBUG,
66+
];
67+
68+
private const MONGOC_TO_PSR = [
69+
LogSubscriber::LEVEL_ERROR => LogLevel::ERROR,
70+
/* libmongoc considers "critical" less severe than "error" so map it to
71+
* "error" in the PSR logger. */
72+
LogSubscriber::LEVEL_CRITICAL => LogLevel::ERROR,
73+
LogSubscriber::LEVEL_WARNING => LogLevel::WARNING,
74+
LogSubscriber::LEVEL_MESSAGE => LogLevel::NOTICE,
75+
LogSubscriber::LEVEL_INFO => LogLevel::INFO,
76+
LogSubscriber::LEVEL_DEBUG => LogLevel::DEBUG,
77+
];
78+
79+
public static function addLogger(LoggerInterface $logger): void
80+
{
81+
$instance = self::getInstance();
82+
83+
$instance->loggers->attach($logger);
84+
85+
addSubscriber($instance);
86+
}
87+
88+
/**
89+
* Forwards a log message from libmongoc/PHPC to all registered PSR loggers.
90+
*
91+
* @see LogSubscriber::log()
92+
*/
93+
public function log(int $mongocLevel, string $domain, string $message): void
94+
{
95+
if (! isset(self::MONGOC_TO_PSR[$mongocLevel])) {
96+
throw new UnexpectedValueException(sprintf(
97+
'Expected level to be >= %d and <= %d, %d given for domain "%s" and message: %s',
98+
LogSubscriber::LEVEL_ERROR,
99+
LogSubscriber::LEVEL_DEBUG,
100+
$mongocLevel,
101+
$domain,
102+
$message,
103+
));
104+
}
105+
106+
$instance = self::getInstance();
107+
$psrLevel = self::MONGOC_TO_PSR[$mongocLevel];
108+
$context = ['domain' => $domain];
109+
110+
foreach ($instance->loggers as $logger) {
111+
$logger->log($psrLevel, $message, $context);
112+
}
113+
}
114+
115+
public static function removeLogger(LoggerInterface $logger): void
116+
{
117+
$instance = self::getInstance();
118+
$instance->loggers->detach($logger);
119+
120+
if ($instance->loggers->count() === 0) {
121+
removeSubscriber($instance);
122+
}
123+
}
124+
125+
/**
126+
* Writes a log message to all registered PSR loggers.
127+
*
128+
* This function is intended for internal use within the library.
129+
*/
130+
public static function writeLog(int $specLevel, string $domain, string $message): void
131+
{
132+
if (! isset(self::SPEC_TO_PSR[$specLevel])) {
133+
throw new UnexpectedValueException(sprintf(
134+
'Expected level to be >= %d and <= %d, %d given for domain "%s" and message: %s',
135+
self::EMERGENCY,
136+
self::TRACE,
137+
$specLevel,
138+
$domain,
139+
$message,
140+
));
141+
}
142+
143+
$instance = self::getInstance();
144+
$psrLevel = self::SPEC_TO_PSR[$specLevel];
145+
$context = ['domain' => $domain];
146+
147+
foreach ($instance->loggers as $logger) {
148+
$logger->log($psrLevel, $message, $context);
149+
}
150+
}
151+
152+
private function __construct()
153+
{
154+
$this->loggers = new SplObjectStorage();
155+
}
156+
157+
private static function getInstance(): self
158+
{
159+
return self::$instance ??= new self();
160+
}
161+
}

src/functions.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use MongoDB\Exception\RuntimeException;
3232
use MongoDB\Operation\ListCollections;
3333
use MongoDB\Operation\WithTransaction;
34+
use Psr\Log\LoggerInterface;
3435
use ReflectionClass;
3536
use ReflectionException;
3637

@@ -46,6 +47,28 @@
4647
use function MongoDB\BSON\toPHP;
4748
use function substr;
4849

50+
/**
51+
* Registers a PSR-3 logger to receive log messages from the driver/library.
52+
*
53+
* Calling this method again with a logger that has already been added will have
54+
* no effect.
55+
*/
56+
function addLogger(LoggerInterface $logger): void
57+
{
58+
PsrLogAdapter::addLogger($logger);
59+
}
60+
61+
/**
62+
* Unregisters a PSR-3 logger.
63+
*
64+
* Calling this method with a logger that has not been added will have no
65+
* effect.
66+
*/
67+
function removeLogger(LoggerInterface $logger): void
68+
{
69+
PsrLogAdapter::removeLogger($logger);
70+
}
71+
4972
/**
5073
* Check whether all servers support executing a write stage on a secondary.
5174
*

0 commit comments

Comments
 (0)