From fa3911987571639aa916c828ef57f45a09718b35 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 7 Nov 2026 21:28:14 -0600 Subject: [PATCH 1/2] [#107] - setting the last exception only for log items at error level or higher Assisted-by: Claude Code --- src/DebugBar/Logger/ExceptionAdapter.php | 25 +++++- .../DebugBar/Logger/ExceptionAdapterTest.php | 78 +++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/src/DebugBar/Logger/ExceptionAdapter.php b/src/DebugBar/Logger/ExceptionAdapter.php index a85fc72..8032227 100644 --- a/src/DebugBar/Logger/ExceptionAdapter.php +++ b/src/DebugBar/Logger/ExceptionAdapter.php @@ -13,8 +13,11 @@ namespace Phalcon\DebugBar\Logger; +use Phalcon\DebugBar\Collector\ExceptionsCollector; +use Phalcon\DebugBar\Contracts\ExceptionAware; use Phalcon\DebugBar\Debug; use Phalcon\Logger\Adapter\AbstractAdapter; +use Phalcon\Logger\Enum; use Phalcon\Logger\Item; use Throwable; @@ -25,6 +28,10 @@ * `Adapter`: an MVC application that logs an exception would record it two times. * The adapter reads the active bar each time the logger writes, so you can create * it before the bar boots. + * + * Only an item at error level or higher sets the last exception, which the ADR + * debug page shows. An item at a lower level is an exception that the + * application handled: it shows only in the Exceptions panel. */ final class ExceptionAdapter extends AbstractAdapter { @@ -36,8 +43,22 @@ public function close(): bool public function process(Item $item): void { $exception = $item->getContext()['exception'] ?? null; - if ($exception instanceof Throwable) { - Debug::getBar()?->addException($exception); + $bar = Debug::getBar(); + if (!$exception instanceof Throwable || null === $bar) { + return; + } + + if ($item->getLevel() <= Enum::ERROR) { + $bar->addException($exception); + + return; + } + + $collector = $bar->hasCollector(ExceptionsCollector::NAME) + ? $bar->getCollector(ExceptionsCollector::NAME) + : null; + if ($collector instanceof ExceptionAware) { + $collector->addThrowable($exception); } } } diff --git a/tests/Unit/DebugBar/Logger/ExceptionAdapterTest.php b/tests/Unit/DebugBar/Logger/ExceptionAdapterTest.php index 3f3da34..d7f0052 100644 --- a/tests/Unit/DebugBar/Logger/ExceptionAdapterTest.php +++ b/tests/Unit/DebugBar/Logger/ExceptionAdapterTest.php @@ -20,9 +20,11 @@ use Phalcon\DebugBar\DebugBar; use Phalcon\DebugBar\Logger\ExceptionAdapter; use Phalcon\DebugBar\Security\Redactor; +use Phalcon\Logger\Enum; use Phalcon\Logger\Item; use Phalcon\Logger\Logger; use Phalcon\Talon\PHPUnit\AbstractUnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; use RuntimeException; final class ExceptionAdapterTest extends AbstractUnitTestCase @@ -41,6 +43,33 @@ protected function tearDown(): void parent::tearDown(); } + /** + * @return array + */ + public static function errorLevelProvider(): array + { + return [ + 'emergency' => [Enum::EMERGENCY], + 'alert' => [Enum::ALERT], + 'critical' => [Enum::CRITICAL], + 'error' => [Enum::ERROR], + ]; + } + + /** + * @return array + */ + public static function lowerLevelProvider(): array + { + return [ + 'warning' => [Enum::WARNING], + 'notice' => [Enum::NOTICE], + 'info' => [Enum::INFO], + 'debug' => [Enum::DEBUG], + 'custom' => [Enum::CUSTOM], + ]; + } + public function testAdapterCreatedBeforeTheBarBootsForwardsToTheBar(): void { $logger = new Logger('debug', ['debugbar' => new ExceptionAdapter()]); @@ -55,6 +84,55 @@ public function testAdapterCreatedBeforeTheBarBootsForwardsToTheBar(): void $this->assertSame($exception, $bar->getLastException()); } + #[DataProvider('errorLevelProvider')] + public function testAnExceptionAtErrorLevelOrHigherIsTheLastException(int $level): void + { + $collector = new ExceptionsCollector(); + $bar = (new DebugBar())->addCollector($collector); + Debug::setBar($bar); + $exception = new RuntimeException('boom'); + + (new ExceptionAdapter())->process( + new Item('failed', 'level', $level, new DateTimeImmutable(), ['exception' => $exception]) + ); + + $this->assertSame(1, $collector->collect()['badge']); + $this->assertSame($exception, $bar->getLastException()); + } + + /** + * A handled exception that is logged below error level must not open the + * debug page of an ADR application. + */ + #[DataProvider('lowerLevelProvider')] + public function testAnExceptionBelowErrorLevelShowsInThePanelButIsNotTheLastException(int $level): void + { + $collector = new ExceptionsCollector(); + $bar = (new DebugBar())->addCollector($collector); + Debug::setBar($bar); + + (new ExceptionAdapter())->process( + new Item('handled', 'level', $level, new DateTimeImmutable(), ['exception' => new RuntimeException('boom')]) + ); + + $this->assertSame(1, $collector->collect()['badge']); + $this->assertNull($bar->getLastException()); + } + + public function testAnExceptionBelowErrorLevelWithNoExceptionsCollectorIsIgnored(): void + { + $bar = new DebugBar(); + Debug::setBar($bar); + + $exception = new RuntimeException('boom'); + + (new ExceptionAdapter())->process( + new Item('handled', 'warning', Enum::WARNING, new DateTimeImmutable(), ['exception' => $exception]) + ); + + $this->assertNull($bar->getLastException()); + } + public function testCloseReturnsTrue(): void { $adapter = new ExceptionAdapter(); From 06556033628f05857fd087e8133716856023d975 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 7 Nov 2026 21:28:14 -0600 Subject: [PATCH 2/2] [#107] - updating changelog/docs Assisted-by: Claude Code --- CHANGELOG.md | 1 + docs/index.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebb1586..e52110b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas - Keep host CSS out of the bar tables, code blocks and buttons, stop history requests after 10 seconds, and show "Unable to load" in a history row that fails. [#102](https://github.com/phalcon/debugbar/issues/102) - Pin `pie.phar` to a release with a sha256 check, build the PHP 8.1 Docker image, declare `ext-mbstring`, and list the Phalcon version that each feature needs. [#104](https://github.com/phalcon/debugbar/issues/104) - Name the collapse toggle and the tabs for screen readers, close the open panel or the history with Escape, and fix two `Phalcon\Debug` docblocks. [#106](https://github.com/phalcon/debugbar/issues/106) +- Open the ADR debug page only for an exception logged at `error` level or higher, and show a lower-level exception only in the `exceptions` panel. [#107](https://github.com/phalcon/debugbar/issues/107) - Test HTML writes, the collapse state and `localStorage` errors in the JS tests, and keep every listener in the fake DOM. [#103](https://github.com/phalcon/debugbar/issues/103) ## [0.4.0](https://github.com/phalcon/debugbar/releases/tag/v0.4.0) (2026-07-14) diff --git a/docs/index.md b/docs/index.md index 1e1cf55..f98dffb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -227,7 +227,7 @@ The provider decorates the router (`Phalcon\Contracts\ADR\Router\Router`) with ` Do not resolve the router during registration. `extend()` on a resolved service throws `CannotExtendResolved`. In a blocked environment, the provider does not change the router. -The `debug_page` key shows the debug page (`Phalcon\Debug`) for a server error. It is off by default. The provider reads the exception from the bar, so add `ExceptionAdapter` to your logger. +The `debug_page` key shows the debug page (`Phalcon\Debug`) for a server error. It is off by default. The provider reads the exception from the bar, so add `ExceptionAdapter` to your logger. Only an exception logged at `error` level or higher opens the debug page. An exception logged at a lower level shows only in the `exceptions` panel, so log an exception that your application handles at `warning` or lower. ```php