iviphp / debug
Debug collectors, handlers and renderers for the IviPHP ecosystem.
Requires
- php: >=8.2
- iviphp/contracts: ^0.1
- iviphp/http: ^0.1
- iviphp/support: ^0.1
This package is not auto-updated.
Last update: 2026-07-21 22:44:34 UTC
README
Structured debugging, exception collection and development renderers for the IviPHP ecosystem.
iviphp/debug provides a framework-independent debugging foundation for collecting application messages, exceptions and custom diagnostic information.
It can render the collected information as HTML or JSON and register PHP error, exception and shutdown handlers.
Requirements
- PHP 8.2 or later
iviphp/contractsiviphp/httpiviphp/support
Installation
composer require iviphp/debug
Features
- Structured debug messages
- Exception and throwable collection
- Safe stack trace normalization
- Automatic sensitive-data redaction
- Custom debug collectors
- PHP error handler registration
- Uncaught exception handling
- Fatal shutdown error capture
- HTML debug pages
- JSON debug responses
- Configurable collection limits
- Framework-independent architecture
- Safe diagnostic exceptions
Core concepts
Debug manager
DebugManager coordinates:
- collector registration;
- structured messages;
- exception capture;
- collector execution;
- HTML rendering;
- JSON rendering;
- collector reset operations.
Debug API
Debug is the public application-facing wrapper around DebugManager.
Collector
A collector stores or generates one category of diagnostic information.
The package includes:
MessageCollector;ExceptionCollector.
Applications may register their own collectors by implementing DebugCollectorInterface.
Handler
DebugHandler connects the debugging system to PHP's native error, exception and shutdown handling functions.
Renderer
The package includes two renderers:
HtmlDebugRenderer;JsonDebugRenderer.
Creating the debug service
<?php declare(strict_types=1); use Ivi\Debug\Debug; use Ivi\Debug\DebugManager; $manager = new DebugManager(); $debug = new Debug($manager);
The manager automatically registers a message collector and an exception collector.
Adding debug messages
$debug->debug('Loading application configuration.');
Add contextual information:
$debug->info( 'User profile loaded.', [ 'user_id' => 42, 'source' => 'database', ], category: 'users' );
The context must use string keys.
Message levels
The following levels are supported:
$debug->debug('Debug message'); $debug->info('Informational message'); $debug->notice('Notice message'); $debug->warning('Warning message'); $debug->error('Error message'); $debug->critical('Critical message'); $debug->alert('Alert message'); $debug->emergency('Emergency message');
A custom level may be passed through message(), but it must be one of the supported levels.
$debug->message( message: 'Cache connection failed.', level: 'warning', context: [ 'driver' => 'redis', ], category: 'cache' );
Message categories
Categories can group related messages.
$debug->info( 'Request received.', category: 'http' ); $debug->debug( 'Executing database query.', category: 'database' );
Retrieve messages by category:
$messages = $debug ->messages() ->byCategory('database');
Retrieve uncategorized messages:
$messages = $debug ->messages() ->byCategory(null);
Retrieving messages
$collector = $debug->messages(); $messages = $collector->all(); $count = $collector->count();
Retrieve messages by level:
$warnings = $collector->byLevel('warning'); $errors = $collector->byLevel('error');
Determine whether the collector is empty:
if ($collector->isEmpty()) { echo 'No debug messages.'; }
Reset collected messages:
$collector->reset();
Sensitive context values
The message collector automatically redacts common sensitive keys.
Examples include:
authorization
cookie
password
passwd
secret
token
access_token
refresh_token
api_key
private_key
client_secret
session
session_id
Example:
$debug->info( 'Authentication request received.', [ 'email' => 'user@example.com', 'password' => 'secret-value', 'access_token' => 'private-token', ] );
The collected context contains:
[
'email' => 'user@example.com',
'password' => '[REDACTED]',
'access_token' => '[REDACTED]',
]
Applications should still avoid sending secrets to the debugging system.
Configuring the message collector
<?php declare(strict_types=1); use Ivi\Debug\Collectors\MessageCollector; use Ivi\Debug\DebugManager; $messages = new MessageCollector( maximumMessages: 200, sensitiveKeys: [ 'payment_reference', 'internal_key', ] ); $manager = new DebugManager( messageCollector: $messages );
When the configured limit is exceeded, the oldest messages are removed.
Capturing exceptions
try { throw new RuntimeException( 'Unable to complete the operation.' ); } catch (Throwable $exception) { $debug->captureException($exception); }
Add safe contextual information:
$debug->captureException( $exception, [ 'operation' => 'create_project', 'project_id' => 25, ] );
Exception context values must be scalar or null.
Sensitive context keys are automatically redacted.
Exception information
The exception collector records:
- exception class;
- message;
- code;
- file;
- line;
- error severity when applicable;
- normalized stack trace;
- previous exception chain;
- safe context;
- collection timestamp.
Stack trace arguments are intentionally excluded.
Object properties and runtime resources are also excluded.
Retrieving exceptions
$collector = $debug->exceptions(); $exceptions = $collector->all(); $latest = $collector->latest(); $count = $collector->count();
Retrieve exceptions by class:
$runtimeExceptions = $collector->byClass( RuntimeException::class );
Retrieve PHP errors by severity:
$warnings = $collector->bySeverity( E_WARNING );
Reset collected exceptions:
$collector->reset();
Configuring the exception collector
<?php declare(strict_types=1); use Ivi\Debug\Collectors\ExceptionCollector; use Ivi\Debug\DebugManager; $exceptions = new ExceptionCollector( maximumExceptions: 25, maximumTraceFrames: 50, maximumPreviousDepth: 5, includeTrace: true ); $manager = new DebugManager( exceptionCollector: $exceptions );
Set includeTrace to false when stack traces should not be collected.
$exceptions = new ExceptionCollector( includeTrace: false );
Collecting all debug information
$data = $debug->collect();
The result is indexed by collector name.
[
'messages' => [
'messages' => [],
],
'exceptions' => [
'exceptions' => [],
],
]
By default, one failing collector does not stop other collectors.
The failed collector produces a safe error entry.
Enable strict collection when failures must be thrown:
$data = $debug->collect( strict: true );
Rendering HTML
$html = $debug->renderHtml();
Render collected information with an exception:
try { throw new RuntimeException( 'Example failure.' ); } catch (Throwable $exception) { $html = $debug->renderHtml( $exception ); }
The HTML renderer generates a complete standalone development page containing:
- exception details;
- source location;
- stack trace;
- previous exception information;
- collected debug data.
The output escapes HTML values before rendering.
Rendering JSON
$json = $debug->renderJson();
Render an exception as JSON:
$json = $debug->renderJson( $exception );
Example structure:
{
"debug": true,
"exception": null,
"data": {
"messages": {
"messages": []
},
"exceptions": {
"exceptions": []
}
}
}
Selecting a renderer dynamically
$output = $debug->render('html');
$output = $debug->render('json');
Unsupported formats throw DebugException.
Renderer content types
$htmlContentType = $debug->contentType('html');
Returns:
text/html; charset=UTF-8
$jsonContentType = $debug->contentType('json');
Returns:
application/json; charset=UTF-8
Configuring the HTML renderer
<?php declare(strict_types=1); use Ivi\Debug\DebugManager; use Ivi\Debug\Renderers\HtmlDebugRenderer; $renderer = new HtmlDebugRenderer( title: 'Application Debug', includeTrace: true, maximumDepth: 10, maximumStringLength: 10000 ); $manager = new DebugManager( htmlRenderer: $renderer );
Long strings are truncated before rendering.
Deeply nested values are replaced with:
[MAX_DEPTH]
Configuring the JSON renderer
<?php declare(strict_types=1); use Ivi\Debug\DebugManager; use Ivi\Debug\Renderers\JsonDebugRenderer; $renderer = new JsonDebugRenderer( prettyPrint: true, includeTrace: true, maximumDepth: 10, maximumStringLength: 10000 ); $manager = new DebugManager( jsonRenderer: $renderer );
Disable formatted JSON:
$renderer = new JsonDebugRenderer( prettyPrint: false );
Disable exception traces:
$renderer = new JsonDebugRenderer( includeTrace: false );
Replacing renderers
$manager->setHtmlRenderer( new HtmlDebugRenderer( title: 'Custom Debug Page' ) );
$manager->setJsonRenderer( new JsonDebugRenderer( prettyPrint: false ) );
Retrieve the configured renderers:
$htmlRenderer = $manager->htmlRenderer(); $jsonRenderer = $manager->jsonRenderer();
Registering PHP handlers
Create and register the default handler:
$handler = $debug->registerHandler();
This registers:
- an exception handler;
- an error handler;
- a shutdown handler for fatal errors.
Check whether the handler is active:
if ($handler->isRegistered()) { echo 'Debug handler registered.'; }
Restore the previous PHP handlers:
$handler->unregister();
Creating a handler without registering it
$handler = $debug->createHandler(); $handler->register();
This is useful when the application controls exactly when handler registration occurs.
Error conversion
By default, supported PHP runtime errors are converted into ErrorException.
$handler = $debug->registerHandler( convertErrorsToExceptions: true );
Disable conversion and collect errors directly:
$handler = $debug->registerHandler( convertErrorsToExceptions: false );
When conversion is disabled, handled errors are captured by the exception collector.
Error reporting rules
The handler respects the current error_reporting() mask by default.
$handler = $debug->registerHandler( respectErrorReporting: true );
To process errors even when excluded from the current mask:
$handler = $debug->registerHandler( respectErrorReporting: false );
Suppressed errors should generally remain ignored in production applications.
Selecting handled error types
$handler = $debug->registerHandler( errorTypes: E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE );
Use E_ALL to support all PHP error types:
$handler = $debug->registerHandler( errorTypes: E_ALL );
Fatal shutdown errors
Fatal shutdown error capture is enabled by default.
$handler = $debug->registerHandler( captureFatalErrors: true );
Disable it when another system handles shutdown failures:
$handler = $debug->registerHandler( captureFatalErrors: false );
The handler may capture errors such as:
E_ERROR;E_PARSE;E_CORE_ERROR;E_COMPILE_ERROR;E_USER_ERROR;E_RECOVERABLE_ERROR.
Custom exception reporter
A reporter runs after an uncaught exception has been captured.
<?php declare(strict_types=1); use Ivi\Debug\DebugManager; $handler = $debug->registerHandler( reporter: static function ( Throwable $exception, DebugManager $manager ): void { $output = $manager->renderHtml( $exception ); echo $output; } );
A JSON reporter may be used for API applications:
$handler = $debug->registerHandler( reporter: static function ( Throwable $exception, DebugManager $manager ): void { if (!headers_sent()) { http_response_code(500); header( 'Content-Type: ' . $manager->contentType('json') ); } echo $manager->renderJson( $exception ); } );
Previous exception handlers
The previous exception handler is not called by default.
Enable propagation:
$handler = $debug->registerHandler( propagateExceptions: true );
The exception is collected before the previous handler is called.
Custom collectors
Create a collector by implementing DebugCollectorInterface.
<?php declare(strict_types=1); use Ivi\Debug\Contracts\DebugCollectorInterface; final class RuntimeCollector implements DebugCollectorInterface { private float $startedAt; public function __construct() { $this->startedAt = microtime(true); } public function name(): string { return 'runtime'; } public function collect(): array { return [ 'duration_ms' => ( microtime(true) - $this->startedAt ) * 1000, 'memory_bytes' => memory_get_usage(true), 'peak_memory_bytes' => memory_get_peak_usage(true), ]; } public function count(): int { return 1; } public function isEmpty(): bool { return false; } public function reset(): void { $this->startedAt = microtime(true); } }
Register the collector:
$debug->registerCollector( new RuntimeCollector() );
Its output becomes available through:
$data = $debug->collect(); $runtime = $data['runtime'];
Collector management
Determine whether a collector exists:
if ($debug->hasCollector('runtime')) { $collector = $debug->collector('runtime'); }
Return all collectors:
$collectors = $debug->collectors();
Return collector names:
$names = $debug->collectorNames();
Replace a collector:
$debug->replaceCollector( new RuntimeCollector() );
Remove a custom collector:
$debug->forgetCollector('runtime');
The primary message and exception collectors cannot be removed.
Collection statistics
Return the total number of entries stored by all collectors:
$count = $debug->count();
Determine whether all collectors are empty:
if ($debug->isEmpty()) { echo 'No debug information.'; }
Resetting collectors
Reset every registered collector:
$debug->reset();
By default, reset failures from individual collectors are ignored.
Use strict mode to throw failures:
$debug->reset( strict: true );
Exceptions
Debugging-system failures are represented by:
Ivi\Debug\Exceptions\DebugException
Examples include:
- duplicate collector registration;
- missing collectors;
- invalid collector names;
- invalid collector output;
- handler registration failures;
- handler unregistration failures;
- unsupported renderers;
- JSON encoding failures;
- rendering failures;
- invalid configuration.
<?php declare(strict_types=1); use Ivi\Debug\Exceptions\DebugException; try { $output = $debug->render( 'unsupported-format' ); } catch (DebugException $exception) { echo $exception->getMessage(); $context = $exception->context(); }
Diagnostic context intentionally excludes collected payload contents and sensitive application values.
Development environment example
<?php declare(strict_types=1); use Ivi\Debug\Debug; use Ivi\Debug\DebugManager; $debug = new Debug( new DebugManager() ); $debug->registerHandler( reporter: static function ( Throwable $exception, DebugManager $manager ): void { if (!headers_sent()) { http_response_code(500); header( 'Content-Type: ' . $manager->contentType('html') ); } echo $manager->renderHtml( $exception ); } ); $debug->info( 'Application started.', [ 'environment' => 'development', ], category: 'application' );
API environment example
<?php declare(strict_types=1); use Ivi\Debug\Debug; use Ivi\Debug\DebugManager; $debug = new Debug( new DebugManager() ); $debug->registerHandler( reporter: static function ( Throwable $exception, DebugManager $manager ): void { if (!headers_sent()) { http_response_code(500); header( 'Content-Type: ' . $manager->contentType('json') ); } echo $manager->renderJson( $exception ); }, convertErrorsToExceptions: true, captureFatalErrors: true );
Production usage
Detailed debug output should not be exposed publicly in production.
Production applications should:
- disable browser-facing stack traces;
- avoid returning filesystem paths;
- log exceptions through a protected logging service;
- send generic error responses to clients;
- protect debug routes with authorization;
- redact application-specific secrets;
- restrict access to collected diagnostic data.
The package limits and normalizes diagnostic values, but application-specific confidential data must still be handled carefully.
Design principles
iviphp/debug follows these principles:
- framework-independent collection;
- explicit collector contracts;
- structured debug information;
- safe normalization;
- sensitive-key redaction;
- no stack trace arguments;
- no object-property inspection;
- configurable collection limits;
- separate HTML and JSON renderers;
- compatibility with web, API and CLI applications.
License
Ivi Debug is open-source software released under the MIT License.
Maintainer
Maintained by Gaspard Kirira and Softadastra.