Skyline

Observability

JSON worker output

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

Each worker writes a line per job to its standard output. The default is the human-readable table you see in the terminal — fine at a terminal, awkward in a log pipeline that would rather parse a field than a column. Skyline can switch that output to structured, one-object-per-line JSON.

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.

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.

Sign up for early access