From 60456bfa08422cd49302335b5fd84412b331973a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 25 Jan 2024 14:19:26 +0100 Subject: [PATCH] Upgrade to monolog3 --- composer.json | 2 +- phpstan.neon | 2 +- .../LogHandler/TracingHandlerSpec.php | 32 +++++++++++------ .../Logging/Formatter/StdOutFormatter.php | 6 +++- src/DependencyInjection/Configuration.php | 7 ++-- src/Logging/Formatter/JsonFormatter.php | 5 +-- .../Processor/NormalizeExceptionProcessor.php | 16 ++++----- .../Processor/TraceContextProcessor.php | 36 ++++++++++--------- .../LogHandler/TracingHandler.php | 17 ++++----- 9 files changed, 71 insertions(+), 52 deletions(-) diff --git a/composer.json b/composer.json index 95bafbb..2f3337f 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "email": "developers@worldia.com" }], "require": { - "monolog/monolog": "^2.0", + "monolog/monolog": "^3.0", "nyholm/dsn": "^2.0", "nyholm/psr7": "^1.5", "open-telemetry/api": ">=1.0.2", diff --git a/phpstan.neon b/phpstan.neon index 7c058d5..5e0824f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,2 +1,2 @@ parameters: - level: 8 + level: 8 \ No newline at end of file diff --git a/spec/Tracing/Instrumentation/LogHandler/TracingHandlerSpec.php b/spec/Tracing/Instrumentation/LogHandler/TracingHandlerSpec.php index b1d82cd..d09e6bf 100644 --- a/spec/Tracing/Instrumentation/LogHandler/TracingHandlerSpec.php +++ b/spec/Tracing/Instrumentation/LogHandler/TracingHandlerSpec.php @@ -8,7 +8,9 @@ namespace spec\Instrumentation\Tracing\Instrumentation\LogHandler; use Instrumentation\Tracing\Instrumentation\MainSpanContextInterface; -use Monolog\Logger; +use Monolog\DateTimeImmutable; +use Monolog\Level; +use Monolog\LogRecord; use OpenTelemetry\API\Trace\SpanInterface; use OpenTelemetry\API\Trace\TracerProviderInterface; use PhpSpec\ObjectBehavior; @@ -21,13 +23,13 @@ public function it_adds_event_from_all_channels( MainSpanContextInterface $mainSpanContext, SpanInterface $span ): void { - $this->beConstructedWith($tracerProvider, $mainSpanContext, Logger::INFO, []); + $this->beConstructedWith($tracerProvider, $mainSpanContext, Level::Info, []); $mainSpanContext->getMainSpan()->willReturn($span); $span->addEvent(Argument::any())->willReturn($span); - $this->handle(['message' => 'Error from channel "foo"', 'channel' => 'foo', 'level' => Logger::ERROR, 'extra' => [], 'context' => []]); - $this->handle(['message' => 'Error from channel "bar"', 'channel' => 'bar', 'level' => Logger::ERROR, 'extra' => [], 'context' => []]); + $this->handle($this->createLogRecord('foo')); + $this->handle($this->createLogRecord('bar')); $span->addEvent('Error from channel "foo"')->shouldHaveBeenCalled(); $span->addEvent('Error from channel "bar"')->shouldHaveBeenCalled(); @@ -38,13 +40,13 @@ public function it_adds_event_from_specific_channel_only( MainSpanContextInterface $mainSpanContext, SpanInterface $span ): void { - $this->beConstructedWith($tracerProvider, $mainSpanContext, Logger::INFO, ['foo']); + $this->beConstructedWith($tracerProvider, $mainSpanContext, Level::Info, ['foo']); $mainSpanContext->getMainSpan()->willReturn($span); $span->addEvent(Argument::any())->willReturn($span); - $this->handle(['message' => 'Error from channel "foo"', 'channel' => 'foo', 'level' => Logger::ERROR, 'extra' => [], 'context' => []]); - $this->handle(['message' => 'Error from channel "bar"', 'channel' => 'bar', 'level' => Logger::ERROR, 'extra' => [], 'context' => []]); + $this->handle($this->createLogRecord('foo')); + $this->handle($this->createLogRecord('bar')); $span->addEvent('Error from channel "foo"')->shouldHaveBeenCalled(); $span->addEvent('Error from channel "bar"')->shouldNotHaveBeenCalled(); @@ -55,15 +57,25 @@ public function it_ignores_event_from_specific_channel( MainSpanContextInterface $mainSpanContext, SpanInterface $span ): void { - $this->beConstructedWith($tracerProvider, $mainSpanContext, Logger::INFO, ['!foo']); + $this->beConstructedWith($tracerProvider, $mainSpanContext, Level::Info, ['!foo']); $mainSpanContext->getMainSpan()->willReturn($span); $span->addEvent(Argument::any())->willReturn($span); - $this->handle(['message' => 'Error from channel "foo"', 'channel' => 'foo', 'level' => Logger::ERROR, 'extra' => [], 'context' => []]); - $this->handle(['message' => 'Error from channel "bar"', 'channel' => 'bar', 'level' => Logger::ERROR, 'extra' => [], 'context' => []]); + $this->handle($this->createLogRecord('foo')); + $this->handle($this->createLogRecord('bar')); $span->addEvent('Error from channel "foo"')->shouldNotHaveBeenCalled(); $span->addEvent('Error from channel "bar"')->shouldHaveBeenCalled(); } + + private function createLogRecord(string $chanel): LogRecord + { + return new LogRecord( + new DateTimeImmutable(true), + $chanel, + Level::Error, + 'Error from channel "'.$chanel.'"', + ); + } } diff --git a/src/Bridge/GoogleCloud/Logging/Formatter/StdOutFormatter.php b/src/Bridge/GoogleCloud/Logging/Formatter/StdOutFormatter.php index f0d6506..5a141cb 100644 --- a/src/Bridge/GoogleCloud/Logging/Formatter/StdOutFormatter.php +++ b/src/Bridge/GoogleCloud/Logging/Formatter/StdOutFormatter.php @@ -24,12 +24,16 @@ public function __construct(private string $project) * @see https://cloud.google.com/logging/docs/agent/configuration#process-payload * @see https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud/blob/master/lib/fluent/plugin/out_google_cloud.rb */ - protected function normalize($data, int $depth = 0) + protected function normalize(mixed $data, int $depth = 0): mixed { $data = parent::normalize($data, $depth); + if (!\is_array($data)) { + return $data; + } if ($depth < 1) { // Map timestamp + // @phpstan-ignore-next-line $date = new \DateTime($data['datetime']); $data['timestamp'] = [ 'seconds' => $date->getTimestamp(), diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 606003e..a5b1683 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -8,7 +8,6 @@ namespace Instrumentation\DependencyInjection; use Monolog\Level; -use Monolog\Logger; use OpenTelemetry\SemConv\ResourceAttributes; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -90,9 +89,9 @@ public function getConfigTreeBuilder(): TreeBuilder ->addDefaultsIfNotSet() ->children() ->enumNode('level') - ->defaultValue(Logger::INFO) - ->values(method_exists(Logger::class, 'getLevels') ? Logger::getLevels() : Level::cases()) // @phpstan-ignore-line - ->info(sprintf('One of the %s levels.', Logger::class)) + ->defaultValue(Level::Info) + ->values(Level::cases()) + ->info(sprintf('One of the %s levels.', Level::class)) ->end() ->arrayNode('channels') ->defaultValue([]) diff --git a/src/Logging/Formatter/JsonFormatter.php b/src/Logging/Formatter/JsonFormatter.php index bd02ccb..64d10a3 100644 --- a/src/Logging/Formatter/JsonFormatter.php +++ b/src/Logging/Formatter/JsonFormatter.php @@ -10,15 +10,16 @@ namespace Instrumentation\Logging\Formatter; use Monolog\Formatter\JsonFormatter as BaseJsonFormatter; +use Monolog\LogRecord; use Monolog\Utils; class JsonFormatter extends BaseJsonFormatter { private ?int $lengthLimit = null; - public function format(array $record): string + public function format(LogRecord $record): string { - $normalized = $this->normalize($record); + $normalized = $this->normalizeRecord($record); $json = $this->toJson($normalized, true); $length = \strlen($json); diff --git a/src/Logging/Processor/NormalizeExceptionProcessor.php b/src/Logging/Processor/NormalizeExceptionProcessor.php index 08cb26d..91e4be6 100644 --- a/src/Logging/Processor/NormalizeExceptionProcessor.php +++ b/src/Logging/Processor/NormalizeExceptionProcessor.php @@ -8,18 +8,16 @@ namespace Instrumentation\Logging\Processor; use Instrumentation\Semantics\Normalizer\ExceptionNormalizer; +use Monolog\LogRecord; +use Monolog\Processor\ProcessorInterface; -class NormalizeExceptionProcessor +class NormalizeExceptionProcessor implements ProcessorInterface { - /** - * @param array $record - * - * @return array - */ - public function __invoke(array $record): array + public function __invoke(LogRecord $record): LogRecord { - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { - $record['context']['exception'] = ExceptionNormalizer::normalizeException($record['context']['exception']); + if (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { + // @phpstan-ignore-next-line + $record->context['exception'] = ExceptionNormalizer::normalizeException($record->context['exception']); } return $record; diff --git a/src/Logging/Processor/TraceContextProcessor.php b/src/Logging/Processor/TraceContextProcessor.php index 917d064..f9df0fd 100644 --- a/src/Logging/Processor/TraceContextProcessor.php +++ b/src/Logging/Processor/TraceContextProcessor.php @@ -7,9 +7,11 @@ namespace Instrumentation\Logging\Processor; +use Monolog\LogRecord; +use Monolog\Processor\ProcessorInterface; use OpenTelemetry\SDK\Trace\Span; -class TraceContextProcessor +class TraceContextProcessor implements ProcessorInterface { /** * @param array{trace:array,span:array,sampled:array,operation:array} $map @@ -18,12 +20,7 @@ public function __construct(private array $map) { } - /** - * @param array $record - * - * @return array - */ - public function __invoke(array $record): array + public function __invoke(LogRecord $record): LogRecord { $span = Span::getCurrent(); $spanContext = $span->getContext(); @@ -32,28 +29,35 @@ public function __invoke(array $record): array return $record; } - $this->setRecordKey($record, $this->map['trace'], $spanContext->getTraceId()); - $this->setRecordKey($record, $this->map['span'], $spanContext->getSpanId()); - $this->setRecordKey($record, $this->map['sampled'], $spanContext->isSampled()); + $record = $this->withRecordKey($record, $this->map['trace'], $spanContext->getTraceId()); + $record = $this->withRecordKey($record, $this->map['span'], $spanContext->getSpanId()); + $record = $this->withRecordKey($record, $this->map['sampled'], $spanContext->isSampled()); if ($span instanceof Span) { - $this->setRecordKey($record, $this->map['operation'], $span->getName()); + $record = $this->withRecordKey($record, $this->map['operation'], $span->getName()); } return $record; } /** - * @param array $record - * @param array $keys - * @param string|bool|int $value + * @param array $keys + * @param string|bool|int $value */ - private function setRecordKey(array &$record, array $keys, $value): void + private function withRecordKey(LogRecord $record, array $keys, $value): LogRecord { - $temp = &$record; + if ([] === $keys) { + return $record; + } + $first = array_shift($keys); + + $array = $record[$first]; + $temp = &$array; foreach ($keys as $key) { $temp = &$temp[$key]; } $temp = $value; + + return $record->with(...[$first => $array]); } } diff --git a/src/Tracing/Instrumentation/LogHandler/TracingHandler.php b/src/Tracing/Instrumentation/LogHandler/TracingHandler.php index d624bfd..02e64e0 100644 --- a/src/Tracing/Instrumentation/LogHandler/TracingHandler.php +++ b/src/Tracing/Instrumentation/LogHandler/TracingHandler.php @@ -11,7 +11,8 @@ use Instrumentation\Tracing\Instrumentation\MainSpanContextInterface; use Monolog\Handler\AbstractProcessingHandler; -use Monolog\Logger; +use Monolog\Level; +use Monolog\LogRecord; use Monolog\Processor\PsrLogMessageProcessor; use OpenTelemetry\API\Trace\TracerProviderInterface; use OpenTelemetry\SDK\Trace\Span; @@ -29,7 +30,7 @@ class TracingHandler extends AbstractProcessingHandler /** * @param array $channels */ - public function __construct(protected TracerProviderInterface $tracerProvider, protected MainSpanContextInterface $mainSpanContext, $level = Logger::INFO, private array $channels = [], private string $strategy = self::STRATEGY_MAIN_SPAN, bool $bubble = true) + public function __construct(protected TracerProviderInterface $tracerProvider, protected MainSpanContextInterface $mainSpanContext, $level = Level::Info, private array $channels = [], private string $strategy = self::STRATEGY_MAIN_SPAN, bool $bubble = true) { parent::__construct($level, $bubble); @@ -43,13 +44,13 @@ public function __construct(protected TracerProviderInterface $tracerProvider, p $this->channels = array_filter($this->channels, fn (string $channel) => !str_starts_with($channel, '!')); } - protected function write(array $record): void + protected function write(LogRecord $record): void { - if ($this->channels && !\in_array($record['channel'], $this->channels)) { + if ($this->channels && !\in_array($record->channel, $this->channels)) { return; } - if ($this->excludedChannels && \in_array($record['channel'], $this->excludedChannels)) { + if ($this->excludedChannels && \in_array($record->channel, $this->excludedChannels)) { return; } @@ -59,10 +60,10 @@ protected function write(array $record): void default => throw new \InvalidArgumentException(sprintf('Unkown strategy "%s".', $this->strategy)) }; - if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) { - $span->recordException($record['context']['exception'], ['raw_stacktrace' => $record['context']['exception']->getTraceAsString()]); + if (isset($record->context['exception']) && $record->context['exception'] instanceof \Throwable) { + $span->recordException($record->context['exception'], ['raw_stacktrace' => $record->context['exception']->getTraceAsString()]); } else { - $span->addEvent($record['message']); + $span->addEvent($record->message); } } }