Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Журнал и страницы ошибок

Посетителю рабочего сайта ошибку не показывают, но разработчик должен о ней узнать. Symfony записывает ошибки в журнал через библиотеку Monolog, а посетителю отдаёт страницу, которую можно оформить под свой сайт.

Запись в журнал

use Psr\Log\LoggerInterface;
 
#[Route('/order/{id}/pay', name: 'order_pay', methods: ['POST'])]
public function pay(Order $order, LoggerInterface $logger): Response
{
    $logger->info('Оплата начата', ['order_id' => $order->getId()]);
 
    // …
 
    $logger->error('Платёжный сервис недоступен', ['order_id' => $order->getId()]);
 
    // …
}

Второй аргумент — массив с подробностями. При разработке записи попадают в var/log/dev.log и видны в профилировщике.

Уровни

УровеньКогда использовать
debugПодробности для отладки
infoОбычные события
warningНеобычное, но работа продолжается
errorОперация не удалась
criticalОтказ важной части системы

Настройка

Куда и что записывать, определяет config/packages/monolog.yaml:

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 копит все сообщения запроса и записывает их, только если случилась ошибка: журнал остаётся небольшим, но рядом с ошибкой видна вся предыстория. rotating_file заводит новый файл каждый день и хранит последние 14.

Свои страницы ошибок

Шаблоны кладут в особую папку:

{# templates/bundles/TwigBundle/Exception/error404.html.twig #}
{% extends 'base.html.twig' %}
 
{% block body %}
    <h1>Страница не найдена</h1>
    <a href="{{ path('home') }}">На главную</a>
{% endblock %}

error404.html.twig — для кода 404, error403.html.twig — для 403, error.html.twig — для всех остальных.

Просмотр при разработке

В режиме разработки вместо этих страниц показывается подробный отчёт. Чтобы увидеть свою страницу, откройте служебный адрес:

http://localhost:8000/_error/404

Вызов ошибки

throw $this->createNotFoundException('Статья не найдена');
 
throw $this->createAccessDeniedException();
 
throw new \Symfony\Component\HttpKernel\Exception\HttpException(429, 'Слишком много запросов');

Ошибки в формате JSON

Для API страница HTML не подходит. Слушатель превращает исключение в ответ JSON:

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));
    }
}

Частая ошибка

Текст исключения нельзя отдавать посетителю при ошибке 500: в нём бывают имена таблиц, пути к файлам, фрагменты запросов. В примере выше для кода 500 лучше вернуть общую фразу, а подробности оставить журналу. По той же причине в журнал не пишут пароли, токены и номера карт — только идентификаторы.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.