From e18aa33d2f7e1bee700ba44cf8e5d598878bf081 Mon Sep 17 00:00:00 2001 From: Sergey Rabochiy Date: Sat, 4 Feb 2023 21:24:39 +0700 Subject: [PATCH] Add ClosureContextProcessor (#1745) Co-authored-by: Jordi Boggiano --- CHANGELOG.md | 4 ++ doc/02-handlers-formatters-processors.md | 1 + .../Processor/ClosureContextProcessor.php | 51 ++++++++++++++ .../Processor/ClosureContextProcessorTest.php | 69 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 src/Monolog/Processor/ClosureContextProcessor.php create mode 100644 tests/Monolog/Processor/ClosureContextProcessorTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fa0acea3..0ab01418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 3.3.0 (unreleased) + + * Added `ClosureContextProcessor` to allow delaying the creation of context data by setting a Closure in context which is called when the log record is used (#1745) + ### 3.2.0 (2022-07-24) * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734) diff --git a/doc/02-handlers-formatters-processors.md b/doc/02-handlers-formatters-processors.md index 96cef403..615731bc 100644 --- a/doc/02-handlers-formatters-processors.md +++ b/doc/02-handlers-formatters-processors.md @@ -160,6 +160,7 @@ ## Processors - [_PsrLogMessageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/PsrLogMessageProcessor.php): Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`. +- [_ClosureContextProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/ClosureContextProcessor.php): Allows delaying the creation of context data by setting a Closure in context which is called when the log record is used - [_IntrospectionProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/IntrospectionProcessor.php): Adds the line/file/class/method from which the log call originated. - [_WebProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/WebProcessor.php): Adds the current request URI, request method and client IP to a log record. - [_MemoryUsageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/MemoryUsageProcessor.php): Adds the current memory usage to a log record. diff --git a/src/Monolog/Processor/ClosureContextProcessor.php b/src/Monolog/Processor/ClosureContextProcessor.php new file mode 100644 index 00000000..514b3547 --- /dev/null +++ b/src/Monolog/Processor/ClosureContextProcessor.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\LogRecord; + +/** + * Generates a context from a Closure if the Closure is the only value + * in the context + * + * It helps reduce the performance impact of debug logs if they do + * need to create lots of context information. If this processor is added + * on the correct handler the context data will only be generated + * when the logs are actually logged to that handler, which is useful when + * using FingersCrossedHandler or other filtering handlers to conditionally + * log records. + */ +class ClosureContextProcessor implements ProcessorInterface +{ + public function __invoke(LogRecord $record): LogRecord + { + $context = $record->context; + if (isset($context[0]) && 1 === \count($context) && $context[0] instanceof \Closure) { + try { + $context = $context[0](); + } catch (\Throwable $e) { + $context = [ + 'error_on_context_generation' => $e->getMessage(), + 'exception' => $e, + ]; + } + + if (!\is_array($context)) { + $context = [$context]; + } + + $record = $record->with(context: $context); + } + + return $record; + } +} diff --git a/tests/Monolog/Processor/ClosureContextProcessorTest.php b/tests/Monolog/Processor/ClosureContextProcessorTest.php new file mode 100644 index 00000000..68a9e729 --- /dev/null +++ b/tests/Monolog/Processor/ClosureContextProcessorTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\Test\TestCase; + +class ClosureContextProcessorTest extends TestCase +{ + public function testReplace() + { + $context = ['obj' => new \stdClass()]; + $processor = new ClosureContextProcessor(); + + $record = $processor($this->getRecord(context: [fn () => $context])); + $this->assertSame($context, $record->context); + } + + /** + * @dataProvider getContexts + */ + public function testSkip(array $context) + { + $processor = new ClosureContextProcessor(); + + $record = $processor($this->getRecord(context: $context)); + $this->assertSame($context, $record->context); + } + + public function testClosureReturnsNotArray() + { + $object = new \stdClass(); + $processor = new ClosureContextProcessor(); + + $record = $processor($this->getRecord(context: [fn () => $object])); + $this->assertSame([$object], $record->context); + } + + public function testClosureThrows() + { + $exception = new \Exception('For test.'); + $expected = [ + 'error_on_context_generation' => 'For test.', + 'exception' => $exception, + ]; + $processor = new ClosureContextProcessor(); + + $record = $processor($this->getRecord(context: [fn () => throw $exception])); + $this->assertSame($expected, $record->context); + } + + public function getContexts(): iterable + { + yield [['foo']]; + yield [['foo' => 'bar']]; + yield [['foo', 'bar']]; + yield [['foo', fn () => 'bar']]; + yield [[fn () => 'foo', 'bar']]; + yield [['foo' => fn () => 'bar']]; + } +}