2
0
Эх сурвалжийг харах

Add ClosureContextProcessor (#1745)


Co-authored-by: Jordi Boggiano <j.boggiano@seld.be>
Sergey Rabochiy 2 жил өмнө
parent
commit
e18aa33d2f

+ 4 - 0
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)
 ### 3.2.0 (2022-07-24)
 
 
   * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)
   * Deprecated `CubeHandler` and `PHPConsoleHandler` as both projects are abandoned and those should not be used anymore (#1734)

+ 1 - 0
doc/02-handlers-formatters-processors.md

@@ -160,6 +160,7 @@
 ## Processors
 ## 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']`.
 - [_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.
 - [_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.
 - [_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.
 - [_MemoryUsageProcessor_](https://github.com/Seldaek/monolog/blob/main/src/Monolog/Processor/MemoryUsageProcessor.php): Adds the current memory usage to a log record.

+ 51 - 0
src/Monolog/Processor/ClosureContextProcessor.php

@@ -0,0 +1,51 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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;
+    }
+}

+ 69 - 0
tests/Monolog/Processor/ClosureContextProcessorTest.php

@@ -0,0 +1,69 @@
+<?php declare(strict_types=1);
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * 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']];
+    }
+}