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 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();