Skip to content

Laravel Telemetry UI

Laravel Telemetry UI

cboxdk/laravel-telemetry-ui is a Laravel-native observability dashboard that queries your existing Grafana stack — Tempo (traces), Loki (logs) and Prometheus/Mimir (metrics) — directly. No agent, no vendor cloud, no data leaves your infrastructure.

It is the presentation counterpart to cboxdk/laravel-telemetry: the emitting package defines a stable schema of metric names, span attributes and resource attributes, and this package is schema-aware. That is what lets it render opinionated, Laravel-shaped screens (Requests, Jobs, Queries, Exceptions, Users…) instead of Grafana's generic panels — and link them: click a slow route, see its traces; open a trace, see the queries and the trace-correlated logs.

The dashboard: golden signals for the whole service, with the time range and scope applying to every panel on the page.

v2 is a versioned JSON API plus a prebuilt React app the package serves itself, so hosts need no Node toolchain. Everything on a page is a panel, a plain PHP class your packages can add to:

TelemetryUi::page('autoscale', 'Autoscale', group: 'Queues');
TelemetryUi::panel(AutoscaleDecisions::class, page: 'autoscale');

final class AutoscaleDecisions extends Panel
{
    public function data(): array
    {
        return $this->promChart(
            'Scaling decisions',
            $this->metric('autoscale_scaling_events_total')->rate('5m')->sumBy('queue'),
            seriesLabel: 'queue',
        );
    }
}

And any attribute your app emits can become a first-class dimension — a facet, a group-by option, a filter chip and an entity page:

TelemetryUi::dimension('billing.customer_id', label: 'Customer',
    link: fn ($id) => route('customers.show', $id));

Dimensions bend to what your app already emits. Show names instead of ids (TelemetryUi::resolve('user.id', User::class, 'name')), read a dimension out of another attribute when a routing layer encodes it there (from: 'http.route', pattern: 'portal:{value}'), give that layer its own page with routeFamily(), or declare a chart over any metric — including one from a sidecar in another language — with metricPanel(), no panel class needed. See dimensions & Explore and developer integrations.

Why not just Grafana?

Grafana is generic; this dashboard knows what a Laravel app is. Routes, jobs, scheduled tasks, cache stores and users are first-class concepts, cross linked across signals. It also lives inside your app: your auth, your gate, and actions a read-only dashboard can never do — create a ticket from an exception, or hand the whole stack to an agent over MCP for incident RCA.

Documentation

The screens

Request explorer. Facets on the left are exact when the traces backend can aggregate server-side, and a labelled sample when it cannot.

Trace search. Every row opens a waterfall; the filter bar compiles to TraceQL, LogQL or the store's own dialect depending on the connection.

Database queries grouped by fingerprint, so an N+1 shows up as one row with a high count rather than a thousand rows.

Log explorer with live tail. The query is the same IR the other explorers use, compiled to LogQL.

Exceptions grouped by their fingerprint, with first and last seen — the grouping is the package's, not a backend's.

Queue health: depth, wait time and throughput per queue, with the labels bounded by the classifier so a per-tenant queue name cannot explode the series count.

Outgoing HTTP by host: connection time, TLS handshake and request duration, which is where a host provisioned in the wrong zone becomes visible.

The infrastructure pages are shown in infrastructure discovery, and the analytics page in the web analytics cookbook.

Every screenshot here is captured from the package's own browser tests against the fixture backends, so what is pictured is what the code renders — not a mock-up, and not a staging environment that has since changed. See src/Testing/ScreenshotManifest.php.