Error Handling & Logging

Rhapsody includes a robust global error handler that catches exceptions, converts PHP errors to exceptions, and handles fatal shutdown errors. In development you get detailed Whoops pages; in production you get clean, user‑friendly error pages while all errors are logged.

How It Works

The Rhapsody\Core\ErrorHandler class is registered early in index.php. It sets three PHP handlers:

  • set_error_handler() – converts warnings, notices, etc. into ErrorException.
  • set_exception_handler() – catches all uncaught exceptions.
  • register_shutdown_function() – catches fatal errors (e.g., missing class, parse errors).

Configuration

The error handler reads the logging array from config.php. Add the error_log_path to specify where errors should be written:

'logging' => [
    'error_log_path' => __DIR__ . '/storage/logs/errors.log',
    // other log paths...
],

Development vs. Production

Development Mode (APP_ENV=development)

When an error occurs, the handler displays a Whoops error page with:

  • Full stack trace with clickable file links.
  • Code snippet around the error line.
  • Environment and request data.
  • Interactive console to inspect variables.

Errors are still logged, but the visual feedback helps you debug quickly.

Production Mode (APP_ENV=production)

The handler shows a generic error page to the user – no sensitive details are leaked. Meanwhile, the full error (including stack trace) is written to the configured log file.

By default, HTTP status codes are respected:

  • 404 Not Found – when a route doesn’t match.
  • 500 Internal Server Error – for all other exceptions.

Custom Error Views

You can create custom Twig templates for error pages. Place them in views/errors/ with the status code as the filename:

  • views/errors/404.twig – shown when a page is not found.
  • views/errors/500.twig – shown for server errors.
  • You can add any HTTP status code (e.g., 403.twig, 503.twig).

Example 404.twig:

&lcur;% extends "layouts/main.twig" %}

&lcur;% block title %}404 Not Found&lcur;% endblock %}

&lcur;% block content %}
    <div class="text-center py-20">
        <h1 class="text-6xl font-bold">404</h1>
        <p class="text-xl mt-4">Page not found.</p>
        <a href="&lcur;&lcur; base_url }}/">Go Home</a>
    </div>
&lcur;% endblock %}

If no custom view exists for a status code, the handler falls back to a simple HTML error message.

Throwing HTTP Exceptions

For more control, you can throw Rhapsody\Core\Exceptions\HttpException (optional class – you need to create it). Example:

use Rhapsody\Core\Exceptions\HttpException;

public function show(string $id): Response
{
    $post = $this->postModel->findById($id);
    if (!$post) {
        throw new HttpException(404, 'Post not found.');
    }
    // ...
}

The error handler will then use the provided status code and message, and show the corresponding error view (if exists).

Logging Format

Each error is logged using the Rhapsody\Core\Logger class. A typical log entry looks like:

[2025-06-15 14:32:10] ERROR: [PDOException] SQLSTATE[42S02]: Base table or view not found in /var/www/app/Models/User.php:45
Stack trace:
#0 /var/www/app/Models/User.php(45): PDO->prepare()
#1 /var/www/app/Controllers/UserController.php(22): App\Models\User->findById()
#2 /var/www/core/Router.php(89): App\Controllers\UserController->show()
...

Manual Logging

Even without errors, you can write to the same log file using the Logger class:

$logger = new \Core\Logger($config['logging']['error_log_path']);
$logger->log('User 123 logged out.', 'INFO');

Disabling the Error Handler

In rare cases (e.g., running unit tests), you may want to disable the custom handler. Call restore_error_handler() and restore_exception_handler() manually.