From c112e71ecbdf9196af73f6575ea33d5b5021866b Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 13 Jun 2024 15:06:33 +0300 Subject: [PATCH 01/11] Add context enricher --- CHANGELOG.md | 5 +- src/ContextEnricher/ContextEnricher.php | 124 ++++++++++++++++++ .../ContextEnricherInterface.php | 13 ++ src/Logger.php | 101 ++++---------- 4 files changed, 168 insertions(+), 75 deletions(-) create mode 100644 src/ContextEnricher/ContextEnricher.php create mode 100644 src/ContextEnricher/ContextEnricherInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 224103c6..5e96a4d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Yii Logging Library Change Log -## 2.0.1 under development +## 2.1.0 under development - Bug #84: Change the type of the `$level` parameter in the `Message` constructor to `string` (@dood-) - New #104: Add new static methods `Logger::assertLevelIsValid()`, `Logger::assertLevelIsString()` and @@ -9,6 +9,9 @@ - Bug #98: Fix error on formatting trace, when it doesn't contain "file" and "line" (@vjik) - New #108: Support of nested values in message templates' variables, e. g. `{foo.bar}` (@vjik) - Bug #89: Fix error on parse messages, that contains variables that cannot cast to a string (@vjik) +- New #109: Add context enricher (@vjik) +- Chg #109: Deprecate `Logger` methods `setTraceLevel()` and `setExcludedTracePaths()` in favor of context enricher + usage (@vjik) ## 2.0.0 May 22, 2022 diff --git a/src/ContextEnricher/ContextEnricher.php b/src/ContextEnricher/ContextEnricher.php new file mode 100644 index 00000000..dd3c78e8 --- /dev/null +++ b/src/ContextEnricher/ContextEnricher.php @@ -0,0 +1,124 @@ +setExcludedTracePaths($excludedTracePaths); + } + + public function process(array $context): array + { + $context['time'] ??= microtime(true); + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + array_shift($trace); + $context['trace'] ??= $this->collectTrace($trace); + + $context['memory'] ??= memory_get_usage(); + $context['category'] ??= CategoryFilter::DEFAULT; + return $context; + } + + /** + * Sets how much call stack information (file name and line number) should be logged for each log message. + * + * @param int $traceLevel The number of call stack information. + * + * @see self::$traceLevel + */ + public function setTraceLevel(int $traceLevel): self + { + $this->traceLevel = $traceLevel; + return $this; + } + + /** + * Sets an array of paths to exclude from tracing when tracing is enabled with {@see self::$traceLevel}. + * + * @param string[] $excludedTracePaths The paths to exclude from tracing. + * + * @throws InvalidArgumentException for non-string values. + * + * @see self::$excludedTracePaths + */ + public function setExcludedTracePaths(array $excludedTracePaths): self + { + foreach ($excludedTracePaths as $excludedTracePath) { + /** @psalm-suppress DocblockTypeContradiction */ + if (!is_string($excludedTracePath)) { + throw new InvalidArgumentException( + sprintf( + 'The trace path must be a string, %s received.', + gettype($excludedTracePath) + ) + ); + } + } + + $this->excludedTracePaths = $excludedTracePaths; + return $this; + } + + /** + * Collects a trace when tracing is enabled with {@see Logger::setTraceLevel()}. + * + * @param array $backtrace The list of call stack information. + * @psalm-param Backtrace|list $backtrace + * + * @return array Collected a list of call stack information. + * @psalm-return Backtrace + */ + private function collectTrace(array $backtrace): array + { + $traces = []; + + if ($this->traceLevel > 0) { + $count = 0; + + foreach ($backtrace as $trace) { + if (isset($trace['file'], $trace['line'])) { + $excludedMatch = array_filter( + $this->excludedTracePaths, + static fn($path) => str_contains($trace['file'], $path) + ); + + if (empty($excludedMatch)) { + unset($trace['object'], $trace['args']); + $traces[] = $trace; + if (++$count >= $this->traceLevel) { + break; + } + } + } + } + } + + return $traces; + } +} diff --git a/src/ContextEnricher/ContextEnricherInterface.php b/src/ContextEnricher/ContextEnricherInterface.php new file mode 100644 index 00000000..e36bfb30 --- /dev/null +++ b/src/ContextEnricher/ContextEnricherInterface.php @@ -0,0 +1,13 @@ +setTargets($targets); + $this->contextEnricher = $contextEnricher ?? new ContextEnricher(); register_shutdown_function(function () { // make regular flush before other shutdown functions, which allows session data collection and so on @@ -154,12 +147,11 @@ public function log(mixed $level, string|Stringable $message, array $context = [ { self::assertLevelIsString($level); - $context['time'] ??= microtime(true); - $context['trace'] ??= $this->collectTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)); - $context['memory'] ??= memory_get_usage(); - $context['category'] ??= CategoryFilter::DEFAULT; - - $this->messages[] = new Message($level, $message, $context); + $this->messages[] = new Message( + $level, + $message, + $this->contextEnricher->process($context), + ); if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { $this->flush(); @@ -199,11 +191,15 @@ public function setFlushInterval(int $flushInterval): self * * @param int $traceLevel The number of call stack information. * - * @see Logger::$traceLevel + * @deprecated since 2.1, to be removed in 3.0 version. Use {@see self::$contextEnricher} + * and {@see ContextEnricher::setTraceLevel()} instead. */ public function setTraceLevel(int $traceLevel): self { - $this->traceLevel = $traceLevel; + if (!$this->contextEnricher instanceof ContextEnricher) { + throw new RuntimeException(); + } + $this->contextEnricher->setTraceLevel($traceLevel); return $this; } @@ -214,21 +210,15 @@ public function setTraceLevel(int $traceLevel): self * * @throws InvalidArgumentException for non-string values. * - * @see Logger::$excludedTracePaths + * @deprecated since 2.1, to be removed in 3.0 version. Use {@see self::$contextEnricher} + * and {@see ContextEnricher::setExcludedTracePaths()} instead. */ public function setExcludedTracePaths(array $excludedTracePaths): self { - foreach ($excludedTracePaths as $excludedTracePath) { - /** @psalm-suppress DocblockTypeContradiction */ - if (!is_string($excludedTracePath)) { - throw new InvalidArgumentException(sprintf( - 'The trace path must be a string, %s received.', - gettype($excludedTracePath) - )); - } + if (!$this->contextEnricher instanceof ContextEnricher) { + throw new RuntimeException(); } - - $this->excludedTracePaths = $excludedTracePaths; + $this->contextEnricher->setExcludedTracePaths($excludedTracePaths); return $this; } @@ -335,41 +325,4 @@ private function dispatch(array $messages, bool $final): void $this->dispatch($targetErrors, true); } } - - /** - * Collects a trace when tracing is enabled with {@see Logger::setTraceLevel()}. - * - * @param array $backtrace The list of call stack information. - * @psalm-param Backtrace|list $backtrace - * - * @return array Collected a list of call stack information. - * @psalm-return Backtrace - */ - private function collectTrace(array $backtrace): array - { - $traces = []; - - if ($this->traceLevel > 0) { - $count = 0; - - foreach ($backtrace as $trace) { - if (isset($trace['file'], $trace['line'])) { - $excludedMatch = array_filter( - $this->excludedTracePaths, - static fn ($path) => str_contains($trace['file'], $path) - ); - - if (empty($excludedMatch)) { - unset($trace['object'], $trace['args']); - $traces[] = $trace; - if (++$count >= $this->traceLevel) { - break; - } - } - } - } - } - - return $traces; - } } From 6891c918e5bd17e9d43aacce2df83a6b8490f720 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 13 Jun 2024 15:12:54 +0300 Subject: [PATCH 02/11] Fix psalm --- src/ContextEnricher/ContextEnricher.php | 9 +++++++-- src/Logger.php | 6 ------ src/Message.php | 24 +++--------------------- src/Message/Formatter.php | 11 ++++++----- 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/ContextEnricher/ContextEnricher.php b/src/ContextEnricher/ContextEnricher.php index dd3c78e8..ce69f632 100644 --- a/src/ContextEnricher/ContextEnricher.php +++ b/src/ContextEnricher/ContextEnricher.php @@ -5,11 +5,16 @@ namespace Yiisoft\Log\ContextEnricher; use InvalidArgumentException; -use Yiisoft\Log\Message; use Yiisoft\Log\Message\CategoryFilter; /** - * @psalm-import-type Backtrace from Message + * @psalm-type Backtrace = list */ final class ContextEnricher implements ContextEnricherInterface { diff --git a/src/Logger.php b/src/Logger.php index 05424f7a..b86b36fd 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -33,8 +33,6 @@ * * When the application ends or {@see Logger::$flushInterval} is reached, Logger will call {@see Logger::flush()} * to send logged messages to different log targets, such as file or email according to the {@see Logger::$targets}. - * - * @psalm-import-type LogMessageContext from Message */ final class Logger implements LoggerInterface { @@ -139,10 +137,6 @@ public function getTargets(): array return $this->targets; } - /** - * @psalm-param LogMessageContext $context - * @psalm-suppress MoreSpecificImplementedParamType,MixedArgumentTypeCoercion - */ public function log(mixed $level, string|Stringable $message, array $context = []): void { self::assertLevelIsString($level); diff --git a/src/Message.php b/src/Message.php index a9ce4d08..519aed24 100644 --- a/src/Message.php +++ b/src/Message.php @@ -15,20 +15,6 @@ /** * Message is a data object that stores log message data. - * - * @psalm-type Backtrace = list - * @psalm-type LogMessageContext = array{ - * category?:string, - * memory?:int, - * time?:float, - * trace?:Backtrace, - * }&array */ final class Message { @@ -45,8 +31,7 @@ final class Message private string $message; /** - * @var array Log message context. - * @psalm-var LogMessageContext + * @var array Log message context. * * Message context has a following keys: * @@ -60,8 +45,7 @@ final class Message /** * @param string $level Log message level. * @param string|Stringable $message Log message. - * @param array $context Log message context. - * @psalm-param LogMessageContext $context + * @param array $context Log message context. * * @throws InvalidArgumentException for invalid log message level. * @@ -105,7 +89,6 @@ public function message(): string * @param mixed $default If the context parameter does not exist, the `$default` will be returned. * * @return mixed The context parameter value. - * @psalm-return LogMessageContext|mixed */ public function context(string $name = null, mixed $default = null): mixed { @@ -121,8 +104,7 @@ public function context(string $name = null, mixed $default = null): mixed * where foo will be replaced by the context data in key "foo". * * @param string|Stringable $message Raw log message. - * @param array $context Message context. - * @psalm-param LogMessageContext $context + * @param array $context Message context. * * @return string Parsed message. */ diff --git a/src/Message/Formatter.php b/src/Message/Formatter.php index 232259ce..29063130 100644 --- a/src/Message/Formatter.php +++ b/src/Message/Formatter.php @@ -21,8 +21,6 @@ * Formatter formats log messages. * * @internal - * - * @psalm-import-type Backtrace from Message */ final class Formatter { @@ -233,14 +231,17 @@ private function getContext(Message $message, array $commonContext): string */ private function getTrace(Message $message): string { - /** @psalm-var Backtrace $traces */ $traces = $message->context('trace', []); - if (empty($traces)) { + if (empty($traces) || !is_array($traces)) { return ''; } $lines = array_map( - static function (array $trace): string { + static function (mixed $trace): string { + if (!is_array($trace)) { + return '???'; + } + $file = $trace['file'] ?? null; $line = $trace['line'] ?? null; if (is_string($file) && is_int($line)) { From 728de563740b167be0be5d0e882ad13342642e83 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 13 Jun 2024 15:19:36 +0300 Subject: [PATCH 03/11] tests --- src/Logger.php | 8 ++++++-- tests/LoggerTest.php | 22 +++++++++++++++++++++- tests/Message/FormatterTest.php | 17 +++++++++++++++++ tests/TestAsset/StubContextEnricher.php | 15 +++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 tests/TestAsset/StubContextEnricher.php diff --git a/src/Logger.php b/src/Logger.php index b86b36fd..39443e14 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -191,7 +191,9 @@ public function setFlushInterval(int $flushInterval): self public function setTraceLevel(int $traceLevel): self { if (!$this->contextEnricher instanceof ContextEnricher) { - throw new RuntimeException(); + throw new RuntimeException( + '"Logger::setTraceLevel()" is unavailable when using a custom context enricher.' + ); } $this->contextEnricher->setTraceLevel($traceLevel); return $this; @@ -210,7 +212,9 @@ public function setTraceLevel(int $traceLevel): self public function setExcludedTracePaths(array $excludedTracePaths): self { if (!$this->contextEnricher instanceof ContextEnricher) { - throw new RuntimeException(); + throw new RuntimeException( + '"Logger::setExcludedTracePaths()" is unavailable when using a custom context enricher.' + ); } $this->contextEnricher->setExcludedTracePaths($excludedTracePaths); return $this; diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 69b4a404..a9c2d56d 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -16,6 +16,8 @@ use Yiisoft\Log\Target; use Yiisoft\Log\Tests\TestAsset\DummyTarget; +use Yiisoft\Log\Tests\TestAsset\StubContextEnricher; + use function memory_get_usage; final class LoggerTest extends TestCase @@ -67,7 +69,7 @@ public function testLogWithTraceLevel(): void $this->assertSame('application', $messages[0]->context('category')); $this->assertSame([ 'file' => __FILE__, - 'line' => 61, + 'line' => 63, 'function' => 'log', 'class' => Logger::class, 'type' => '->', @@ -402,6 +404,24 @@ public function testDispatchWithFakeTarget2ThrowExceptionWhenCollect(): void $logger->flush(true); } + public function testSetTraceLevelWithCustomContextEnricher(): void + { + $logger = new Logger(contextEnricher: new StubContextEnricher()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('"Logger::setTraceLevel()" is unavailable when using a custom context enricher.'); + $logger->setTraceLevel(0); + } + + public function testSetExcludedTracePathsWithCustomContextEnricher(): void + { + $logger = new Logger(contextEnricher: new StubContextEnricher()); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('"Logger::setExcludedTracePaths()" is unavailable when using a custom context enricher.'); + $logger->setExcludedTracePaths([]); + } + /** * Sets an inaccessible object property to a designated value. * diff --git a/tests/Message/FormatterTest.php b/tests/Message/FormatterTest.php index 1fc74bb0..1cc16de2 100644 --- a/tests/Message/FormatterTest.php +++ b/tests/Message/FormatterTest.php @@ -213,6 +213,23 @@ public function testFormatWithTraceInContext(string $expectedTrace, array $trace $this->assertSame($expected, $this->formatter->format($message, [])); } + public function testNonArrayTraceItem() + { + $timestamp = 1_508_160_390; + $this->formatter->setTimestampFormat('Y-m-d H:i:s'); + $message = new Message( + LogLevel::INFO, + 'message', + ['category' => 'app', 'time' => $timestamp, 'trace' => [new stdClass()]], + ); + + $expected = "2017-10-16 13:26:30 [info][app] message\n\nMessage context:\n\n" + . "trace:\n ???\n" + . "category: 'app'\ntime: $timestamp\n"; + + $this->assertSame($expected, $this->formatter->format($message, [])); + } + public function invalidCallableReturnStringProvider(): array { return [ diff --git a/tests/TestAsset/StubContextEnricher.php b/tests/TestAsset/StubContextEnricher.php new file mode 100644 index 00000000..86c030bd --- /dev/null +++ b/tests/TestAsset/StubContextEnricher.php @@ -0,0 +1,15 @@ + Date: Thu, 13 Jun 2024 12:19:59 +0000 Subject: [PATCH 04/11] Apply fixes from StyleCI --- src/Logger.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Logger.php b/src/Logger.php index 39443e14..d74f2e57 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -85,8 +85,7 @@ final class Logger implements LoggerInterface public function __construct( array $targets = [], ?ContextEnricherInterface $contextEnricher = null, - ) - { + ) { $this->setTargets($targets); $this->contextEnricher = $contextEnricher ?? new ContextEnricher(); From f5c13c5d8f8a18e1cb32eccc9ba5f14ca7be4e38 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 13 Jun 2024 15:21:28 +0300 Subject: [PATCH 05/11] fix --- tests/LoggerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index a9c2d56d..0330ae3b 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -15,7 +15,6 @@ use Yiisoft\Log\Message; use Yiisoft\Log\Target; use Yiisoft\Log\Tests\TestAsset\DummyTarget; - use Yiisoft\Log\Tests\TestAsset\StubContextEnricher; use function memory_get_usage; From 6c1108b603cfcd5582ec23719dc23c1147e5fea1 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Thu, 13 Jun 2024 15:23:32 +0300 Subject: [PATCH 06/11] fix --- tests/LoggerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 0330ae3b..4c2a5393 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -68,7 +68,7 @@ public function testLogWithTraceLevel(): void $this->assertSame('application', $messages[0]->context('category')); $this->assertSame([ 'file' => __FILE__, - 'line' => 63, + 'line' => 62, 'function' => 'log', 'class' => Logger::class, 'type' => '->', From 628d3d740cb6a3224f1a42af5a0c0b4815969fe7 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 14 Jun 2024 10:03:34 +0300 Subject: [PATCH 07/11] Add `CompositeContextEnricher` --- .../CompositeContextEnricher.php | 30 +++++++++++++ .../CompositeContextEnricherTest.php | 43 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/ContextEnricher/CompositeContextEnricher.php create mode 100644 tests/ContextEnricher/CompositeContextEnricherTest.php diff --git a/src/ContextEnricher/CompositeContextEnricher.php b/src/ContextEnricher/CompositeContextEnricher.php new file mode 100644 index 00000000..4f427956 --- /dev/null +++ b/src/ContextEnricher/CompositeContextEnricher.php @@ -0,0 +1,30 @@ +enrichers = $enrichers; + } + + public function process(array $context): array + { + foreach ($this->enrichers as $enricher) { + $context = $enricher->process($context); + } + return $context; + } +} diff --git a/tests/ContextEnricher/CompositeContextEnricherTest.php b/tests/ContextEnricher/CompositeContextEnricherTest.php new file mode 100644 index 00000000..d7d80c69 --- /dev/null +++ b/tests/ContextEnricher/CompositeContextEnricherTest.php @@ -0,0 +1,43 @@ +process(['key' => 'value']); + + $this->assertSame( + [ + 'key' => 'value', + 'a' => 1, + 'b' => 2, + ], + $context + ); + } +} From bb93641c1ea39e3bf010c77ed1903ff13061fe40 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Fri, 14 Jun 2024 10:22:15 +0300 Subject: [PATCH 08/11] Replace enricher to provider --- CHANGELOG.md | 4 +- .../CompositeContextEnricher.php | 30 ------------- .../ContextEnricherInterface.php | 13 ------ .../CompositeContextProvider.php | 31 +++++++++++++ .../ContextProvider.php} | 19 ++++---- .../ContextProviderInterface.php | 13 ++++++ src/Logger.php | 34 +++++++-------- .../CompositeContextEnricherTest.php | 43 ------------------- .../CompositeContextProviderTest.php | 37 ++++++++++++++++ tests/LoggerTest.php | 14 +++--- tests/TestAsset/StubContextEnricher.php | 15 ------- tests/TestAsset/StubContextProvider.php | 20 +++++++++ 12 files changed, 136 insertions(+), 137 deletions(-) delete mode 100644 src/ContextEnricher/CompositeContextEnricher.php delete mode 100644 src/ContextEnricher/ContextEnricherInterface.php create mode 100644 src/ContextProvider/CompositeContextProvider.php rename src/{ContextEnricher/ContextEnricher.php => ContextProvider/ContextProvider.php} (90%) create mode 100644 src/ContextProvider/ContextProviderInterface.php delete mode 100644 tests/ContextEnricher/CompositeContextEnricherTest.php create mode 100644 tests/ContextProvider/CompositeContextProviderTest.php delete mode 100644 tests/TestAsset/StubContextEnricher.php create mode 100644 tests/TestAsset/StubContextProvider.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e96a4d4..0acbc517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ - Bug #98: Fix error on formatting trace, when it doesn't contain "file" and "line" (@vjik) - New #108: Support of nested values in message templates' variables, e. g. `{foo.bar}` (@vjik) - Bug #89: Fix error on parse messages, that contains variables that cannot cast to a string (@vjik) -- New #109: Add context enricher (@vjik) -- Chg #109: Deprecate `Logger` methods `setTraceLevel()` and `setExcludedTracePaths()` in favor of context enricher +- New #109: Add context provider (@vjik) +- Chg #109: Deprecate `Logger` methods `setTraceLevel()` and `setExcludedTracePaths()` in favor of context provider usage (@vjik) ## 2.0.0 May 22, 2022 diff --git a/src/ContextEnricher/CompositeContextEnricher.php b/src/ContextEnricher/CompositeContextEnricher.php deleted file mode 100644 index 4f427956..00000000 --- a/src/ContextEnricher/CompositeContextEnricher.php +++ /dev/null @@ -1,30 +0,0 @@ -enrichers = $enrichers; - } - - public function process(array $context): array - { - foreach ($this->enrichers as $enricher) { - $context = $enricher->process($context); - } - return $context; - } -} diff --git a/src/ContextEnricher/ContextEnricherInterface.php b/src/ContextEnricher/ContextEnricherInterface.php deleted file mode 100644 index e36bfb30..00000000 --- a/src/ContextEnricher/ContextEnricherInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -providers = $providers; + } + + public function getContext(): array + { + $context = []; + foreach ($this->providers as $provider) { + $context = array_merge($context, $provider->getContext()); + } + return $context; + } +} diff --git a/src/ContextEnricher/ContextEnricher.php b/src/ContextProvider/ContextProvider.php similarity index 90% rename from src/ContextEnricher/ContextEnricher.php rename to src/ContextProvider/ContextProvider.php index ce69f632..85ea1c93 100644 --- a/src/ContextEnricher/ContextEnricher.php +++ b/src/ContextProvider/ContextProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yiisoft\Log\ContextEnricher; +namespace Yiisoft\Log\ContextProvider; use InvalidArgumentException; use Yiisoft\Log\Message\CategoryFilter; @@ -16,7 +16,7 @@ * type?:string, * }> */ -final class ContextEnricher implements ContextEnricherInterface +final class ContextProvider implements ContextProviderInterface { /** * @var string[] $excludedTracePaths Array of paths to exclude from tracing when tracing is enabled. @@ -37,17 +37,16 @@ public function __construct( $this->setExcludedTracePaths($excludedTracePaths); } - public function process(array $context): array + public function getContext(): array { - $context['time'] ??= microtime(true); - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); array_shift($trace); - $context['trace'] ??= $this->collectTrace($trace); - - $context['memory'] ??= memory_get_usage(); - $context['category'] ??= CategoryFilter::DEFAULT; - return $context; + return [ + 'time' => microtime(true), + 'trace' => $this->collectTrace($trace), + 'memory' => memory_get_usage(), + 'category' => CategoryFilter::DEFAULT, + ]; } /** diff --git a/src/ContextProvider/ContextProviderInterface.php b/src/ContextProvider/ContextProviderInterface.php new file mode 100644 index 00000000..caaa95fd --- /dev/null +++ b/src/ContextProvider/ContextProviderInterface.php @@ -0,0 +1,13 @@ +setTargets($targets); - $this->contextEnricher = $contextEnricher ?? new ContextEnricher(); + $this->contextProvider = $contextProvider ?? new ContextProvider(); register_shutdown_function(function () { // make regular flush before other shutdown functions, which allows session data collection and so on @@ -143,7 +143,7 @@ public function log(mixed $level, string|Stringable $message, array $context = [ $this->messages[] = new Message( $level, $message, - $this->contextEnricher->process($context), + array_merge($this->contextProvider->getContext(), $context), ); if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { @@ -184,17 +184,17 @@ public function setFlushInterval(int $flushInterval): self * * @param int $traceLevel The number of call stack information. * - * @deprecated since 2.1, to be removed in 3.0 version. Use {@see self::$contextEnricher} - * and {@see ContextEnricher::setTraceLevel()} instead. + * @deprecated since 2.1, to be removed in 3.0 version. Use {@see self::$contextProvider} + * and {@see ContextProvider::setTraceLevel()} instead. */ public function setTraceLevel(int $traceLevel): self { - if (!$this->contextEnricher instanceof ContextEnricher) { + if (!$this->contextProvider instanceof ContextProvider) { throw new RuntimeException( - '"Logger::setTraceLevel()" is unavailable when using a custom context enricher.' + '"Logger::setTraceLevel()" is unavailable when using a custom context provider.' ); } - $this->contextEnricher->setTraceLevel($traceLevel); + $this->contextProvider->setTraceLevel($traceLevel); return $this; } @@ -205,17 +205,17 @@ public function setTraceLevel(int $traceLevel): self * * @throws InvalidArgumentException for non-string values. * - * @deprecated since 2.1, to be removed in 3.0 version. Use {@see self::$contextEnricher} - * and {@see ContextEnricher::setExcludedTracePaths()} instead. + * @deprecated since 2.1, to be removed in 3.0 version. Use {@see self::$contextProvider} + * and {@see ContextProvider::setExcludedTracePaths()} instead. */ public function setExcludedTracePaths(array $excludedTracePaths): self { - if (!$this->contextEnricher instanceof ContextEnricher) { + if (!$this->contextProvider instanceof ContextProvider) { throw new RuntimeException( - '"Logger::setExcludedTracePaths()" is unavailable when using a custom context enricher.' + '"Logger::setExcludedTracePaths()" is unavailable when using a custom context provider.' ); } - $this->contextEnricher->setExcludedTracePaths($excludedTracePaths); + $this->contextProvider->setExcludedTracePaths($excludedTracePaths); return $this; } diff --git a/tests/ContextEnricher/CompositeContextEnricherTest.php b/tests/ContextEnricher/CompositeContextEnricherTest.php deleted file mode 100644 index d7d80c69..00000000 --- a/tests/ContextEnricher/CompositeContextEnricherTest.php +++ /dev/null @@ -1,43 +0,0 @@ -process(['key' => 'value']); - - $this->assertSame( - [ - 'key' => 'value', - 'a' => 1, - 'b' => 2, - ], - $context - ); - } -} diff --git a/tests/ContextProvider/CompositeContextProviderTest.php b/tests/ContextProvider/CompositeContextProviderTest.php new file mode 100644 index 00000000..4e99aa61 --- /dev/null +++ b/tests/ContextProvider/CompositeContextProviderTest.php @@ -0,0 +1,37 @@ + 1, + 'b' => 2, + ]); + $provider2 = new StubContextProvider([ + 'b' => 3, + 'c' => 4, + ]); + + $compositeProvider = new CompositeContextProvider($provider1, $provider2); + + $context = $compositeProvider->getContext(); + + $this->assertSame( + [ + 'a' => 1, + 'b' => 3, + 'c' => 4, + ], + $context + ); + } +} diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php index 4c2a5393..46c9a038 100644 --- a/tests/LoggerTest.php +++ b/tests/LoggerTest.php @@ -15,7 +15,7 @@ use Yiisoft\Log\Message; use Yiisoft\Log\Target; use Yiisoft\Log\Tests\TestAsset\DummyTarget; -use Yiisoft\Log\Tests\TestAsset\StubContextEnricher; +use Yiisoft\Log\Tests\TestAsset\StubContextProvider; use function memory_get_usage; @@ -403,21 +403,21 @@ public function testDispatchWithFakeTarget2ThrowExceptionWhenCollect(): void $logger->flush(true); } - public function testSetTraceLevelWithCustomContextEnricher(): void + public function testSetTraceLevelWithCustomContextProvider(): void { - $logger = new Logger(contextEnricher: new StubContextEnricher()); + $logger = new Logger(contextProvider: new StubContextProvider()); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('"Logger::setTraceLevel()" is unavailable when using a custom context enricher.'); + $this->expectExceptionMessage('"Logger::setTraceLevel()" is unavailable when using a custom context provider.'); $logger->setTraceLevel(0); } - public function testSetExcludedTracePathsWithCustomContextEnricher(): void + public function testSetExcludedTracePathsWithCustomContextProvider(): void { - $logger = new Logger(contextEnricher: new StubContextEnricher()); + $logger = new Logger(contextProvider: new StubContextProvider()); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('"Logger::setExcludedTracePaths()" is unavailable when using a custom context enricher.'); + $this->expectExceptionMessage('"Logger::setExcludedTracePaths()" is unavailable when using a custom context provider.'); $logger->setExcludedTracePaths([]); } diff --git a/tests/TestAsset/StubContextEnricher.php b/tests/TestAsset/StubContextEnricher.php deleted file mode 100644 index 86c030bd..00000000 --- a/tests/TestAsset/StubContextEnricher.php +++ /dev/null @@ -1,15 +0,0 @@ -context; + } +} From 1ecf7640486d0dd139ff12b994a9f27e5b4d05e3 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 17 Jun 2024 10:11:18 +0300 Subject: [PATCH 09/11] improve performance of composite context provider --- src/ContextProvider/CompositeContextProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ContextProvider/CompositeContextProvider.php b/src/ContextProvider/CompositeContextProvider.php index 959632c9..b31ef53f 100644 --- a/src/ContextProvider/CompositeContextProvider.php +++ b/src/ContextProvider/CompositeContextProvider.php @@ -22,10 +22,10 @@ public function __construct( public function getContext(): array { - $context = []; + $contexts = []; foreach ($this->providers as $provider) { - $context = array_merge($context, $provider->getContext()); + $contexts[] = $provider->getContext(); } - return $context; + return array_merge(...$contexts); } } From bad946c15d4f8bd5c5d2d10feb330e0ed6360f90 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 17 Jun 2024 10:23:59 +0300 Subject: [PATCH 10/11] improve readme --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3da224e8..2971cc50 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ $logger->info('Info message', ['key' => 'value']); $logger->debug('Debug message', ['key' => 'value']); ``` -## Message Flushing and Exporting +### Message Flushing and Exporting Log messages are collected and stored in memory. To limit memory consumption, the logger will flush the recorded messages to the log targets each time a certain number of log messages accumulate. @@ -76,7 +76,7 @@ $target->setExportInterval(100); // default is 1000 > Note: All message flushing and exporting also occurs when the application ends. -## Logging targets +### Logging targets This package contains two targets: @@ -90,6 +90,42 @@ Extra logging targets are implemented as separate packages: - [File](https://github.com/yiisoft/log-target-file) - [Syslog](https://github.com/yiisoft/log-target-syslog) +### Context providers + +Context providers are used to provide additional context data for log messages. You can define your own context provider +in `Logger` constructor: + +```php +$logger = new \Yiisoft\Log\Logger(contextProvider: $myContextProvider); +``` + +By default, the logger uses built-in `Yiisoft\Log\ContextProvider\ContextProvider` that added following data to context: + +- `time` — current Unix timestamp with microseconds (float value); +- `trace` — array of call stack information; +- `memory` — memory usage in bytes. +- `category` — category of the log message (always "application"). + +`Yiisoft\Log\ContextProvider\ContextProvider` constructor parameters: + +- `traceLevel` — how much call stack information (file name and line number) should be logged for each + log message. If it is greater than 0, at most that number of call stacks will be logged. Note that only + application call stacks are counted. +- `excludedTracePaths` — array of paths to exclude from tracing when tracing is enabled with `traceLevel`. + +Example of custom parameters' usage: + +```php +$logger = new \Yiisoft\Log\Logger( + contextProvider: new Yiisoft\Log\ContextProvider\ContextProvider( + traceLevel: 3, + excludedTracePaths: [ + '/vendor/yiisoft/di', + ], + ) +); +``` + ## Documentation - [Yii guide to logging](https://github.com/yiisoft/docs/blob/master/guide/en/runtime/logging.md) From 3c6b73779a1de1190a0a93daf9f7119b2f0b58d8 Mon Sep 17 00:00:00 2001 From: Sergei Predvoditelev Date: Mon, 17 Jun 2024 10:25:47 +0300 Subject: [PATCH 11/11] deprecate methods --- src/ContextProvider/ContextProvider.php | 7 ++++++- src/Logger.php | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ContextProvider/ContextProvider.php b/src/ContextProvider/ContextProvider.php index 85ea1c93..8fc3b59c 100644 --- a/src/ContextProvider/ContextProvider.php +++ b/src/ContextProvider/ContextProvider.php @@ -28,12 +28,13 @@ final class ContextProvider implements ContextProviderInterface * log message. If it is greater than 0, at most that number of call stacks will be logged. Note that only * application call stacks are counted. * @param string[] $excludedTracePaths Array of paths to exclude from tracing when tracing is enabled - * with {@see $traceLevel}. + * with {@see $traceLevel}. */ public function __construct( private int $traceLevel = 0, array $excludedTracePaths = [], ) { + /** @psalm-suppress DeprecatedMethod `setExcludedTracePaths` will be private and not deprecated */ $this->setExcludedTracePaths($excludedTracePaths); } @@ -55,6 +56,8 @@ public function getContext(): array * @param int $traceLevel The number of call stack information. * * @see self::$traceLevel + * + * @deprecated since 2.1.0, to be removed in 3.0.0. Use constructor parameter "traceLevel" instead. */ public function setTraceLevel(int $traceLevel): self { @@ -70,6 +73,8 @@ public function setTraceLevel(int $traceLevel): self * @throws InvalidArgumentException for non-string values. * * @see self::$excludedTracePaths + * + * @deprecated since 2.1.0, to be removed in 3.0.0. Use constructor parameter "excludedTracePaths" instead. */ public function setExcludedTracePaths(array $excludedTracePaths): self { diff --git a/src/Logger.php b/src/Logger.php index 9cf4b522..b99106f7 100644 --- a/src/Logger.php +++ b/src/Logger.php @@ -194,6 +194,7 @@ public function setTraceLevel(int $traceLevel): self '"Logger::setTraceLevel()" is unavailable when using a custom context provider.' ); } + /** @psalm-suppress DeprecatedMethod */ $this->contextProvider->setTraceLevel($traceLevel); return $this; } @@ -215,6 +216,7 @@ public function setExcludedTracePaths(array $excludedTracePaths): self '"Logger::setExcludedTracePaths()" is unavailable when using a custom context provider.' ); } + /** @psalm-suppress DeprecatedMethod */ $this->contextProvider->setExcludedTracePaths($excludedTracePaths); return $this; }