Core provides a unified error and exception handler — the Debug class — that controls how problems are displayed, logged, and rendered for both development and production environments. It catches PHP errors, warnings, notices, exceptions, and fatal shutdown errors through a single pipeline.
Basic Configuration
By default, errors are displayed and logging is enabled. Configure this before App::initialize() is called:
use Webrium\Debug;
use Webrium\App;
if (env('APP_DEBUG', false)) {
Debug::enableErrorDisplay(true);
} else {
Debug::enableErrorDisplay(false);
Debug::enableErrorLogging(true);
Debug::setLogPath(storage_path('logs'));
}
App::initialize(__DIR__ . '/..');
| Method | Description |
|---|---|
enableErrorDisplay(bool) |
Show errors directly in the response (development) |
enableErrorLogging(bool) |
Write errors to a log file |
setLogPath(string) |
Directory where error logs are stored |
isDisplayingErrors() |
Check the current display setting |
If no log path is set, Webrium falls back to the directory registered as logs (via Directory::path('logs')), creating it if it does not exist.
Application Logging with Logger
Debug decides when something is worth logging as an error; the actual file-writing is handled by a separate class, Webrium\Logger. Debug::setLogPath() above simply forwards to Logger::setLogPath() — both point at the same directory.
Unlike Debug, Logger isn't tied to error handling at all — call it directly for anything your application wants recorded, at any of the eight standard severity levels:
use Webrium\Logger;
Logger::info('user exported a report', ['user_id' => $user->id]);
Logger::warning('payment retried', ['order_id' => $order->id, 'attempt' => 2]);
Logger::error('third-party API timed out');
| Method | Level |
|---|---|
Logger::emergency($message, $context = []) |
emergency |
Logger::alert($message, $context = []) |
alert |
Logger::critical($message, $context = []) |
critical |
Logger::error($message, $context = []) |
error |
Logger::warning($message, $context = []) |
warning |
Logger::notice($message, $context = []) |
notice |
Logger::info($message, $context = []) |
info |
Logger::debug($message, $context = []) |
debug |
Logger::log($level, $message, $context = []) |
any of the above, or a custom string |
Each level writes to its own file, named {level}_{Y_m_d}.txt (e.g. storage/logs/error_2026_09_25.txt, storage/logs/info_2026_09_25.txt) — so an error and an info entry from the same day never land in the same file. $context is optional structured data, appended to the entry as JSON.
JSON Error Responses
For API-only applications, force errors to be returned as JSON instead of HTML:
Debug::forceJsonResponse(true);
{
"error": "Class App\\Controllers\\UserController not found",
"status": 500
}
Triggering Errors Manually
Debug::triggerError() lets you raise an application-level error with full control over its status code and behavior:
Debug::triggerError(
message: 'Invalid API key',
statusCode: 401
);
Full Signature
Debug::triggerError(
string $message,
string|false $file = false,
int|false $line = false,
int $statusCode = 500,
bool $isFatal = false,
string $errorType = 'Error'
): void;
- Non-fatal errors (
$isFatal = false, the default) are reported, and execution continues. If a non-fatal error has already been handled in the current request, any further non-fatal errors are still logged but are not re-rendered. - Fatal errors (
$isFatal = true) stop further error processing — only the first fatal error in a request is handled.
Triggering a 404
Debug::notFound('User not found');
This is a shortcut for triggerError($message, ..., 404, false, 'Not Found').
Custom Error Pages
By default, errors render a built-in HTML page (or JSON, if forceJsonResponse is enabled). To use your own templates — for example, with webrium/view — register a custom renderer:
use Webrium\Debug;
Debug::setErrorRenderer(function (array $data): string {
// $data contains: error_message, error_line, error_file,
// error_backtrace, error_type, status_code
return view('errors.debug', $data);
});
This must be set before App::initialize() as well.
Listening for Errors
Every error handled by Webrium is also broadcast as an error event, which you can hook into for logging, monitoring, or notifications:
use Webrium\Event;
Event::on('error', function ($data) {
// $data: ['message', 'line', 'file', 'type', 'is_fatal']
if ($data['is_fatal']) {
// notify via Slack, email, error tracker, etc.
}
});
The event is emitted internally through Event::emit('error', ...) and is dispatched before the error is logged or rendered, so listeners can observe every error — fatal or not.
Inspecting the Current Error State
Debug::hasError(); // bool
Debug::getErrorString(); // error message
Debug::getErrorFile(); // file where the error occurred
Debug::getErrorLine(); // line number
Debug::getHtmlOutput(); // rendered error HTML, if any
View Exceptions
webrium/view maps compiled-template failures back to their original template and line. Core remains responsible for displaying and logging the exception:
if ($exception instanceof \Webrium\View\ViewException) {
$file = $exception->getOriginalView();
$line = $exception->getOriginalLine();
$message = $exception->getMessage();
}
Preserve the complete exception message; do not derive it by splitting on : because view messages and filesystem paths may contain colons. See View Error Handling for source-map and exception details.
Recommended Setup by Environment
Development
Debug::enableErrorDisplay(true);
Debug::enableErrorLogging(false);
Production (Web Application)
Debug::enableErrorDisplay(false);
Debug::enableErrorLogging(true);
Debug::setLogPath(storage_path('logs'));
Debug::setErrorRenderer(fn ($data) => view('errors.500', $data));
Production (API)
Debug::enableErrorDisplay(false);
Debug::enableErrorLogging(true);
Debug::setLogPath(storage_path('logs'));
Debug::forceJsonResponse(true);