Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Horizon Exporter Laravel Package

renoki-co/horizon-exporter

A Prometheus exporter for Laravel Horizon that exposes queue, job, and worker metrics from Horizon's Redis data via a metrics endpoint, making it easy to monitor Horizon in Grafana and alert on queue health.

View on GitHub
Deep Wiki
Context7

Getting Started

renoki-co/horizon-exporter exposes Laravel Horizon's Redis-backed metrics as a Prometheus scrape endpoint. Version 2.0.0 targets Laravel 9+ only — Laravel 8.x support was dropped in this release, so an existing Laravel 8 app cannot take this upgrade without also moving the framework to 9 (PHP >= 8.0).

Minimal path from zero to a scraped metric:

  1. composer require renoki-co/horizon-exporter:^2.0 — on a Laravel 8 project, upgrade the framework first, then the package.
  2. Publish the config and confirm the route:
    php artisan vendor:publish --provider="RenokiCo\HorizonExporter\HorizonExporterServiceProvider"
    
    The published config/horizon-exporter.php controls whether the endpoint is enabled, the URI path, and the middleware stack applied to it.
  3. Curl the endpoint locally: curl -s http://localhost:8000/horizon-exporter/metrics | head -50. Do this before writing a single dashboard — the exact metric names and label sets (queue, connection, supervisor-ish labels depending on version) are what you will bind PromQL and alert rules to, and they are the one thing worth copy-pasting from your own response rather than from any blog post.
  4. Add the scrape target in prometheus.yml:
    - job_name: 'laravel-horizon'
      metrics_path: /horizon-exporter/metrics
      scrape_interval: 60s
      scrape_timeout: 10s
      static_configs:
        - targets: ['app.example.com']
    
    If you are on Kubernetes with the Prometheus Operator, this is usually a ServiceMonitor/PodMonitor against the app Service rather than a static target.

First real use case: one Grafana panel plotting queue workload and wait time per queue, plus an alert on wait time. That is ~90% of the value; everything else is refinement.

Prerequisite: Horizon must actually be installed, configured, and running against the same Redis connection the exporter reads from. Without Horizon's queue configuration there is nothing meaningful to export.

Implementation Patterns

Treat the exporter as a metrics adapter, not a monitoring system. Horizon still owns the sampling; the exporter only publishes it. The typical production shape is: Horizon runs on its own host/container → exporter endpoint on that same app → Prometheus scrapes it → Grafana/Alertmanager consume it.

Gate the route by environment. Keep it off in local/staging where Horizon isn't supervised:

// config/horizon-exporter.php
'enabled' => env('HORIZON_EXPORTER_ENABLED', false),

and set the env var only on the Horizon-running production instances. In practice you only need to scrape the node(s) where horizon workers are supervised — not every web pod.

Protect the endpoint rather than leaving it open. The route is registered by the package; use the config's middleware to attach auth-style or IP-allowlist middleware, or terminate access at the ingress/nginx level (allow 10.0.0.0/8; deny all;). Prometheus' own basic_auth/bearer_token scrape config pairs with a middleware entry here.

Alert on wait time, not on job counts alone. Queue length is noisy and normal; sustained wait time is the signal that actually means "users are waiting":

max by (queue) (laravel_horizon_queue_wait_time) > 30

(substitute the metric name your /metrics output actually emits; run the alert for for: 5m so a single bursty deploy doesn't page anyone).

Pair throughput against queue length to distinguish "backlog growing because producers are fast" from "workers are dead". A flat throughput with rising queue length is the classic stalled-worker signature and worth its own alert.

Multi-environment / multi-cluster: add an env or cluster external label at the Prometheus scrape-config level rather than trying to get it out of the exporter — it keeps dashboards portable across clusters.

Ship the dashboard as code. Store the Grafana JSON (or a Jsonnet/grafonnet source) next to the app. The metric names are version-coupled to the exporter, so a dashboard committed at the same time as the ^2.0 bump is the cheap way to survive the next major.

If your app exposes other Prometheus endpoints already, keep this route alongside them and use one scrape job per endpoint or a shared metrics_path convention — don't try to merge exporters into one path.

Gotchas and Tips

  • Laravel 8 is gone. 2.0.0 supports Laravel 9 only. Because composer.json will now constrain illuminate/* to ^9.0, an accidental composer update on a Laravel 8 app will fail resolution (usually a good failure, but check it doesn't silently downgrade the package back to 1.x and leave you reading docs for the wrong version).
  • Only Laravel 9 is listed. If you're planning a Laravel 10 upgrade, verify the package's illuminate/support constraint first — the release notes mention nothing beyond v9, so pin the framework and validate in a branch before rolling the whole app forward.
  • Values can be empty or stale right after a deploy or restart. Horizon samples wait time/throughput periodically and Redis list lengths only exist once queues have traffic. A freshly booted Horizon will report zeros; don't build "is the queue healthy" logic that fires during the first few minutes.
  • route:cache and config:cache will bite you. After changing the path or middleware in horizon-exporter.php, run php artisan config:clear && php artisan route:clear (or re-cache) — otherwise you'll debug an unchanged endpoint for twenty minutes.
  • Scrape latency scales with queue count. The endpoint does Redis round-trips across Horizon's supervised queues; with dozens of queues and a default 10s scrape_timeout you can get context deadline exceeded scrapes and gaps in Grafana. Either raise scrape_timeout or trim horizon.php's supervised queue list.
  • Don't scrape every web replica. Multiple targets produce duplicate series with different instance labels; aggregate with max by (queue) in PromQL, or restrict the scrape target to Horizon hosts only.
  • Redis connectivity is the failure mode. If the exporter's Redis connection (Horizon's connection name, not a hard-coded default) points somewhere Horizon isn't writing, you get a 200 with empty/repeated-zero metrics rather than an error — validate against Horizon's own UI at /horizon when numbers look wrong.
  • No auth by default. Treat the route as an information leak (queue names, throughput, infrastructure size) until you've added middleware or network policy; it belongs on the same allowlist as /horizon.
  • Extension point: if you need extra series (e.g. per-tenant job counters), don't fork — register your own /metrics route with promphp/prometheus_client_php and keep this package for the Horizon-native gauges.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin