Skip to content

Commit 5c8bbc4

Browse files
committed
Changes for phpcs
1 parent 88e5e50 commit 5c8bbc4

File tree

3 files changed

+113
-16
lines changed

3 files changed

+113
-16
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/*
3+
* This file is part of the MODX Revolution package.
4+
*
5+
* Copyright (c) MODX, LLC
6+
*
7+
* For complete copyright and license information, see the COPYRIGHT and LICENSE
8+
* files found in the top-level directory of this distribution.
9+
*
10+
* @package modx-test
11+
*/
12+
13+
namespace MODX\Revolution\Tests\Model;
14+
15+
use Psr\Log\AbstractLogger;
16+
17+
class TestLogger extends AbstractLogger
18+
{
19+
/** @var array */
20+
public $records = [];
21+
22+
public function log($level, $message, array $context = []): void
23+
{
24+
$this->records[] = [
25+
'level' => $level,
26+
'message' => $message,
27+
'context' => $context,
28+
];
29+
}
30+
}

_build/test/Tests/Model/modXLoggingTest.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
*
1010
* @package modx-test
1111
*/
12+
1213
namespace MODX\Revolution\Tests\Model;
1314

1415
use MODX\Revolution\modX;
1516
use MODX\Revolution\MODxTestCase;
16-
use Psr\Log\AbstractLogger;
1717
use Psr\Log\LoggerInterface;
1818
use Psr\Log\LogLevel;
1919
use xPDO\xPDO;
@@ -427,18 +427,3 @@ public function testPsrLoggerStringifiesNonStringMessages()
427427
$this->assertStringContainsString('value', $logger->records[0]['message']);
428428
}
429429
}
430-
431-
class TestLogger extends AbstractLogger
432-
{
433-
/** @var array */
434-
public $records = [];
435-
436-
public function log($level, $message, array $context = []): void
437-
{
438-
$this->records[] = [
439-
'level' => $level,
440-
'message' => $message,
441-
'context' => $context,
442-
];
443-
}
444-
}

draft-pr.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# PSR-3 logging support in MODX
2+
3+
## What this does
4+
5+
MODX currently can't use standard PHP logging tools like Monolog. The `modX::_log()` override bypasses xPDO's new PSR-3 support (commit `55fd34318a`) in several code paths, so a logger registered on xPDO never sees all of MODX's log output.
6+
7+
This PR adds `setLogger(LoggerInterface $logger)` to modX, restructures `_log()` to dispatch to a PSR-3 logger when one is set, and adds 20 new tests (11 baseline, 9 PSR-3).
8+
9+
```php
10+
use Monolog\Logger;
11+
use Monolog\Handler\StreamHandler;
12+
13+
$logger = new Logger('modx');
14+
$logger->pushHandler(new StreamHandler('/path/to/modx.log', Logger::DEBUG));
15+
$logger->pushProcessor(new Monolog\Processor\IntrospectionProcessor());
16+
17+
$modx->setLogger($logger);
18+
$modx->setLogLevel(modX::LOG_LEVEL_DEBUG); // control what reaches the logger
19+
```
20+
21+
## Backward compatibility
22+
23+
When no PSR-3 logger is registered (`$this->logger === null`, the default), all logging behavior is identical to the previous implementation. 11 baseline tests verify this.
24+
25+
## Design decisions and alternatives
26+
27+
Three questions came up during implementation that have meaningful trade-offs. The current implementation reflects one set of choices, but these could reasonably go a different way.
28+
29+
### 1. Should the PSR-3 logger respect modX's `logLevel`, or receive everything?
30+
31+
**Options considered:**
32+
33+
- **A. Respect `logLevel` (chosen):** The PSR-3 logger only sees messages that pass the `logLevel` threshold. Users set `$modx->setLogLevel(modX::LOG_LEVEL_DEBUG)` when they want full visibility, and can filter further in their logger config (e.g. Monolog handler levels).
34+
35+
- **B. Send everything to the logger:** The PSR-3 logger receives all messages regardless of `logLevel`. The logger's own filtering decides what to keep.
36+
37+
**Why A was chosen:** Performance. Every `log()` call that isn't filtered out early triggers `debug_backtrace()` in the legacy path to resolve the calling file and line. The early `logLevel` check (an integer comparison) skips all that work for messages below threshold. With option B and the default `LOG_LEVEL_ERROR`, every DEBUG/INFO/WARN call would still build context and dispatch to the logger. The trade-off is one extra line of config (`setLogLevel`) when setting up a logger.
38+
39+
**If you'd prefer B:** We remove the early return at the top of `_log()` for the PSR-3 path, or add a separate threshold property for the PSR-3 logger. Note that this also interacts with decision 2 below; if the backtrace is skipped for the PSR-3 path (as it is now), the performance cost of option B is much lower.
40+
41+
### 2. Should `debug_backtrace()` be called for the PSR-3 path?
42+
43+
**Options considered:**
44+
45+
- **A. Skip backtrace for PSR-3 (chosen):** The `file` and `line` context fields are only populated if the caller explicitly passed them (e.g. `__FILE__`, `__LINE__`). When not provided, they're empty strings. Monolog users add `IntrospectionProcessor` for automatic caller info.
46+
47+
- **B. Always resolve backtrace:** Every log call resolves the backtrace so that `file`/`line` are always present in the PSR-3 context, matching legacy behavior.
48+
49+
**Why A was chosen:** `debug_backtrace()` is the most expensive operation in the log path. Monolog's `IntrospectionProcessor` does the same thing but only for messages that survive handler-level filtering — so a DEBUG message that gets discarded by the handler never triggers a backtrace. This gives users better control over the performance/detail trade-off.
50+
51+
**If you'd prefer B:** Move the backtrace resolution to before the `_dispatchToPsrLogger()` call, so the PSR-3 context always has file/line. Simpler for users who don't want to configure Monolog processors, but costs a backtrace on every dispatched log call.
52+
53+
### 3. Should the PSR-3 logger replace legacy output, or be additive?
54+
55+
**Options considered:**
56+
57+
- **A. Replace legacy output (chosen):** When a PSR-3 logger is set, legacy FILE/ECHO/HTML/ARRAY output is suppressed. Only modRegister logging is preserved alongside the PSR-3 logger.
58+
59+
- **B. Additive (both):** Both the PSR-3 logger and legacy targets receive messages. The existing `error.log` continues alongside the custom logger.
60+
61+
- **C. Configurable:** Add an option (e.g. `psr_logger_exclusive`) to let users choose at runtime.
62+
63+
**Why A was chosen:** If you've set up Monolog, you're taking control of where logs go. Continuing to write to the legacy `error.log` is confusing and potentially wasteful. modRegister is preserved because it's a message queue mechanism, not traditional logging.
64+
65+
**If you'd prefer B:** In the restructured `_log()`, remove the early return after the PSR-3 dispatch (lines 2989-2995 in the current code) and let execution fall through to the legacy path. The `$this->logger = null` / `try/finally` trick already exists for the legacy `parent::_log()` call and would prevent double-dispatch.
66+
67+
**If you'd prefer C:** Add a config option check before the early return, e.g.:
68+
```php
69+
if ($hasPsrLogger && $this->getOption('psr_logger_exclusive', null, true)) {
70+
// skip legacy
71+
return;
72+
}
73+
// fall through to legacy path
74+
```
75+
76+
### Other notes
77+
78+
- **xPDOLogger exclusion:** The bundled `xPDOLogger` (standalone xPDO's default logger) is explicitly excluded from modX's PSR-3 dispatch. Its `handleXpdo()` method calls `exit()` for FATAL, while modX uses `sendError('fatal')` which renders a proper error page. xPDOLogger is intended for standalone xPDO only.
79+
80+
- **FATAL handling:** modX continues to use `sendError('fatal')` for fatal log messages, not `exit()`. The PSR-3 logger receives the FATAL message (mapped to `LogLevel::CRITICAL`) before `sendError()` is called.
81+
82+
- **Backward compatibility:** When no PSR-3 logger is set (`$this->logger === null`, the default), all behavior is identical to the previous implementation. The baseline tests verify this.

0 commit comments

Comments
 (0)