Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Помилки й логування»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

3 питання

Коли під час запиту виникає виняток і ніхто його не перехопив, Laravel передає його обробнику винятків. Обробник робить дві незалежні речі:

Крок Що відбувається Для кого
report (звітування) запис у лог, відправка в Sentry, Flare, Bugsnag для розробників
render (рендеринг) перетворення винятку на HTTP-відповідь для користувача
виняток → report: laravel.log, Sentry
        → render: сторінка 500, JSON {"message": "..."}, редірект з помилками валідації

Чому це розділено: те, що бачить користувач, і те, що бачить розробник, мають бути різними. Користувачу - зрозуміле повідомлення без деталей, розробнику - повний стек викликів, контекст запиту, користувач.

Як Laravel рендерить різні винятки:

  • ValidationException - редірект назад з помилками й старим введенням, або 422 з JSON для API;
  • AuthenticationException - редірект на сторінку входу чи 401;
  • AuthorizationException - 403;
  • ModelNotFoundException (від findOrFail) - 404;
  • HttpException (від abort(404)) - відповідний код;
  • будь-що інше - 500.

Деякі винятки взагалі не звітуються: валідація, 404, помилки автентифікації - це очікувані ситуації, а не баги. Інакше лог заповнився б шумом.

JSON чи HTML: якщо запит очікує JSON (заголовок Accept: application/json), Laravel повертає помилку у форматі JSON.

APP_DEBUG:

  • true - сторінка помилки зі стеком викликів, кодом і змінними - лише для локальної розробки;
  • false - загальна сторінка «Server Error» без деталей. На продакшені тільки так.

Налаштовується все в bootstrap/app.php:

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (PaymentFailed $e) {
        // власне звітування
    });

    $exceptions->render(function (PaymentFailed $e, Request $request) {
        return response()->view('errors.payment', status: 402);
    });
})

Докладніше в документації: Помилки: обробка винятків

abort() кидає HttpException з указаним кодом - обробник винятків перетворює його на відповідь з цим статусом:

abort(404);
abort(403, 'Цей проєкт архівовано.');

abort_if(! $user->isAdmin(), 403);
abort_unless($post->isPublished(), 404);

Після abort() код далі не виконується - це виняток, а не return.

Коли що використовувати:

  • 404 - ресурсу немає чи користувачу не можна знати, що він існує;
  • 403 - ресурс є, але доступ заборонено. Для перевірки прав краще політики ($this->authorize(), Gate) - вони кидають той самий 403, але логіка прав зібрана в одному місці;
  • findOrFail() замість ручного abort(404) після find() - коротше й без забутої перевірки.

Власні сторінки помилок - просто Blade-шаблони за кодом статусу:

resources/views/errors/404.blade.php
resources/views/errors/403.blade.php
resources/views/errors/500.blade.php
resources/views/errors/503.blade.php   # режим обслуговування

Усередині доступний виняток:

<h1>{{ $exception->getMessage() ?: 'Сторінку не знайдено' }}</h1>

Запасні сторінки для групи кодів: 4xx.blade.php і 5xx.blade.php використовуються, якщо немає шаблону для конкретного коду.

Стандартні шаблони Laravel можна скопіювати як основу:

php artisan vendor:publish --tag=laravel-errors

Типові пастки:

  • сторінка 500 не повинна залежати від того, що могло зламатися: запитів до бази, сесії, складних компонентів. Якщо помилку спричинила база, сторінка помилки, що звертається до бази, впаде теж;
  • повідомлення в abort(403, '...') бачить користувач - жодних технічних деталей;
  • для API шаблони не використовуються: при Accept: application/json Laravel повертає {"message": "..."} з відповідним статусом.

Докладніше в документації: Помилки: власні сторінки HTTP-помилок

Laravel використовує Monolog, а писати в лог можна через фасад Log чи хелпер logger():

use Illuminate\Support\Facades\Log;

Log::info('Замовлення оформлено', ['order_id' => $order->id]);
Log::warning('Платіжний шлюз відповідає повільно', ['ms' => $elapsed]);
Log::error('Не вдалося надіслати рахунок', ['order_id' => $order->id]);

logger('Коротке налагоджувальне повідомлення');   // рівень debug

Рівні логування (за RFC 5424, від найважливішого):

Рівень Коли використовувати
emergency система непрацездатна
alert потрібна негайна дія (база недоступна)
critical критична помилка компонента
error помилка, що не зупиняє застосунок, але потребує уваги
warning щось незвичне, але не помилка (повторна спроба, повільна відповідь)
notice нормальна, але важлива подія
info звичайні події: вхід користувача, оформлення замовлення
debug подробиці для налагодження

Мінімальний рівень задається для каналу (змінна LOG_LEVEL). На продакшені зазвичай info чи warning: повідомлення debug просто відкидаються, не засмічуючи лог.

Другий аргумент - контекст: масив з даними, а не склеєний рядок.

// погано: неможливо шукати й фільтрувати
Log::info("Order {$order->id} paid by {$user->email}");

// добре: структуровані дані
Log::info('Order paid', ['order_id' => $order->id, 'user_id' => $user->id]);

Структуровані дані легко шукати в системах збору логів, і повідомлення лишається однаковим для всіх подій цього типу.

Куди пишеться: за замовчуванням у storage/logs/laravel.log (канал stack з single). Канали налаштовуються в config/logging.php.

Чого не писати в лог: паролі, токени, повні номери карток, персональні дані понад необхідне. Логи часто мають ширший доступ, ніж база даних, і зберігаються довше.

Не плутати з dd() і dump(): вони виводять у відповідь і для продакшену не підходять, а лог працює і в черзі, і в консольних командах, і без браузера.

Докладніше в документації: Логування: запис повідомлень