Skyline

JSON worker output

Emit structured, one-object-per-line worker output for your log pipeline.

Setting worker_output to json makes Skyline's queue workers print one structured JSON object per line — job id, uuid, connection, queue, job class, status, attempts and duration — instead of the human-readable table. It is the format you want when your containers ship stdout straight to a log aggregator.

Each worker writes a line per job to its standard output either way. The default table is fine at a terminal and awkward in a pipeline that would rather parse a field than a column.

Enabling#

// config/horizon.php
'worker_output' => env('HORIZON_WORKER_OUTPUT', 'cli'),
# .env
HORIZON_WORKER_OUTPUT=json

The value is trimmed and lower-cased before it is compared, so a stray JSON or a trailing space still opts in rather than silently falling back. Any value other than json means CLI output.

Because the setting is read by the worker process itself, a manually invoked php artisan horizon:work honours it too — you do not have to thread a flag through the supervisor.

Requires Laravel 11+

JSON output is built on the framework's structured worker output, which exists on Laravel 11 and later. On earlier versions the setting is never consulted and output stays in CLI format. No error is raised.

Format#

Each job produces a starting line when a worker picks it up and a terminal line when it finishes, on one line each:

{"level":"info","id":"8813","uuid":"9f2c…","connection":"redis","queue":"default","job":"App\\Jobs\\ProcessPodcast","status":"starting","attempts":1,"timestamp":"2026-07-10T09:14:02.114302+00:00"}
{"level":"info","id":"8813","uuid":"9f2c…","connection":"redis","queue":"default","job":"App\\Jobs\\ProcessPodcast","status":"success","result":"deleted","attempts":1,"timestamp":"2026-07-10T09:14:04.882015+00:00","duration":2.767713}
Field Meaning
levelinfo for starting and success; warning otherwise.
idThe queue's job id — the same id used throughout the dashboard and in lifecycle logging.
uuidThe job's uuid.
connectionQueue connection name.
queueQueue name.
jobResolved job class name.
statusstarting, success, released_after_exception, or failed.
resultdeleted, released or failed — the job's disposition after the attempt.
attemptsAttempt number for this run.
exceptionException class, on a failure.
messageException message, on a failure.
durationSeconds the attempt took. Absent on the starting line.
timestampISO-8601 timestamp with microseconds.

Empty fields are omitted rather than emitted as null, so a successful job carries no exception key at all.

Worker output or lifecycle logging?#

They overlap, and they are not the same tool.

  • JSON worker output is what the worker process prints to stdout. It is the natural fit when your containers ship stdout to a log aggregator, and it covers the job's execution — picked up, finished, how long it took.
  • Job lifecycle logging goes through Laravel's logging stack to a channel you choose, and covers events a worker never sees at all: a job being queued, a delayed job migrating, a dispatch discarded because a unique lock was held.

Both tag lines with the same job id, so if you enable both you can join across them. A common setup is JSON worker output for execution telemetry, plus a lifecycle channel at warning for the silent drops.

Common questions

How do I make Laravel queue workers output JSON?

Set worker_output to json in config/horizon.php (HORIZON_WORKER_OUTPUT=json). Each worker then writes one structured JSON object per line — job id, uuid, connection, queue, job class, status, attempts and duration — instead of the human-readable terminal table. It requires Laravel 11 or later.

Should I use JSON worker output or job lifecycle logging?

They overlap, and they are not the same tool. JSON worker output is what the worker process prints to stdout and covers the job's execution — picked up, finished, how long it took — which suits containers shipping stdout to an aggregator. Lifecycle logging goes through Laravel's logging stack to a channel you choose, and covers events a worker never sees at all: a job being queued, a delayed job migrating, a dispatch discarded because a unique lock was held. Both tag lines with the same job id, so you can enable both and join across them.

Queue control, not just queue monitoring.

Skyline is a drop-in replacement for Laravel Horizon that lets you act on what you see — pause a queue, jump a job to the front, drain a backlog. $99 once, every app you run it on.

Buy Skyline — $99 once

Secure checkout by Anystack. 30 days to change your mind.