Журнал и страницы ошибок
Посетителю рабочего сайта ошибку не показывают, но разработчик должен о ней узнать. 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: 14fingers_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 лучше вернуть общую фразу, а подробности оставить журналу. По той же причине в журнал не пишут пароли, токены и номера карт — только идентификаторы.