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/jsonLaravel повертає{"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(): вони виводять у відповідь і для продакшену не підходять, а лог працює і в черзі, і в консольних командах, і без браузера.