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.
- PHP 8.2 or later
iviphp/contractsiviphp/httpiviphp/support
composer require iviphp/debug- 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
DebugManager coordinates:
- collector registration;
- structured messages;
- exception capture;
- collector execution;
- HTML rendering;
- JSON rendering;
- collector reset operations.
Debug is the public application-facing wrapper around DebugManager.
A collector stores or generates one category of diagnostic information.
The package includes:
MessageCollector;ExceptionCollector.
Applications may register their own collectors by implementing DebugCollectorInterface.
DebugHandler connects the debugging system to PHP's native error, exception and shutdown handling functions.
The package includes two renderers:
HtmlDebugRenderer;JsonDebugRenderer.
<?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.
$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.
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'
);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);$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();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.
<?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.
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.
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.
$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();<?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
);$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
);$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.
$json = $debug->renderJson();Render an exception as JSON:
$json = $debug->renderJson(
$exception
);Example structure:
{
"debug": true,
"exception": null,
"data": {
"messages": {
"messages": []
},
"exceptions": {
"exceptions": []
}
}
}$output = $debug->render('html');$output = $debug->render('json');Unsupported formats throw DebugException.
$htmlContentType = $debug->contentType('html');Returns:
text/html; charset=UTF-8
$jsonContentType = $debug->contentType('json');Returns:
application/json; charset=UTF-8
<?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]
<?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
);$manager->setHtmlRenderer(
new HtmlDebugRenderer(
title: 'Custom Debug Page'
)
);$manager->setJsonRenderer(
new JsonDebugRenderer(
prettyPrint: false
)
);Retrieve the configured renderers:
$htmlRenderer = $manager->htmlRenderer();
$jsonRenderer = $manager->jsonRenderer();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();$handler = $debug->createHandler();
$handler->register();This is useful when the application controls exactly when handler registration occurs.
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.
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.
$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 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.
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
);
}
);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.
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'];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.
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.';
}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
);Debugging-system failures are represented by:
Ivi\Debug\Exceptions\DebugExceptionExamples 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.
<?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'
);<?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
);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.
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.
Ivi Debug is open-source software released under the MIT License.
Maintained by Gaspard Kirira and Softadastra.