Middle: питання на співбесіді з теми «Налагодження»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Логування в Laravel побудоване на Monolog; канали налаштовуються в config/logging.php.
Log::info('Замовлення створено', ['id' => $order->id]);
Log::channel('slack')->critical('Платіж не пройшов');
Типи каналів: single (один файл), daily (ротація по днях), slack, papertrail, stderr, а також stack - який пише одразу в кілька:
'stack' => ['driver' => 'stack', 'channels' => ['daily', 'slack']],
Практики: рівні (debug…emergency), структуроване логування з контекстом, маскування PII, окремий канал для критичних подій у Slack/Sentry. У проді LOG_LEVEL зазвичай warning+.
Подивитися SQL одного запиту без виконання:
User::where('active', true)->orderBy('name')->toSql();
// select * from "users" where "active" = ? order by "name" asc
User::where('active', true)->toRawSql();
// select * from "users" where "active" = 1 ... - з підставленими значеннями
Вивести й продовжити чи зупинитися:
User::where('active', true)->dumpRawSql()->get(); // вивести й виконати
User::where('active', true)->ddRawSql(); // вивести й зупинити
toRawSql() зручний, щоб скопіювати запит у консоль бази й запустити EXPLAIN. Але для реального виконання Laravel завжди використовує підготовлені запити з окремими параметрами - підставлені значення лише для читання людиною.
Усі запити за запит чи команду:
DB::enableQueryLog();
// ... код ...
dump(DB::getQueryLog()); // SQL, параметри й час кожного
Слухач запитів - для логування в розробці:
// AppServiceProvider::boot()
if (app()->isLocal()) {
DB::listen(function (QueryExecuted $query): void {
Log::debug($query->toRawSql(), ['ms' => $query->time]);
});
}
Сповіщення про повільну роботу з базою за весь запит:
DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event): void {
Log::warning('Багато часу в базі', ['connection' => $connection->getName()]);
});
Як знайти проблеми системно:
- N+1:
Model::preventLazyLoading(! app()->isProduction())- виняток при лінивому завантаженні зв'язку в циклі; - Laravel Debugbar чи Telescope - список запитів сторінки з дублікатами й часом;
- сторінка помилки Laravel також показує виконані запити;
- на продакшені - журнал повільних запитів бази (
pg_stat_statements, slow query log), а не логування кожного запиту.
Пастка: enableQueryLog у довгому процесі (воркер, команда з мільйоном записів) накопичує всі запити в пам'яті - вмикайте його лише на час налагодження.
Класичний спосіб - tail -f storage/logs/laravel.log. Він працює лише з файловим каналом, показує сирий текст разом з багаторядковими стеками, і його незручно фільтрувати.
Laravel Pail - Artisan-команда, яка показує записи логів у реальному часі в зручному вигляді:
composer require --dev laravel/pail
php artisan pail
Головна перевага: Pail отримує записи напряму від застосунку, тому працює з будь-яким драйвером логів - навіть якщо записи йдуть у Sentry, Papertrail чи stderr і локального файлу немає.
Фільтри:
php artisan pail --filter="QueryException" # тип винятку, файл, текст
php artisan pail --message="Payment" # лише за текстом повідомлення
php artisan pail --level=error # рівень
php artisan pail --user=42 # записи, зроблені в запитах користувача 42
php artisan pail -v # без скорочень
php artisan pail -vv # зі стеком викликів
Фільтр за користувачем особливо корисний: можна відтворити проблему під потрібним обліковим записом і бачити лише свої записи серед загального потоку.
Типові сценарії:
- налагодження черги чи планувальника, де немає сторінки помилки: запустити
pailпоруч з воркером; - розробка інтеграції з вебхуками - бачити, що прийшло й що записано, одразу;
composer run devу новому Laravel-проєкті запускає Pail разом із сервером, чергою й Vite.
Обмеження:
- потрібне розширення PCNTL - на Windows лише через WSL;
- Pail показує записи, що з'являються після запуску, - історію дивляться в логах;
- це інструмент розробника: на продакшені для пошуку причин збоїв потрібне централізоване сховище логів з пошуком і зберіганням, а не перегляд у терміналі.
Щоб логи були корисними в Pail: структурований контекст замість склеювання рядків - Log::info('Order paid', ['order_id' => $order->id]).