From b99fd5f8035d580c30e0a2a8c1e2e394648d0c12 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 17 Mar 2021 10:35:09 +0100 Subject: [PATCH 01/10] Add new BreadCrumbHandler --- src/Monolog/BreadcrumbHandler.php | 90 +++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/Monolog/BreadcrumbHandler.php diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php new file mode 100644 index 000000000..5398c8758 --- /dev/null +++ b/src/Monolog/BreadcrumbHandler.php @@ -0,0 +1,90 @@ +hub = $hub; + + parent::__construct($level, $bubble); + } + + /** + * @psalm-suppress MoreSpecificImplementedParamType + * + * @param array{ + * level: int, + * channel: string, + * datetime: \DateTimeImmutable, + * message: string, + * formatted?: string, + * extra?: array + * } $record {@see https://github.com/Seldaek/monolog/blob/main/doc/message-structure.md} + */ + protected function write(array $record): void + { + $breadcrumb = new Breadcrumb( + $this->getBreadcrumbLevel($record['level']), + $this->getBreadcrumbType($record['level']), + $record['channel'], + $record['formatted'] ?? $record['message'], + $record['extra'] ?? [], + $record['datetime']->getTimestamp() + ); + + $this->hub->addBreadcrumb($breadcrumb); + } + + private function getBreadcrumbLevel(int $level): string + { + switch ($level) { + case Logger::DEBUG: + return Breadcrumb::LEVEL_DEBUG; + case Logger::INFO: + case Logger::NOTICE: + return Breadcrumb::LEVEL_INFO; + case Logger::WARNING: + return Breadcrumb::LEVEL_WARNING; + case Logger::ERROR: + return Breadcrumb::LEVEL_ERROR; + default: + return Breadcrumb::LEVEL_FATAL; + } + } + + private function getBreadcrumbType(int $level): string + { + if ($level >= Logger::ERROR) { + return Breadcrumb::TYPE_ERROR; + } + + return Breadcrumb::TYPE_DEFAULT; + } +} From 3fb9d7ffb8c9095a3061210a5d2903d690f05b4c Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 17 Mar 2021 10:50:42 +0100 Subject: [PATCH 02/10] Add first unit test --- src/Monolog/BreadcrumbHandler.php | 3 +- tests/Monolog/BreadcrumbHandlerTest.php | 64 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/Monolog/BreadcrumbHandlerTest.php diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index 5398c8758..f01ce3ace 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -44,7 +44,6 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub * channel: string, * datetime: \DateTimeImmutable, * message: string, - * formatted?: string, * extra?: array * } $record {@see https://github.com/Seldaek/monolog/blob/main/doc/message-structure.md} */ @@ -54,7 +53,7 @@ protected function write(array $record): void $this->getBreadcrumbLevel($record['level']), $this->getBreadcrumbType($record['level']), $record['channel'], - $record['formatted'] ?? $record['message'], + $record['message'], $record['extra'] ?? [], $record['datetime']->getTimestamp() ); diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php new file mode 100644 index 000000000..5388a0ee3 --- /dev/null +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -0,0 +1,64 @@ +createMock(HubInterface::class); + $hub->expects($this->once()) + ->method('addBreadcrumb') + ->with($this->callback(function (Breadcrumb $breadcrumb) use ($expectedBreadcrumb): bool { + $this->assertSame($expectedBreadcrumb->getMessage(), $breadcrumb->getMessage()); + $this->assertSame($expectedBreadcrumb->getLevel(), $breadcrumb->getLevel()); + $this->assertSame($expectedBreadcrumb->getType(), $breadcrumb->getType()); + $this->assertSame($expectedBreadcrumb->getTimestamp(), $breadcrumb->getTimestamp()); + $this->assertSame($expectedBreadcrumb->getCategory(), $breadcrumb->getCategory()); + $this->assertEquals($expectedBreadcrumb->getMetadata(), $breadcrumb->getMetadata()); + + return true; + })); + + $handler = new BreadcrumbHandler($hub); + $handler->handle($record); + } + + public function handleDataProvider(): iterable + { + $defaultData = [ + 'message' => 'foo bar', + 'level' => Logger::DEBUG, + 'level_name' => Logger::getLevelName(Logger::DEBUG), + 'channel' => 'channel.foo', + 'context' => [], + 'extra' => [], + 'datetime' => new \DateTimeImmutable(), + ]; + + $defaultBreadcrumb = new Breadcrumb( + Breadcrumb::LEVEL_DEBUG, + Breadcrumb::TYPE_DEFAULT, + 'channel.foo', + 'foo bar', + [], + $defaultData['datetime']->getTimestamp() + ); + + yield [ + $defaultData, + $defaultBreadcrumb, + ]; + } +} From 2d23abeb15ea566ac212d9b6ca89f23136cde799 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 17 Mar 2021 11:03:43 +0100 Subject: [PATCH 03/10] Complete the unit tests --- src/Monolog/BreadcrumbHandler.php | 2 +- tests/Monolog/BreadcrumbHandlerTest.php | 50 +++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index f01ce3ace..e15f6103f 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -54,7 +54,7 @@ protected function write(array $record): void $this->getBreadcrumbType($record['level']), $record['channel'], $record['message'], - $record['extra'] ?? [], + ($record['context'] ?? []) + ($record['extra'] ?? []), $record['datetime']->getTimestamp() ); diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 5388a0ee3..54efe5813 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -35,6 +35,9 @@ public function testHandle(array $record, Breadcrumb $expectedBreadcrumb): void $handler->handle($record); } + /** + * @return iterable, Breadcrumb}> + */ public function handleDataProvider(): iterable { $defaultData = [ @@ -56,9 +59,50 @@ public function handleDataProvider(): iterable $defaultData['datetime']->getTimestamp() ); - yield [ - $defaultData, - $defaultBreadcrumb, + $levelsToBeTested = [ + Logger::DEBUG => Breadcrumb::LEVEL_DEBUG, + Logger::INFO => Breadcrumb::LEVEL_INFO, + Logger::NOTICE => Breadcrumb::LEVEL_INFO, + Logger::WARNING => Breadcrumb::LEVEL_WARNING, + ]; + + foreach ($levelsToBeTested as $loggerLevel => $breadcrumbLevel) { + yield 'with level ' . Logger::getLevelName($loggerLevel) => [ + ['level' => $loggerLevel] + $defaultData, + $defaultBreadcrumb->withLevel($breadcrumbLevel), + ]; + } + + yield 'with level ERROR' => [ + ['level' => Logger::ERROR] + $defaultData, + $defaultBreadcrumb->withLevel(Breadcrumb::LEVEL_ERROR) + ->withType(Breadcrumb::TYPE_ERROR), + ]; + + yield 'with level ALERT' => [ + ['level' => Logger::ALERT] + $defaultData, + $defaultBreadcrumb->withLevel(Breadcrumb::LEVEL_FATAL) + ->withType(Breadcrumb::TYPE_ERROR), + ]; + + yield 'with context' => [ + ['context' => ['foo' => 'bar']] + $defaultData, + $defaultBreadcrumb->withMetadata('foo', 'bar'), + ]; + + yield 'with extra' => [ + ['extra' => ['foo' => 'bar']] + $defaultData, + $defaultBreadcrumb->withMetadata('foo', 'bar'), + ]; + + yield 'with context + extra' => [ + [ + 'context' => ['foo' => 'bar', 'context' => 'baz'], + 'extra' => ['foo' => 'baz', 'extra' => 'baz'], + ] + $defaultData, + $defaultBreadcrumb->withMetadata('foo', 'bar') + ->withMetadata('context', 'baz') + ->withMetadata('extra', 'baz'), ]; } } From e26d547fcbdccec521dc9e4c94ed0cd4390f2f2f Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 13 Jan 2022 23:33:46 +0100 Subject: [PATCH 04/10] Fix PHPStan error --- src/Monolog/BreadcrumbHandler.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index e15f6103f..b5cf5bd68 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -6,6 +6,7 @@ use Monolog\Handler\AbstractProcessingHandler; use Monolog\Logger; +use Psr\Log\LogLevel; use Sentry\Breadcrumb; use Sentry\Event; use Sentry\State\HubInterface; @@ -14,6 +15,9 @@ /** * This Monolog handler logs every message as a {@see Breadcrumb} into the current {@see Scope}, * to enrich any event sent to Sentry. + * + * @phpstan-import-type Level from \Monolog\Logger + * @phpstan-import-type LevelName from \Monolog\Logger */ final class BreadcrumbHandler extends AbstractProcessingHandler { @@ -23,6 +27,8 @@ final class BreadcrumbHandler extends AbstractProcessingHandler private $hub; /** + * @phpstan-param Level|LevelName|LogLevel::* $level + * * @param HubInterface $hub The hub to which errors are reported * @param int|string $level The minimum logging level at which this * handler will be triggered From 9e39adf1510303b6a6e9bd797652651d75f8aafb Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Thu, 13 Jan 2022 23:35:25 +0100 Subject: [PATCH 05/10] Add changelog entry --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a3ee399..5ef4ff422 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## Unreleased - Update the Guzzle tracing middleware to meet the [expected standard](https://develop.sentry.dev/sdk/features/#http-client-integrations) (#1234) -- Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serialization +- Add `toArray` public method in `PayloadSerializer` to be able to re-use Event serializatio +- Add `Sentry\Monolog\BreadcrumbHandler`, a Monolog handler to allow registration of logs as breadcrumbs (#1199) ## 3.3.5 (2021-12-27) From 12e76235ff8ffbe394a9c30f2ee8db589143aa8a Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 30 May 2022 11:01:56 +0200 Subject: [PATCH 06/10] Add Monolog 3 compat --- phpstan-baseline.neon | 5 +++++ src/Monolog/BreadcrumbHandler.php | 20 +++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index f6d81901a..303b84f87 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -95,6 +95,11 @@ parameters: count: 1 path: src/Integration/RequestIntegration.php + - + message: "#^Parameter \\#1 \\$level of method Monolog\\\\Handler\\\\AbstractHandler\\:\\:__construct\\(\\) expects 100\\|200\\|250\\|300\\|400\\|500\\|550\\|600\\|'ALERT'\\|'alert'\\|'CRITICAL'\\|'critical'\\|'DEBUG'\\|'debug'\\|'EMERGENCY'\\|'emergency'\\|'ERROR'\\|'error'\\|'INFO'\\|'info'\\|'NOTICE'\\|'notice'\\|'WARNING'\\|'warning'\\|Monolog\\\\Level, int\\|Monolog\\\\Level\\|string given\\.$#" + count: 1 + path: src/Monolog/BreadcrumbHandler.php + - message: "#^Method Sentry\\\\Options\\:\\:getBeforeBreadcrumbCallback\\(\\) should return callable\\(Sentry\\\\Breadcrumb\\)\\: Sentry\\\\Breadcrumb\\|null but returns mixed\\.$#" count: 1 diff --git a/src/Monolog/BreadcrumbHandler.php b/src/Monolog/BreadcrumbHandler.php index b5cf5bd68..8c5e03b0a 100644 --- a/src/Monolog/BreadcrumbHandler.php +++ b/src/Monolog/BreadcrumbHandler.php @@ -5,7 +5,9 @@ namespace Sentry\Monolog; use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Level; use Monolog\Logger; +use Monolog\LogRecord; use Psr\Log\LogLevel; use Sentry\Breadcrumb; use Sentry\Event; @@ -15,9 +17,6 @@ /** * This Monolog handler logs every message as a {@see Breadcrumb} into the current {@see Scope}, * to enrich any event sent to Sentry. - * - * @phpstan-import-type Level from \Monolog\Logger - * @phpstan-import-type LevelName from \Monolog\Logger */ final class BreadcrumbHandler extends AbstractProcessingHandler { @@ -27,7 +26,7 @@ final class BreadcrumbHandler extends AbstractProcessingHandler private $hub; /** - * @phpstan-param Level|LevelName|LogLevel::* $level + * @phpstan-param int|string|Level|LogLevel::* $level * * @param HubInterface $hub The hub to which errors are reported * @param int|string $level The minimum logging level at which this @@ -45,7 +44,7 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub /** * @psalm-suppress MoreSpecificImplementedParamType * - * @param array{ + * @param LogRecord|array{ * level: int, * channel: string, * datetime: \DateTimeImmutable, @@ -53,7 +52,7 @@ public function __construct(HubInterface $hub, $level = Logger::DEBUG, bool $bub * extra?: array * } $record {@see https://github.com/Seldaek/monolog/blob/main/doc/message-structure.md} */ - protected function write(array $record): void + protected function write($record): void { $breadcrumb = new Breadcrumb( $this->getBreadcrumbLevel($record['level']), @@ -67,8 +66,15 @@ protected function write(array $record): void $this->hub->addBreadcrumb($breadcrumb); } - private function getBreadcrumbLevel(int $level): string + /** + * @param Level|int $level + */ + private function getBreadcrumbLevel($level): string { + if ($level instanceof Level) { + $level = $level->value; + } + switch ($level) { case Logger::DEBUG: return Breadcrumb::LEVEL_DEBUG; From 78a4a9c8bf4de9989f64885e3880f03cc10d2fc2 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 30 May 2022 11:16:27 +0200 Subject: [PATCH 07/10] Add Monolog 3 compat to tests too --- tests/Monolog/BreadcrumbHandlerTest.php | 39 +++++++------------------ 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 54efe5813..0cdde06e9 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -5,6 +5,7 @@ namespace Sentry\Tests\Monolog; use Monolog\Logger; +use Monolog\LogRecord; use PHPUnit\Framework\TestCase; use Sentry\Breadcrumb; use Sentry\Monolog\BreadcrumbHandler; @@ -15,7 +16,7 @@ final class BreadcrumbHandlerTest extends TestCase /** * @dataProvider handleDataProvider */ - public function testHandle(array $record, Breadcrumb $expectedBreadcrumb): void + public function testHandle($record, Breadcrumb $expectedBreadcrumb): void { $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) @@ -36,19 +37,11 @@ public function testHandle(array $record, Breadcrumb $expectedBreadcrumb): void } /** - * @return iterable, Breadcrumb}> + * @return iterable, Breadcrumb}> */ public function handleDataProvider(): iterable { - $defaultData = [ - 'message' => 'foo bar', - 'level' => Logger::DEBUG, - 'level_name' => Logger::getLevelName(Logger::DEBUG), - 'channel' => 'channel.foo', - 'context' => [], - 'extra' => [], - 'datetime' => new \DateTimeImmutable(), - ]; + $defaultData = RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', [], []); $defaultBreadcrumb = new Breadcrumb( Breadcrumb::LEVEL_DEBUG, @@ -68,41 +61,31 @@ public function handleDataProvider(): iterable foreach ($levelsToBeTested as $loggerLevel => $breadcrumbLevel) { yield 'with level ' . Logger::getLevelName($loggerLevel) => [ - ['level' => $loggerLevel] + $defaultData, + RecordFactory::create('foo bar', $loggerLevel, 'channel.foo', [], []), $defaultBreadcrumb->withLevel($breadcrumbLevel), ]; } yield 'with level ERROR' => [ - ['level' => Logger::ERROR] + $defaultData, + RecordFactory::create('foo bar', Logger::ERROR, 'channel.foo', [], []), $defaultBreadcrumb->withLevel(Breadcrumb::LEVEL_ERROR) ->withType(Breadcrumb::TYPE_ERROR), ]; yield 'with level ALERT' => [ - ['level' => Logger::ALERT] + $defaultData, + RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo', [], []), $defaultBreadcrumb->withLevel(Breadcrumb::LEVEL_FATAL) ->withType(Breadcrumb::TYPE_ERROR), ]; yield 'with context' => [ - ['context' => ['foo' => 'bar']] + $defaultData, - $defaultBreadcrumb->withMetadata('foo', 'bar'), + RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', ['context' => ['foo' => 'bar']], []), + $defaultBreadcrumb->withMetadata('context', ['foo' => 'bar']), ]; yield 'with extra' => [ - ['extra' => ['foo' => 'bar']] + $defaultData, - $defaultBreadcrumb->withMetadata('foo', 'bar'), - ]; - - yield 'with context + extra' => [ - [ - 'context' => ['foo' => 'bar', 'context' => 'baz'], - 'extra' => ['foo' => 'baz', 'extra' => 'baz'], - ] + $defaultData, - $defaultBreadcrumb->withMetadata('foo', 'bar') - ->withMetadata('context', 'baz') - ->withMetadata('extra', 'baz'), + RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', [], ['extra' => ['foo' => 'bar']]), + $defaultBreadcrumb->withMetadata('extra', ['foo' => 'bar']), ]; } } From b4e6ed5429181dc7f2c0ae1a6c24344546cf2cc3 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 30 May 2022 11:24:11 +0200 Subject: [PATCH 08/10] Fix timestamp comparation in unit test --- tests/Monolog/BreadcrumbHandlerTest.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/Monolog/BreadcrumbHandlerTest.php b/tests/Monolog/BreadcrumbHandlerTest.php index 0cdde06e9..14802d951 100644 --- a/tests/Monolog/BreadcrumbHandlerTest.php +++ b/tests/Monolog/BreadcrumbHandlerTest.php @@ -21,11 +21,11 @@ public function testHandle($record, Breadcrumb $expectedBreadcrumb): void $hub = $this->createMock(HubInterface::class); $hub->expects($this->once()) ->method('addBreadcrumb') - ->with($this->callback(function (Breadcrumb $breadcrumb) use ($expectedBreadcrumb): bool { + ->with($this->callback(function (Breadcrumb $breadcrumb) use ($expectedBreadcrumb, $record): bool { $this->assertSame($expectedBreadcrumb->getMessage(), $breadcrumb->getMessage()); $this->assertSame($expectedBreadcrumb->getLevel(), $breadcrumb->getLevel()); $this->assertSame($expectedBreadcrumb->getType(), $breadcrumb->getType()); - $this->assertSame($expectedBreadcrumb->getTimestamp(), $breadcrumb->getTimestamp()); + $this->assertEquals($record['datetime']->getTimestamp(), $breadcrumb->getTimestamp()); $this->assertSame($expectedBreadcrumb->getCategory(), $breadcrumb->getCategory()); $this->assertEquals($expectedBreadcrumb->getMetadata(), $breadcrumb->getMetadata()); @@ -41,15 +41,12 @@ public function testHandle($record, Breadcrumb $expectedBreadcrumb): void */ public function handleDataProvider(): iterable { - $defaultData = RecordFactory::create('foo bar', Logger::DEBUG, 'channel.foo', [], []); - $defaultBreadcrumb = new Breadcrumb( Breadcrumb::LEVEL_DEBUG, Breadcrumb::TYPE_DEFAULT, 'channel.foo', 'foo bar', - [], - $defaultData['datetime']->getTimestamp() + [] ); $levelsToBeTested = [ From 4c805f69bf31ddabee430679dd8cc14560e9d937 Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Mon, 30 May 2022 11:32:38 +0200 Subject: [PATCH 09/10] Add datetime field to RecordFactory in tests --- tests/Monolog/RecordFactory.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Monolog/RecordFactory.php b/tests/Monolog/RecordFactory.php index 9f65473db..b302c509b 100644 --- a/tests/Monolog/RecordFactory.php +++ b/tests/Monolog/RecordFactory.php @@ -40,6 +40,7 @@ public static function create(string $message, int $level, string $channel, arra 'level_name' => Logger::getLevelName($level), 'channel' => $channel, 'extra' => $extra, + 'datetime' => new \DateTimeImmutable(), ]; } } From aaa83ed618c3f6d02987e9ffe83686d93c622f0d Mon Sep 17 00:00:00 2001 From: Alessandro Lai Date: Wed, 24 Aug 2022 09:55:04 +0200 Subject: [PATCH 10/10] Update Psalm baseline due to Monolog enums --- psalm-baseline.xml | 45 ++++++++++++++++----------------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 814952ffa..93bf65e2c 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $parsedDsn['host'] @@ -22,6 +22,21 @@ $userIntegrations + + + $record['channel'] + $record['level'] + $record['level'] + $record['message'] + + + getTimestamp + + + Level|int + int|string|Level|LogLevel::* + + CompatibilityProcessingHandlerTrait @@ -80,32 +95,4 @@ startTransaction - - - $name - , - , - , - , - , - , - , - , - , - Level - case - - - - - \DateTimeImmutable - - - - - : - => - } - -