Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Logging and error pages

A visitor of a live site is not shown an error, but the developer must learn about it. Symfony writes errors to a log through the Monolog library and gives the visitor a page that you can style to match your site.

Writing to the log

use Psr\Log\LoggerInterface;
 
#[Route('/order/{id}/pay', name: 'order_pay', methods: ['POST'])]
public function pay(Order $order, LoggerInterface $logger): Response
{
    $logger->info('Payment started', ['order_id' => $order->getId()]);
 
    // …
 
    $logger->error('The payment service is unavailable', ['order_id' => $order->getId()]);
 
    // …
}

The second argument is an array of details. During development entries go to var/log/dev.log and are visible in the profiler.

Levels

LevelWhen to use it
debugDetails for debugging
infoOrdinary events
warningSomething unusual, but work continues
errorAn operation failed
criticalAn important part of the system is down

Configuration

config/packages/monolog.yaml decides what is written and where:

when@prod:
    monolog:
        handlers:
            main:
                type: fingers_crossed
                action_level: error
                handler: file
            file:
                type: rotating_file
                path: '%kernel.logs_dir%/%kernel.environment%.log'
                max_files: 14

fingers_crossed collects every message of a request and writes them only if an error happened: the log stays small, yet the whole history sits next to the error. rotating_file starts a new file each day and keeps the last 14.

Custom error pages

The templates go into a special folder:

{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
 
{% block body %}
    <h1>Page not found</h1>
    <a href="{{ path('home') }}">Go to the home page</a>
{% endblock %}

error404.html.twig is for code 404, error403.html.twig for 403, error.html.twig for all the rest.

Previewing during development

In development mode a detailed report is shown instead of these pages. To see your own page, open the service address:

http://localhost:8000/_error/404

Raising an error

throw $this->createNotFoundException('Post not found');
 
throw $this->createAccessDeniedException();
 
throw new \Symfony\Component\HttpKernel\Exception\HttpException(429, 'Too many requests');

Errors as JSON

An HTML page does not suit an API. A listener turns an exception into a JSON response:

use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 
#[AsEventListener]
class ApiExceptionListener
{
    public function __invoke(ExceptionEvent $event): void
    {
        if (!str_starts_with($event->getRequest()->getPathInfo(), '/api')) {
            return;
        }
 
        $e = $event->getThrowable();
        $status = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500;
 
        $event->setResponse(new JsonResponse(['error' => $e->getMessage()], $status));
    }
}

Common mistake

The exception text must not be given to a visitor on a 500 error: it may contain table names, file paths and fragments of queries. In the example above it is better to return a general phrase for code 500 and leave the details to the log. For the same reason passwords, tokens and card numbers are not written to the log — only identifiers.

Thanks for reading!

Donate to the site — we work hard for you.