From 569a25814651e8b40f7158be58209f8c82e4249c Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 8 Jan 2026 13:01:32 +0100 Subject: [PATCH 1/6] ref(logs): allow different log level types --- src/Monolog/LogsHandler.php | 16 ++-- tests/Monolog/LogsHandlerTest.php | 143 ++++++++++++++++++++++++------ tests/Monolog/RecordFactory.php | 2 +- 3 files changed, 126 insertions(+), 35 deletions(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 9ee342a4fc..74f127485d 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -18,7 +18,7 @@ class LogsHandler implements HandlerInterface /** * The minimum logging level at which this handler will be triggered. * - * @var LogLevel + * @var LogLevel|\Monolog\Level|int */ private $logLevel; @@ -32,10 +32,10 @@ class LogsHandler implements HandlerInterface /** * Creates a new Monolog handler that converts Monolog logs to Sentry logs. * - * @param LogLevel|null $logLevel the minimum logging level at which this handler will be triggered and collects the logs - * @param bool $bubble whether the messages that are handled can bubble up the stack or not + * @param LogLevel|\Monolog\Level|int|null $logLevel the minimum logging level at which this handler will be triggered and collects the logs + * @param bool $bubble whether the messages that are handled can bubble up the stack or not */ - public function __construct(?LogLevel $logLevel = null, bool $bubble = true) + public function __construct($logLevel = null, bool $bubble = true) { $this->logLevel = $logLevel ?? LogLevel::debug(); $this->bubble = $bubble; @@ -46,7 +46,13 @@ public function __construct(?LogLevel $logLevel = null, bool $bubble = true) */ public function isHandling($record): bool { - return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority(); + if ($this->logLevel instanceof LogLevel) { + return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority(); + } elseif ($this->logLevel instanceof \Monolog\Level) { + return $record['level'] >= $this->logLevel->value; + } else { + return $record['level'] >= $this->logLevel; + } } /** diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 2a6af32982..603c9cd17e 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -4,19 +4,17 @@ namespace Sentry\Tests\Monolog; +use Monolog\Level; use Monolog\Logger; use PHPUnit\Framework\TestCase; use Sentry\ClientBuilder; -use Sentry\Event; use Sentry\Logs\Log; use Sentry\Logs\LogLevel; use Sentry\Logs\Logs; use Sentry\Monolog\LogsHandler; use Sentry\SentrySdk; use Sentry\State\Hub; -use Sentry\Transport\Result; -use Sentry\Transport\ResultStatus; -use Sentry\Transport\TransportInterface; +use Sentry\Tests\StubTransport; final class LogsHandlerTest extends TestCase { @@ -75,31 +73,53 @@ public function testLogLevels($record, int $countLogs): void $this->assertCount($countLogs, $logs); } + /** + * @dataProvider logLevelDataProvider + */ + public function testLogLevelsMonologEnum($record, int $countLogs): void + { + if (Logger::API < 3) { + $this->markTestSkipped('Test only works for Monolog >= 3'); + } + $handler = new LogsHandler(Level::Warning); + $handler->handle($record); + + $logs = Logs::getInstance()->aggregator()->all(); + $this->assertCount($countLogs, $logs); + } + + /** + * @dataProvider logLevelDataProvider + */ + public function testLogLevelsLegacyMonolog($record, int $countLogs): void + { + $handler = new LogsHandler(Logger::WARNING); + $handler->handle($record); + + $logs = Logs::getInstance()->aggregator()->all(); + $this->assertCount($countLogs, $logs); + } + + /** + * @dataProvider monologLevelDataProvider + */ + public function testFilterOnMonologLevels(int $level, $record, ?Log $log = null): void + { + $handler = new LogsHandler($level); + $handler->handle($record); + + $logs = Logs::getInstance()->aggregator()->all(); + if ($log === null) { + $this->assertEmpty($logs); + } else { + $this->assertNotEmpty($logs); + $this->assertEquals($log->getLevel(), $logs[0]->getLevel()); + } + } + public function testLogsHandlerDestructor() { - $transport = new class implements TransportInterface { - private $events = []; - - public function send(Event $event): Result - { - $this->events[] = $event; - - return new Result(ResultStatus::success()); - } - - public function close(?int $timeout = null): Result - { - return new Result(ResultStatus::success()); - } - - /** - * @return Event[] - */ - public function getEvents(): array - { - return $this->events; - } - }; + $transport = new StubTransport(); $client = ClientBuilder::create([ 'enable_logs' => true, ])->setTransport($transport) @@ -110,8 +130,8 @@ public function getEvents(): array $this->handleLogAndDrop(); - $this->assertCount(1, $transport->getEvents()); - $this->assertSame('I was dropped :(', $transport->getEvents()[0]->getLogs()[0]->getBody()); + $this->assertCount(1, StubTransport::$events); + $this->assertSame('I was dropped :(', StubTransport::$events[0]->getLogs()[0]->getBody()); } private function handleLogAndDrop(): void @@ -362,4 +382,69 @@ public static function logLevelDataProvider(): iterable 1, ]; } + + public static function monologLevelDataProvider(): iterable + { + yield [ + Logger::NOTICE, + RecordFactory::create( + 'foo bar', + Logger::NOTICE, + 'channel.foo' + ), + new Log(123, 'abc', LogLevel::info(), 'foo bar'), + ]; + + yield [ + Logger::NOTICE, + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo' + ), + null, + ]; + + yield 'Warnings are passed through if Notice is configured' => [ + Logger::NOTICE, + RecordFactory::create('foo bar', Logger::WARNING, 'channel.foo'), + new Log(123, 'abc', LogLevel::warn(), 'foo bar'), + ]; + + yield 'Filter out critical even though both convert to Sentry Fatal' => [ + Logger::ALERT, + RecordFactory::create('foo bar', Logger::CRITICAL, 'channel.foo'), + null, + ]; + + yield [ + Logger::ALERT, + RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield [ + Logger::ALERT, + RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield 'Emergency is passed through if Alert is configured (both are sentry fatal)' => [ + Logger::ALERT, + RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield 'Alert is filtered when emergency is configured (both are sentry fatal)' => [ + Logger::EMERGENCY, + RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), + null, + ]; + + yield [ + Logger::EMERGENCY, + RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + } } diff --git a/tests/Monolog/RecordFactory.php b/tests/Monolog/RecordFactory.php index 39e2b67d62..be22130590 100644 --- a/tests/Monolog/RecordFactory.php +++ b/tests/Monolog/RecordFactory.php @@ -19,7 +19,7 @@ final class RecordFactory * * @return array|LogRecord */ - public static function create(string $message, int $level, string $channel, array $context, array $extra) + public static function create(string $message, int $level, string $channel, array $context = [], array $extra = []) { if (Logger::API >= 3) { return new LogRecord( From 2396a334d2ea103e6da4d78a8c76fb126b6e9195 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 8 Jan 2026 14:15:22 +0100 Subject: [PATCH 2/6] tests --- src/Monolog/LogsHandler.php | 2 +- tests/Monolog/LogsHandlerTest.php | 89 ++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 74f127485d..194553fb82 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -48,7 +48,7 @@ public function isHandling($record): bool { if ($this->logLevel instanceof LogLevel) { return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority(); - } elseif ($this->logLevel instanceof \Monolog\Level) { + } elseif (class_exists(\Monolog\Level::class) && $this->logLevel instanceof \Monolog\Level) { return $record['level'] >= $this->logLevel->value; } else { return $record['level'] >= $this->logLevel; diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 603c9cd17e..1e72b8cbfb 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -101,7 +101,7 @@ public function testLogLevelsLegacyMonolog($record, int $countLogs): void } /** - * @dataProvider monologLevelDataProvider + * @dataProvider monologLegacyLevelDataProvider */ public function testFilterOnMonologLevels(int $level, $record, ?Log $log = null): void { @@ -117,6 +117,26 @@ public function testFilterOnMonologLevels(int $level, $record, ?Log $log = null) } } + /** + * @dataProvider monologLevelDataProvider + */ + public function testFilterOnNewMonologLevels(Level $level, $record, ?Log $log = null): void + { + if (!class_exists(Level::class)) { + $this->markTestSkipped('Test only works for Monolog >= 3'); + } + $handler = new LogsHandler($level); + $handler->handle($record); + + $logs = Logs::getInstance()->aggregator()->all(); + if ($log === null) { + $this->assertEmpty($logs); + } else { + $this->assertNotEmpty($logs); + $this->assertEquals($log->getLevel(), $logs[0]->getLevel()); + } + } + public function testLogsHandlerDestructor() { $transport = new StubTransport(); @@ -383,7 +403,7 @@ public static function logLevelDataProvider(): iterable ]; } - public static function monologLevelDataProvider(): iterable + public static function monologLegacyLevelDataProvider(): iterable { yield [ Logger::NOTICE, @@ -447,4 +467,69 @@ public static function monologLevelDataProvider(): iterable new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), ]; } + + public static function monologLevelDataProvider(): iterable + { + yield [ + Level::Notice, + RecordFactory::create( + 'foo bar', + Logger::NOTICE, + 'channel.foo' + ), + new Log(123, 'abc', LogLevel::info(), 'foo bar'), + ]; + + yield [ + Level::Notice, + RecordFactory::create( + 'foo bar', + Logger::INFO, + 'channel.foo' + ), + null, + ]; + + yield 'Warnings are passed through if Notice is configured' => [ + Level::Notice, + RecordFactory::create('foo bar', Logger::WARNING, 'channel.foo'), + new Log(123, 'abc', LogLevel::warn(), 'foo bar'), + ]; + + yield 'Filter out critical even though both convert to Sentry Fatal' => [ + Level::Alert, + RecordFactory::create('foo bar', Logger::CRITICAL, 'channel.foo'), + null, + ]; + + yield [ + Level::Alert, + RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield [ + Level::Alert, + RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield 'Emergency is passed through if Alert is configured (both are sentry fatal)' => [ + Level::Alert, + RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + + yield 'Alert is filtered when emergency is configured (both are sentry fatal)' => [ + Level::Emergency, + RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), + null, + ]; + + yield [ + Level::Emergency, + RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), + new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + ]; + } } From 0bc523de0f37d27b57679bde6e141ede2d55b754 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 8 Jan 2026 14:17:31 +0100 Subject: [PATCH 3/6] tests --- tests/Monolog/LogsHandlerTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 1e72b8cbfb..41c07afbf9 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -118,9 +118,10 @@ public function testFilterOnMonologLevels(int $level, $record, ?Log $log = null) } /** + * @param Level $level * @dataProvider monologLevelDataProvider */ - public function testFilterOnNewMonologLevels(Level $level, $record, ?Log $log = null): void + public function testFilterOnNewMonologLevels($level, $record, ?Log $log = null): void { if (!class_exists(Level::class)) { $this->markTestSkipped('Test only works for Monolog >= 3'); From 907846d93fe11163958455cbe95612b7c80e9470 Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 8 Jan 2026 14:23:23 +0100 Subject: [PATCH 4/6] tests --- src/Monolog/LogsHandler.php | 6 +++++- tests/Monolog/LogsHandlerTest.php | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 194553fb82..29f49b7da9 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -18,6 +18,8 @@ class LogsHandler implements HandlerInterface /** * The minimum logging level at which this handler will be triggered. * + * @psalm-suppress UndefinedDocblockClass + * * @var LogLevel|\Monolog\Level|int */ private $logLevel; @@ -48,7 +50,9 @@ public function isHandling($record): bool { if ($this->logLevel instanceof LogLevel) { return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority(); - } elseif (class_exists(\Monolog\Level::class) && $this->logLevel instanceof \Monolog\Level) { + + /** @psalm-suppress UndefinedClass */ + } elseif ($this->logLevel instanceof \Monolog\Level) { return $record['level'] >= $this->logLevel->value; } else { return $record['level'] >= $this->logLevel; diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 41c07afbf9..72fb4a818b 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -119,6 +119,7 @@ public function testFilterOnMonologLevels(int $level, $record, ?Log $log = null) /** * @param Level $level + * * @dataProvider monologLevelDataProvider */ public function testFilterOnNewMonologLevels($level, $record, ?Log $log = null): void From acd94123f27043a75f5d4e057fc7f5a1843b8fdf Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 8 Jan 2026 14:48:14 +0100 Subject: [PATCH 5/6] improved test --- src/Monolog/LogsHandler.php | 5 +- tests/Monolog/LogsHandlerTest.php | 289 +++++++++--------------------- 2 files changed, 85 insertions(+), 209 deletions(-) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 29f49b7da9..390b7df665 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -44,14 +44,15 @@ public function __construct($logLevel = null, bool $bubble = true) } /** + * @psalm-suppress UndefinedDocblockClass + * @psalm-suppress UndefinedClass + * * @param array|LogRecord $record */ public function isHandling($record): bool { if ($this->logLevel instanceof LogLevel) { return self::getSentryLogLevelFromMonologLevel($record['level'])->getPriority() >= $this->logLevel->getPriority(); - - /** @psalm-suppress UndefinedClass */ } elseif ($this->logLevel instanceof \Monolog\Level) { return $record['level'] >= $this->logLevel->value; } else { diff --git a/tests/Monolog/LogsHandlerTest.php b/tests/Monolog/LogsHandlerTest.php index 72fb4a818b..004a80c38e 100644 --- a/tests/Monolog/LogsHandlerTest.php +++ b/tests/Monolog/LogsHandlerTest.php @@ -61,81 +61,42 @@ static function (string $key) { ); } - /** - * @dataProvider logLevelDataProvider - */ - public function testLogLevels($record, int $countLogs): void - { - $handler = new LogsHandler(LogLevel::warn()); - $handler->handle($record); - - $logs = Logs::getInstance()->aggregator()->all(); - $this->assertCount($countLogs, $logs); - } - - /** - * @dataProvider logLevelDataProvider - */ - public function testLogLevelsMonologEnum($record, int $countLogs): void - { - if (Logger::API < 3) { - $this->markTestSkipped('Test only works for Monolog >= 3'); - } - $handler = new LogsHandler(Level::Warning); - $handler->handle($record); - - $logs = Logs::getInstance()->aggregator()->all(); - $this->assertCount($countLogs, $logs); - } - - /** - * @dataProvider logLevelDataProvider - */ - public function testLogLevelsLegacyMonolog($record, int $countLogs): void - { - $handler = new LogsHandler(Logger::WARNING); - $handler->handle($record); - - $logs = Logs::getInstance()->aggregator()->all(); - $this->assertCount($countLogs, $logs); - } - /** * @dataProvider monologLegacyLevelDataProvider */ - public function testFilterOnMonologLevels(int $level, $record, ?Log $log = null): void + public function testFiltersAndMapsUsingLegacyMonologThreshold(int $threshold, int $recordLevel, int $expectedCount, ?LogLevel $expectedMappedLevel): void { - $handler = new LogsHandler($level); - $handler->handle($record); + $handler = new LogsHandler($threshold); + $handler->handle(RecordFactory::create('foo bar', $recordLevel, 'channel.foo', [], [])); $logs = Logs::getInstance()->aggregator()->all(); - if ($log === null) { - $this->assertEmpty($logs); - } else { - $this->assertNotEmpty($logs); - $this->assertEquals($log->getLevel(), $logs[0]->getLevel()); + $this->assertCount($expectedCount, $logs); + + if ($expectedMappedLevel !== null) { + $this->assertEquals($expectedMappedLevel, $logs[0]->getLevel()); } } /** - * @param Level $level - * * @dataProvider monologLevelDataProvider */ - public function testFilterOnNewMonologLevels($level, $record, ?Log $log = null): void + public function testFiltersAndMapsUsingMonologEnumThreshold($threshold, $recordLevel, int $expectedCount, ?LogLevel $expectedMappedLevel): void { if (!class_exists(Level::class)) { $this->markTestSkipped('Test only works for Monolog >= 3'); } - $handler = new LogsHandler($level); - $handler->handle($record); + + $this->assertInstanceOf(Level::class, $threshold); + $this->assertInstanceOf(Level::class, $recordLevel); + + $handler = new LogsHandler($threshold); + $handler->handle(RecordFactory::create('foo bar', $recordLevel->value, 'channel.foo', [], [])); $logs = Logs::getInstance()->aggregator()->all(); - if ($log === null) { - $this->assertEmpty($logs); - } else { - $this->assertNotEmpty($logs); - $this->assertEquals($log->getLevel(), $logs[0]->getLevel()); + $this->assertCount($expectedCount, $logs); + + if ($expectedMappedLevel !== null) { + $this->assertEquals($expectedMappedLevel, $logs[0]->getLevel()); } } @@ -325,213 +286,127 @@ public static function handleDataProvider(): iterable ]; } - public static function logLevelDataProvider(): iterable - { - yield [ - RecordFactory::create( - 'foo bar', - Logger::DEBUG, - 'channel.foo', - [], - [] - ), - 0, - ]; - - yield [ - RecordFactory::create( - 'foo bar', - Logger::NOTICE, - 'channel.foo', - [], - [] - ), - 0, - ]; - - yield [ - RecordFactory::create( - 'foo bar', - Logger::INFO, - 'channel.foo', - [], - [] - ), - 0, - ]; - - yield [ - RecordFactory::create( - 'foo bar', - Logger::WARNING, - 'channel.foo', - [], - [] - ), - 1, - ]; - - yield [ - RecordFactory::create( - 'foo bar', - Logger::CRITICAL, - 'channel.foo', - [], - [] - ), - 1, - ]; - - yield [ - RecordFactory::create( - 'foo bar', - Logger::ALERT, - 'channel.foo', - [], - [] - ), - 1, - ]; - - yield [ - RecordFactory::create( - 'foo bar', - Logger::EMERGENCY, - 'channel.foo', - [], - [] - ), - 1, - ]; - } - public static function monologLegacyLevelDataProvider(): iterable { - yield [ + yield 'NOTICE threshold drops INFO (both map to sentry info)' => [ Logger::NOTICE, - RecordFactory::create( - 'foo bar', - Logger::NOTICE, - 'channel.foo' - ), - new Log(123, 'abc', LogLevel::info(), 'foo bar'), + Logger::INFO, + 0, + null, ]; - yield [ + yield 'NOTICE threshold keeps NOTICE (mapped to sentry info)' => [ Logger::NOTICE, - RecordFactory::create( - 'foo bar', - Logger::INFO, - 'channel.foo' - ), - null, + Logger::NOTICE, + 1, + LogLevel::info(), ]; - yield 'Warnings are passed through if Notice is configured' => [ + yield 'NOTICE threshold keeps WARNING (mapped to sentry warn)' => [ Logger::NOTICE, - RecordFactory::create('foo bar', Logger::WARNING, 'channel.foo'), - new Log(123, 'abc', LogLevel::warn(), 'foo bar'), + Logger::WARNING, + 1, + LogLevel::warn(), ]; - yield 'Filter out critical even though both convert to Sentry Fatal' => [ + yield 'ALERT threshold drops CRITICAL (both map to sentry fatal)' => [ Logger::ALERT, - RecordFactory::create('foo bar', Logger::CRITICAL, 'channel.foo'), + Logger::CRITICAL, + 0, null, ]; - yield [ + yield 'ALERT threshold keeps ALERT (mapped to sentry fatal)' => [ Logger::ALERT, - RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), - ]; - - yield [ Logger::ALERT, - RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + 1, + LogLevel::fatal(), ]; - yield 'Emergency is passed through if Alert is configured (both are sentry fatal)' => [ + yield 'ALERT threshold keeps EMERGENCY (mapped to sentry fatal)' => [ Logger::ALERT, - RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + Logger::EMERGENCY, + 1, + LogLevel::fatal(), ]; - yield 'Alert is filtered when emergency is configured (both are sentry fatal)' => [ + yield 'EMERGENCY threshold drops ALERT (both map to sentry fatal)' => [ Logger::EMERGENCY, - RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), + Logger::ALERT, + 0, null, ]; - yield [ + yield 'EMERGENCY threshold keeps EMERGENCY (mapped to sentry fatal)' => [ Logger::EMERGENCY, - RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + Logger::EMERGENCY, + 1, + LogLevel::fatal(), ]; } public static function monologLevelDataProvider(): iterable { - yield [ + if (!class_exists(Level::class)) { + yield 'Monolog < 3 (skipped)' => [null, null, 0, null]; + + return; + } + + yield 'NOTICE threshold drops INFO (both map to sentry info)' => [ Level::Notice, - RecordFactory::create( - 'foo bar', - Logger::NOTICE, - 'channel.foo' - ), - new Log(123, 'abc', LogLevel::info(), 'foo bar'), + Level::Info, + 0, + null, ]; - yield [ + yield 'NOTICE threshold keeps NOTICE (mapped to sentry info)' => [ Level::Notice, - RecordFactory::create( - 'foo bar', - Logger::INFO, - 'channel.foo' - ), - null, + Level::Notice, + 1, + LogLevel::info(), ]; - yield 'Warnings are passed through if Notice is configured' => [ + yield 'NOTICE threshold keeps WARNING (mapped to sentry warn)' => [ Level::Notice, - RecordFactory::create('foo bar', Logger::WARNING, 'channel.foo'), - new Log(123, 'abc', LogLevel::warn(), 'foo bar'), + Level::Warning, + 1, + LogLevel::warn(), ]; - yield 'Filter out critical even though both convert to Sentry Fatal' => [ + yield 'ALERT threshold drops CRITICAL (both map to sentry fatal)' => [ Level::Alert, - RecordFactory::create('foo bar', Logger::CRITICAL, 'channel.foo'), + Level::Critical, + 0, null, ]; - yield [ + yield 'ALERT threshold keeps ALERT (mapped to sentry fatal)' => [ Level::Alert, - RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), - ]; - - yield [ Level::Alert, - RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + 1, + LogLevel::fatal(), ]; - yield 'Emergency is passed through if Alert is configured (both are sentry fatal)' => [ + yield 'ALERT threshold keeps EMERGENCY (mapped to sentry fatal)' => [ Level::Alert, - RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + Level::Emergency, + 1, + LogLevel::fatal(), ]; - yield 'Alert is filtered when emergency is configured (both are sentry fatal)' => [ + yield 'EMERGENCY threshold drops ALERT (both map to sentry fatal)' => [ Level::Emergency, - RecordFactory::create('foo bar', Logger::ALERT, 'channel.foo'), + Level::Alert, + 0, null, ]; - yield [ + yield 'EMERGENCY threshold keeps EMERGENCY (mapped to sentry fatal)' => [ Level::Emergency, - RecordFactory::create('foo bar', Logger::EMERGENCY, 'channel.foo'), - new Log(123, 'abc', LogLevel::fatal(), 'foo bar'), + Level::Emergency, + 1, + LogLevel::fatal(), ]; } } From f01a0dd1314e0060969cf692f87e282f5f34844f Mon Sep 17 00:00:00 2001 From: Martin Linzmayer Date: Thu, 8 Jan 2026 14:58:14 +0100 Subject: [PATCH 6/6] psalm --- src/Monolog/LogsHandler.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Monolog/LogsHandler.php b/src/Monolog/LogsHandler.php index 390b7df665..b92640b148 100644 --- a/src/Monolog/LogsHandler.php +++ b/src/Monolog/LogsHandler.php @@ -34,6 +34,8 @@ class LogsHandler implements HandlerInterface /** * Creates a new Monolog handler that converts Monolog logs to Sentry logs. * + * @psalm-suppress UndefinedDocblockClass + * * @param LogLevel|\Monolog\Level|int|null $logLevel the minimum logging level at which this handler will be triggered and collects the logs * @param bool $bubble whether the messages that are handled can bubble up the stack or not */