In-app monitoring for Laravel — requests, exceptions, queries, jobs, commands, scheduled tasks, logs and mail — branched directly onto Arkhè Main with a Flux UI dashboard and a Watcher menu.
It reproduces the core of laraowl's
application telemetry, but without a separate application to deploy. There
is no server to host, no laraowl/client to point at it, no Reverb WebSocket
server and no second queue: everything runs inside the host app and stores to a
single table. Think Telescope/Pulse, wearing Arkhè's admin chrome.
adhocrat-io/arkhe-main ^3.2 (this package requires it and mounts onto it — the navigation registry it branches onto landed in 3.2.0)composer require adhocrat-io/arkhe-watcher
php artisan arkhe:watcher:install
The installer:
config/watcher.php) and the migrations,watcher_entries and watcher_settings),@source line to resources/css/app.css so Tailwind scans the
package's Blade views,view-watcher permission and grants it to the root and
administrator roles.That's it — open /administration/watcher.
The Watcher menu needs no sidebar patching: the package registers its own
collapsible group into Arkhè Main's shared navigation registry (see below), and
Arkhè Main's sidebar partial renders it. As long as your layout includes
@include('arkhe::partials.sidebar-items') (Arkhè Main's installer wires that
in), the menu appears automatically.
Routes mount under Arkhè's admin prefix: {arkhe.admin.prefix}/watcher
(e.g. /administration/watcher), named arkhe.watcher.*.
Access uses Arkhè's web, auth and arkhe.backend middleware, plus
this package's arkhe.watcher gate: an Arkhè root always passes,
otherwise the user needs the view-watcher permission.
Layout is resolved from config('arkhe.admin.layout'), so dashboard
pages render inside your existing Arkhè shell.
Menu — the package registers a watcher section on Arkhè Main's
ArkheNav registry from its service provider (requires Arkhè Main ≥ 3.2):
ArkheNav::section('watcher', heading: fn () => __('arkhe-watcher::watcher.menu'),
priority: 90, can: /* root or view-watcher */)
->item('overview', fn () => __('…'), 'chart-bar', route: 'arkhe.watcher.index')
// …one item per dashboard page
This is the same mechanism any satellite package uses to branch onto Arkhè's
backend — settings entries go into the shared settings ("Réglages") section,
richer tools declare their own section like the Watcher does.
| Watcher | Captures | Toggle env |
|---|---|---|
| Requests | method, URI, route, status, duration, memory, user, slow flag | WATCHER_REQUESTS |
| Exceptions | class, message, file/line, trimmed stack trace, grouped by fingerprint | WATCHER_EXCEPTIONS |
| Queries | SQL, normalised fingerprint, bindings, time, connection, slow flag | WATCHER_QUERIES |
| Jobs | name, queue, connection, status, attempts, duration, error | WATCHER_JOBS |
| Commands | command, arguments, exit code, duration | WATCHER_COMMANDS |
| Schedule | scheduled task runs — finished / failed / skipped, output, cron | WATCHER_SCHEDULE |
| Logs | level, message, sanitised context | WATCHER_LOGS |
| subject, from/to/cc/bcc, mailer, HTML body | WATCHER_MAIL |
|
| Cache | hit / miss / write / forget by key (off by default — noisy) | WATCHER_CACHE |
| Outgoing HTTP | method, URL, status, duration for Http:: client calls |
WATCHER_HTTP |
Overview (headline counts + recent exceptions + slowest requests),
Requests, Exceptions (grouped, with occurrence counts), Queries
(slow-only filter), Issues (query health — see below), Jobs
(failed-only filter), Commands, Scheduled tasks, Logs (level filter) and
Mail. Every page has a time-range filter (1h / 24h / 7d / 30d / all),
free-text search, a detail flyout and live polling.
Reproducing the heart of wilfridterry/cardinal,
the Issues page surfaces database problems the analyzer derives from each
request's queries:
where id = ?); a fingerprint that fires more than WATCHER_NPLUSONE_THRESHOLD
times in one lifecycle is flagged as an N+1, with the offending queries marked.table.column.php artisan arkhe:watcher:analyze <uuid>), the watcher asks Claude for a
root-cause diagnosis and a concrete fix: a ready-to-run Laravel migration for a
missing index, or the eager-loading change for an N+1. Enable with
WATCHER_AI_ENABLED=true and an API key; it calls the Anthropic Messages API
through Laravel's HTTP client (no extra package dependency), defaulting to
claude-opus-4-8.php artisan arkhe:watcher:issues prints the same report on the CLI.
A root-only Settings page (arkhe.watcher.settings) lets you toggle the
master switch and each collector, and adjust retention, HTTP sampling and the
live-refresh interval — all from the UI. It is branched into Arkhè Main's shared
Réglages section (not the Watcher group), so it sits next to every other
package's settings. Values are stored in watcher_settings and layered over the
config-file defaults at boot (collector toggles take effect on the next request).
Each watcher subscribes to framework events and pushes an IncomingEntry into a
buffered Recorder. The buffer is written in one bulk insert at the end of
each lifecycle — HTTP terminate, job processed, command finished, scheduled task
done — so monitoring stays off the request's critical path. Everything recorded
during one lifecycle shares a batch_id.
Recording is fail-safe (storage errors are swallowed), governed by a master
switch (WATCHER_ENABLED) and optional HTTP sampling (WATCHER_SAMPLING),
and never records its own dashboard reads.
// config/watcher.php
'enabled' => env('WATCHER_ENABLED', true),
'sampling' => env('WATCHER_SAMPLING', 1.0), // record this fraction of HTTP traffic
'retention_days' => env('WATCHER_RETENTION_DAYS', 7),
'slow' => ['request' => 1000, 'query' => 100, 'job' => 5000], // ms
'poll_interval' => env('WATCHER_POLL_INTERVAL', '10s'),
'connection' => env('WATCHER_DB_CONNECTION'), // keep monitoring off your primary DB
'detectors' => ['n_plus_one' => ['enabled' => true, 'threshold' => 5], 'missing_index' => ['enabled' => true]],
'ai' => ['enabled' => false, 'model' => 'claude-opus-4-8', 'api_key' => env('WATCHER_AI_API_KEY', env('ANTHROPIC_API_KEY'))],
Point connection at a separate database to isolate monitoring writes. Lower
sampling on busy production apps. Disable the cache watcher (default) unless
you are actively tuning cache behaviour.
php artisan arkhe:watcher:install # publish, migrate, wire the menu, grant access
php artisan arkhe:watcher:prune # delete entries past retention (scheduled daily)
php artisan arkhe:watcher:prune --hours=6
php artisan arkhe:watcher:clear # truncate everything (--force to skip the prompt)
php artisan arkhe:watcher:issues # report N+1 / missing-index issues (--kind=, --hours=)
php artisan arkhe:watcher:analyze <uuid> # AI diagnosis + fix for an issue (needs watcher.ai)
The daily prune is scheduled automatically (watcher.prune.schedule, default
0 2 * * *); it only runs if your app runs schedule:run.
use Arkhe\Watcher\Watch;
Watch::pause(); // stop recording
Watch::resume();
Watch::withoutRecording(fn () => /* noisy bulk op */);
laraowl is a standalone monitoring server with a client agent. Arkhè Watcher is the single-app model, so a few laraowl features that fundamentally need external infrastructure are intentionally out of scope: uptime/heartbeat checks from an outside vantage point, the WAF/firewall threat engine, multi-tenant teams/projects, and real-time WebSocket streaming (replaced by lightweight polling). Everything that observes the app itself is covered.
composer install
composer test
The suite covers the recording engine, every watcher, pruning, the access gate
and a full Flux render of all nine dashboard pages. tests/bootstrap.php falls
back to the sibling arkhe-main package's vendor/ when this package has not
been installed standalone, so the suite runs inside the monorepo without a
second composer install.
MIT. See LICENSE.
How can I help you explore Laravel packages today?