Раніше, коли воркер черги зупинявся і Supervisor запускав новий, логи показували лише обробку завдань, потім пропуск, а потім знову обробку. Незалежно від того, чи була це команда queue:restart, досягнення ліміту пам'яті або втрата з'єднання з базою даних - вивід виглядав однаково.
У Laravel 13.30 queue:work тепер записує причину зупинки:
2026-09-01 13:20:40 Worker STOPPED Memory limit exceeded
Що вже було раніше
Воркер і раніше знав, чому він зупиняється. Подія WorkerStopping передає WorkerStopReason разом зі статусом виходу, кількістю оброблених завдань, часовою міткою останнього завдання та використаною пам'яттю:
use Illuminate\Queue\Events\WorkerStopping;
Event::listen(function (WorkerStopping $event) {
Log::info('Worker stopped', [
'reason' => $event->reason?->value,
'status' => $event->status,
'jobs' => $event->jobsProcessed,
'memory' => $event->memoryUsage,
]);
});
Це працює і досі залишається правильним інструментом, коли потрібно відправляти дані в систему метрик. Але це занадто багато коду для типового випадку - коли ви просто хочете побачити в терміналі або лозі, чому процес завершився.
Новий вивід
Тепер queue:work сам слухає цю подію та виводить фінальний рядок. У терміналі:
2026-09-01 13:20:40 Worker STOPPED Queue empty
З опцією --json це виводиться як запис поряд із виводом для кожного завдання, тому pipeline логів, що вже парсить одне, зможе парсити й інше:
{"level":"info","status":"stopped","reason":"empty","exit_code":0,"jobs_processed":12,"memory":34.0,"timestamp":"2026-09-01T13:20:40.118273+00:00"}
reason - це backing value enum, стабільний для порівняння. memory вказується в мегабайтах, округлених до одного знака після коми. level залежить від коду виходу: info для чистого виходу, warning для всього іншого - на практиці це означає перевищення ліміту пам'яті або таймаут завдання.
Нічого не виводиться з опціями --quiet або --silent, або коли причина null. Останній випадок варто знати: воркер, убитий ззовні (OOM killer або SIGKILL), ніколи не досягає коду, що відправляє подію. Тиша все ще означає, що процес завершився без запиту.
Дев'ять причин зупинки
WorkerStopReason отримав метод description(), що містить людино-читабельні рядки. Тепер enum - це єдине місце, звідки і консольний вивід, і ваші власні слухачі можуть їх читати:
| Значення |
Опис |
Код виходу |
empty |
Queue empty |
0 |
empty_for |
Queue empty for the configured duration |
0 |
max_jobs |
Maximum jobs exceeded |
0 |
max_time |
Maximum run time exceeded |
0 |
restart_signal |
Received restart signal |
0 |
interrupted |
Interrupted |
0 |
lost_connection |
Lost connection |
0 |
memory |
Memory limit exceeded |
12 |
timed_out |
Job timed out |
1 |
Два ненульові коди - це ті, на які варто налаштувати алерти, і вони означають різні речі:
memory - це коли воркер помічає, що його власне використання пам'яті перевищило --memory між завданнями. Це механізм працює як задумано. Це стає сигналом, коли це відбувається після двох завдань замість двох тисяч - це вказує на завдання, що тримає великий result set, або на витік пам'яті в чомусь, що воркер завантажує один раз. Код виходу 12 - це Worker::EXIT_MEMORY_LIMIT, і його можна налаштувати через Worker::$memoryExceededExitCode, якщо ваш менеджер процесів обробляє конкретні коди по-різному.
timed_out відрізняється за своєю природою. Інші вісім причин - це коли воркер вирішує зупинитися між завданнями; ця - коли батьківський процес вбиває дочірній, що перевищив --timeout посеред завдання. Метод failed() цього завдання може не виконатися так, як при звичайній помилці, тому серія виходів timed_out зазвичай означає, що одне завдання потребує довшого $timeout або меншої одиниці роботи.
Інші причини зупинки
Причини з нульовим статусом переважно інформаційні, з одним винятком. lost_connection завершується чисто, бо перепідключення - це робота менеджера процесів, але постійний потік таких виходів означає, що з'єднання з базою даних або Redis обривається під воркером, часто через idle timeout на файрволі або проксі, а не самою базою даних.
Інші описують намір: restart_signal - це коли хтось запускає queue:restart, зазвичай під час деплою. empty та empty_for - це --stop-when-empty та --stop-when-empty-for, що очікується на контейнері, який масштабується до нуля. max_jobs та max_time - це --max-jobs та --max-time, навмисне перезапуск, що запобігає накопиченню стану в довготривалому PHP-процесі. interrupted - це SIGINT або SIGTERM, що відправляє Ctrl-C та більшість завершень контейнерів.
Читання в продакшені
Цінність JSON-формату в тому, що питання "чому воркер перезапустився" більше не потребує кореляції часових міток. Фільтруйте за причиною, і відповідь у одному полі:
php artisan queue:work --json --max-time=3600 2>&1 | \
jq -c 'select(.status == "stopped")'
Під менеджером процесів, що пише у файл, той самий запит виконується проти логу постфактум. Воркер, що перезапускається на max_time кожну годину - це конфігурація працює. Той самий воркер, що перезапускається на memory кожні чотири хвилини - це витік пам'яті. Раніше обидва виглядали як перезапуск.
Слухач події все ще є для будь-чого, що виходить за межі читання, наприклад, інкрементування лічильника на причину, щоб дашборд показував розподіл у часі, а не один рядок на вихід.
Додано @jackbayliss у #61339.