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

Laravel Telemetry Ui Laravel Package

cboxdk/laravel-telemetry-ui

Grafana-replacement observability UI for Laravel. Query Tempo, Loki and Prometheus/Mimir directly; explore Laravel-shaped screens for routes, jobs, exceptions, hosts and users with drill-down and cross-signal correlation. No agent or vendor cloud.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimum viable install (4 steps):

composer require cboxdk/laravel-telemetry-ui
TELEMETRY_UI_METRICS_URL=http://prometheus:9090
TELEMETRY_UI_TEMPO_URL=http://tempo:3200
TELEMETRY_UI_LOKI_URL=http://loki:3100

Open the gate — it defaults to local only, so a staging box will just 403 until you say otherwise:

// AppServiceProvider::boot()
Gate::define('viewTelemetryUi', fn ($user) => $user?->isAdmin() ?? false);

Then verify the wiring before you trust a single chart:

php artisan telemetry-ui:check   # per-connection OK / FAIL / not-configured, non-zero on failure

Visit /telemetry-ui.

Where to look first. The requests list and a route detail page. That single path exercises every layer of the stack — Prometheus for throughput/latency, Tempo for the traces, the waterfall, and (if node/process exporters are scraped) the host context strip. If that works end to end, the rest of the screens are the same machinery scoped differently. Read docs/core-concepts/configuration.md and docs/core-concepts/connections.md next; both are short and answer most "why is this card empty" questions.

First real use case. Next slow-endpoint report: open Requests, sort by p95, click the route to reach its detail page, walk throughput → latency → status codes → the individual traces, open a trace, read the waterfall, then check the context strip to see whether the box was hot at the time. That answer normally takes three tools and a Grafana dashboard hunt; here it's four clicks.

Implementation Patterns

The triage loop (exceptions). Start at the Exceptions page, not at Loki. The unified errors card groups by the Sentry-style exception.group fingerprint, so a browser TypeError and the PHP exception behind it collapse into one row tagged web/server/full-stack. Click the row for a representative trace, then use the drawer's issue-tracker action to file a GitHub/Sentry/Linear ticket without leaving the page. This is the highest-value daily pattern because it removes the "which signal do I open first" decision entirely.

Drill-down over filtered search. Resist the urge to build a generic TraceQL query for a route or job. Clicking a row opens a dedicated detail page scoped to that entity (scopeMatchers() + the ScopesTo* traits reuse the overview cards against one route/job/exception). The detail page keeps you in a known-good scope while you narrow; hand-written TraceQL is for when you already know what you're looking for.

Dimensional filtering. Every span/resource attribute in the trace properties window is a click-to-filter link — host, user, team, tenant.id, client IP, deployment. Use it in preference to retyping TraceQL { .key = "value" }, and prefer attributes your app deliberately emits (team, tenant, deployment) over high-cardinality accidents. The Hosts page is the same idea applied to servers: it lists everything reporting telemetry and filters requests per host on click.

Annotate deploys as part of the pipeline. Charts without vertical deploy markers are nearly undecorated for correlation work. Wire this into your deploy script:

php artisan telemetry-ui:annotate deploy      # also: incident, scaling, migration, feature, version

And schedule the safety net so an un-announced deploy still lands on the charts:

$schedule->command('telemetry-ui:scan-versions')->hourly();

scan-versions is stateless — it diffs the live laravel_version against version annotations already in Loki — so running it aggressively is cheap and idempotent.

Before/after comparisons. Label both runs with the emitter's header, then diff:

curl -H 'X-Telemetry-Label: before' https://app.test/orders
# apply fix
curl -H 'X-Telemetry-Label: after'  https://app.test/orders

compare_traces (via MCP, or by finding the labelled traces in the UI) diffs totals, time per category, query shapes and flags. Report the deltas, not "feels faster" — a fixed N+1 shows up as a flag present only on before.

Scoped sampling with monitors. For an intermittent bug, don't crank global sampling. Place a monitor on one route, path, user, context value or job for a bounded window; kept traces carry telemetry.directive = <id> and are queryable via search_traces with { span.telemetry.directive = "<id>" }. Keep it narrow, short, and always give a reason. Stop it when you're done.

MCP for agent-assisted RCA. php artisan mcp:start telemetry-ui for stdio (Claude Desktop / Cursor); TELEMETRY_UI_MCP_WEB=true + laravel/passport for remote HTTP with OAuth + DCR. The six read tools (list_traces, trace_summary, trace_context, search_traces, query_metrics, query_logs) plus trace_markdown cover the "why is this slow" loop. trace_markdown output is designed to be pasted verbatim into a PR description — don't paraphrase it.

Extending without a JS build. Custom cards and detail pages are Blade + a PromQL/TraceQL/LogQL string; register them via the documented page/card hooks in docs/extension-points/. Custom backends (a non-standard metrics store, an internal issue tracker) go through the MetricsSource/TracesSource/LogsSource/IssuesSource contracts resolved lazily by the ConnectionManager, so boot stays cheap.

Scope lock for multi-team installs. If different teams shouldn't see each other's traces, set telemetry-ui.scope.lock or call TelemetryUi::restrictScopeUsing() in a service provider. Do this deliberately and document it — it's the kind of restriction that produces confusing "not found" reports otherwise.

Deploy healthcheck. telemetry-ui:check exits non-zero on failure. Run it after deploy (or as a post-deploy smoke step) so a broken datasource URL surfaces from CI, not from someone squinting at an empty chart at 3am.

Gotchas and Tips

It's alpha — pin and read the changelog. The screens are in daily use but APIs may shift before 1.0. Pin the version in composer.json and read CHANGELOG.md before every bump. The 0.2.1 fix is a good illustration: queue throughput cards silently matched nothing because OTLP translates the {jobs}/min unit to a _per_min suffix. Metric names are derived, not hand-written, in places — a collector upgrade can rename a series out from under you.

Hard dependency on cboxdk/laravel-telemetry. This package is schema-aware of that emitter: it reads the metric names and span attributes laravel-telemetry writes. Upgrade them together. If a card goes empty after an emitter upgrade, suspect the schema before you suspect the connector.

The gate defaults to local. A staging or prod-adjacent environment will show a bare 403 with no hint. If a colleague says "the dashboard is broken," check viewTelemetryUi first.

Query cache TTL is 5 seconds by default. That's the whole point — a dashboard full of auto-refreshing cards must not hammer Prometheus/Tempo/Loki — but it means a just-deployed annotation or a just-fixed route can take a few seconds to appear. It's overridable per connection when a specific page genuinely needs fresher data. Only decoded raw arrays are cached, never DTOs, so custom drivers get this for free as long as they return arrays.

Throttle is 120,1. With stacked drawers, several charts and a short refresh interval, a single power user can brush against that. Raise telemetry-ui.throttle before you raise the cache TTL — they solve different problems.

Transient-blip retries are configured, not magical. telemetry-ui.retries handles flaky backend connections. If you see intermittent FAILs from telemetry-ui:check against a healthy Prometheus, look at retry count before rewriting the connector.

"Not found" usually means the scope lock, not a missing trace. Every MCP tool and page runs inside the viewer's scope lock. This wastes a lot of agent time if the lock isn't documented — tell your team (and your agents) explicitly.

MCP writes are off by default. monitor only exists when TELEMETRY_UI_MCP_WRITE=true; over HTTP it additionally needs the manageTelemetryUi ability. If the tool is absent, use php artisan telemetry:watch from laravel-telemetry instead of hunting for a workaround, and never claim to have changed sampling or config through the read tools — they can't.

Redaction follows page permissions. trace_markdown includes stack traces and source only when the viewer may open the exceptions page. If a snapshot comes back thin, that's authorization, not a bug.

Trace-sourced lists are bounded samples. Analytics, RUM, the unified errors list and frontend page timings are all read from Tempo spans, not from pre-aggregated metrics — that's what makes them exact on low-traffic sites and affordable with no extra store. At scale they're a bounded recent sample. Don't quote an error count from these screens as a precise number in an incident report; quote the trend and the representative trace.

Context signals are fail-open per signal. Each entry in telemetry-ui.context.signals can be absent without breaking the trace view, and each tile compares against a baseline over a longer lookback. A tile reading "CPU 95% (typical 30%)" is a comparison to that scope's normal, not an alert. If a signal is missing entirely, check that the exporter (node_exporter, mysqld_exporter, process_*) is actually scraped — the UI won't invent it.

Time alignment between Prometheus and Tempo. The context strip queries a window around the trace's timestamp. Clock skew between the app host and Prometheus will quietly misalign that window. Keep NTP honest across the fleet.

Frontend spans need the emitter's proxy. Browser spans share the backend's service.name and are marked by the server-stamped browser=true attribute. If the Frontend page is empty, the question is whether the frontend proxy is installed on the emitter — not whether the UI is configured.

Rebuild the ECharts bundle when you fork. public/ holds a prebuilt bundle. npm run build is only needed if you touch it; custom cards and pages themselves need no JS build. Running composer check (pint + phpstan level 8 + pest) before any patch to the package is expected — it's the project's own bar, and level 8 catches the sort of nullable-array mistakes that show up as blank cards.

Pointing at Grafana Cloud / hosted LGTM? Use the datasource proxy rather than direct endpoints — docs/cookbook/connect-via-grafana-proxy.md. Same three env vars, different URLs, and the auth lives in the proxy.

Schema families light up their own pages. Optional families (e.g. cboxdk/statamic-telemetry) add pages only when their metrics exist. An absent page is usually absent metrics, not a missing route.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource