victorstochero/warden
最新稳定版本:v0.1.0
Composer 安装命令:
composer require victorstochero/warden
包简介
Self-hosted APM for Laravel with zero external dependencies. Parent/child observability over native Laravel events, stored in your existing RDBMS.
README 文档
README
Zero external dependencies. Parent/child observability built entirely on native Laravel events, stored in the relational database you already run (MySQL / MariaDB / PostgreSQL).
Warden is a single installable package that gives you full application-performance coverage — requests, queries, jobs, exceptions, logs, mail, notifications, cache, commands, scheduled tasks, outbound HTTP, users and host metrics — with correlated traces, exception grouping into issues, aggregated dashboards and internal alerting. No SaaS, no third-party agent, no external service.
Why Warden
You already have great tools for one app: Telescope (local debugging), Pulse (in-app production metrics) and SaaS suites like Sentry / Flare (powerful, but paid and off-premise). The gap nobody fills well is a single self-hosted panel for your whole fleet of Laravel apps — no SaaS account, no agent, no external service, and zero runtime dependencies (no build step, nothing outside Laravel core).
That's Warden: run one parent, point every app at it, and watch the entire fleet from one place — stored in the database you already operate.
Screenshots
The parent's self-hosted dashboard (Blade + Tailwind, no build step):
| Fleet overview | Project dashboard |
|---|---|
![]() |
![]() |
| Trace timeline (N+1 flagged) | Issues |
![]() |
![]() |
How it works
One app runs as the parent (ingests, stores, aggregates, exposes read contracts). Every other app runs as a child (observes its own lifecycle via native Laravel events and ships batches to the parent). Capture is fully decoupled from delivery:
request lifecycle ──> in-memory buffer ──(terminate)──> wdn_outbox ──(warden:ship daemon)──> parent /ingest
The request path never does network I/O or heavy serialization. If the parent is offline the outbox accumulates and drains later — the host app never breaks (RNF-2).
Getting started
The mental model first: you run one parent app (it collects the data and shows the
dashboard) and one or more children (the apps you want to observe). The same package
powers both — warden:install just writes WARDEN_MODE=parent or =child to that app's
.env, and the rest of the package reads that flag to decide how to behave.
Setup is two parts: A — stand up the parent once; B — connect each child. Every
warden:install run publishes the config + migrations and runs migrate for you, and the
child form even writes the credentials into the child's .env — so there are no .env files
to hand-edit except the parent's dashboard login (Part A, step 2).
Part A — Set up the parent (once)
A1. Install the package in parent mode
composer require victorstochero/warden
php artisan warden:install --parent # publishes config + migrations, migrates, writes WARDEN_MODE=parent
This boots the app in parent mode and auto-registers the maintenance schedule
(aggregate / evaluate / partition / prune). It does not set up dashboard
access — that's the next step.
A2. Open the dashboard login (required outside local)
The dashboard lives at https://apm.example.com/warden. Important: out of the box,
when no login is configured Warden locks the dashboard to the local environment only — so
on a real server you'll get denied until you pick an auth mode. The simplest is a built-in
password (no host users, no code), set in the parent's .env:
WARDEN_DASHBOARD_AUTH=password WARDEN_DASHBOARD_PASSWORD=choose-a-strong-view-password WARDEN_DASHBOARD_ADMIN_PASSWORD=choose-a-strong-admin-password # optional: grants "manage" rights
WARDEN_DASHBOARD_PASSWORD grants read access; WARDEN_DASHBOARD_ADMIN_PASSWORD grants
management (creating projects, rotating secrets, running maintenance). If you set only the
first, any successful login is treated as admin. Prefer logging in with your app's own users,
or wiring custom gates? See Dashboard access for the email and gate
modes — but password is the fastest way to get in.
A3. Make sure the scheduler cron is running
The maintenance schedule from A1 only fires if Laravel's scheduler is running. One cron line on the parent (Forge adds this for you by default):
* * * * * cd /path && php artisan schedule:run >> /dev/null 2>&1
The parent is now live. Log in, and continue to Part B to start sending it data.
Part B — Connect each child
Repeat this for every app you want to observe.
B1. Create a project on the parent (mints the credentials)
Each child authenticates with its own token + signing secret. Create a project to mint them — either in the dashboard (Manage projects → New, which shows a ready-to-run install command; the secret is shown only once) or from the parent's CLI:
php artisan warden:project "My App" # scheduler delivery (default) php artisan warden:project "My App" --delivery=daemon # for high-volume children
B2. Run install on the child
Paste the command from B1 into the child app (or into your Forge deploy script — it's fully non-interactive). Note this runs in the child's project, not the parent's:
php artisan warden:install --child \ --parent-url=https://apm.example.com \ --project=my-app \ --token=Yz3... \ --secret=9aF...
That single command publishes + migrates and writes the four credentials into the child's
.env for you (WARDEN_PARENT_URL, WARDEN_PROJECT, WARDEN_TOKEN, WARDEN_SECRET). With
the default scheduler delivery it also auto-registers warden:ship --once to run every
minute — so as long as that child's scheduler cron is running, nothing else is needed.
High volume? Create the project with
--delivery=daemon(or setWARDEN_DELIVERY=daemonin the child's.env) and supervisephp artisan warden:shipunder Supervisor / a Forge Daemon for near-real-time delivery instead of once-a-minute.
B3. Verify it's working
Generate some traffic on the child (load a page, run a job). Within a minute the project lights
up on the parent's overview, with traces, slow queries, issues and host metrics. If nothing
appears, check that the child's scheduler cron is running and that WARDEN_PARENT_URL points
at the parent over HTTPS.
Tuning knobs (most common)
WARDEN_SAMPLE_REQUEST=1.0 # keep 100% of request traces (lower for high volume) WARDEN_ALWAYS_KEEP_MS=1000 # always keep traces slower than this, regardless of sampling WARDEN_RAW_RETENTION_DAYS=7 # how long raw events live WARDEN_AGG_RETENTION_DAYS=90 # how long aggregates live WARDEN_DELIVERY=scheduler # scheduler (cron) or daemon (supervised warden:ship)
Disable a noisy recorder entirely, or sample a category, in config/warden.php
(child.recorders and child.sample.type_gate).
Commands
| Command | Mode | What it does |
|---|---|---|
warden:install --parent|--child |
both | Write .env, publish config + migrations, migrate |
warden:project {name} |
parent | Create a project (mints token + secret); --list to list |
warden:ship |
child | Drain the outbox and ship batches (daemon; --once for the scheduler) |
warden:aggregate |
parent | Roll raw events into aggregates + group exceptions into issues |
warden:evaluate |
parent | Evaluate heartbeats/issues, open/resolve incidents, fire alerts |
warden:partition |
parent | Ensure/pre-create wdn_events partitions (MySQL) |
warden:prune |
parent | Apply retention (drop old raw events + aggregates) |
warden:audit |
child | Run composer audit + npm audit and ship vulnerabilities to the parent |
warden:demo |
child | Generate one of each event type to exercise the pipeline (dev/testing) |
The parent's maintenance schedule and the child's shipping (
schedulerdelivery) are auto-registered by the package — you only need the Laravel scheduler cron running. SetWARDEN_PARENT_SCHEDULE=false/WARDEN_CHILD_SCHEDULE=falseto opt out and wire them by hand.
Dashboard
The parent serves a self-contained dashboard (Blade + a bundled Tailwind stylesheet served locally — no build step, no NPM, no Composer package outside Laravel core) at the route prefix:
https://apm.example.com/warden
It reads exclusively through the read layer (WardenRepository / DashboardRepository) and
covers an overview of all projects (health, throughput, error rate, p95, 30-day uptime),
per-project drill-down (requests, slow queries + N+1, jobs/queues, cache hit rate, schedule +
heartbeats, outgoing HTTP, logs, mail/notifications, host metrics), grouped issues with stack
traces, and a span-waterfall trace viewer. Access is guarded by the viewWarden ability —
define it in a service provider to open it beyond the local environment:
use Illuminate\Support\Facades\Gate; Gate::define('viewWarden', fn ($user) => $user->isAdmin());
Write actions (creating/rotating projects, triggering maintenance commands) are
guarded by a separate manageWarden ability. Define it the same way:
use Illuminate\Support\Facades\Gate; Gate::define('manageWarden', fn ($user) => $user->isAdmin());
Beyond the aggregate views, each section has a drill-down of recent raw events (the actual log message, mail recipient, job error, outgoing URL + status, per-request status…), incidents are clickable with a detail page, KPI cards link to their section, the Logs breakdown filters the list by level, and a per-project timezone controls how absolute timestamps render. A Delivery section shows when batches arrive (so you can see daemon vs. minute-by-minute cron at a glance), and Manage projects lets you reset a project's metrics, set its display timezone, and schedule its security audit. See the wiki for the full tour.
Alerting
Incidents (a dead scheduler, an error spike) fire through internal channels
listed in warden.alerts.channels. By default that's the Database channel
(the incident surfaces in the dashboard) and the Log channel. To also send
e-mail, enable the mail channel and set recipients — it uses the parent app's
own mailer (config/mail.php / your .env SMTP), no separate transport:
WARDEN_ALERT_EMAILS=ops@example.com,oncall@example.com
// config/warden.php — warden.alerts.channels \VictorStochero\Warden\Alerting\Channels\MailAlertChannel::class,
Security audits
A child can audit its own dependencies and surface vulnerabilities on the parent:
php artisan warden:audit # runs composer audit + npm audit, ships a snapshot
The result appears in the project's Security section (counts by severity + the
advisory list). To run it automatically, set a frequency per project under Manage
projects → Audit (hourly / 6h / daily / weekly): the parent advertises "audit due" on
the ingest response and the child's shipper runs warden:audit when it elapses — no
extra cron. A child-side cron (WARDEN_AUDIT_SCHEDULE=true, WARDEN_AUDIT_CRON) is
also available as an alternative.
Scaling & databases
- MySQL / MariaDB:
wdn_eventsis RANGE-partitioned onoccurred_date;warden:prunedrops whole partitions (cheap at any volume). - PostgreSQL / SQLite: a single table pruned with chunked DELETEs — fine for moderate volume; for very high volume prefer MySQL partitioning.
- The parent ingest is a single write path. Past roughly 5–10M events/day on one node, scale the parent's database (faster disk, more IOPS) first.
- High shipping volume? Create the project with
--delivery=daemonand lowerwarden:ship --batchif individual traces are large — the parent rejects a POST overWARDEN_MAX_BODY_BYTESorWARDEN_MAX_EVENTSwith HTTP 413.
Security
Child → parent communication
The ingest channel is authenticated and tamper-evident end to end:
- Per-project token identifies the sender; an inactive project or a wrong token is rejected with 401.
- HMAC-SHA256 signature over the exact request body, compared timing-safe
(
hash_equals). The signing secret is stored encrypted and shown only once. - Anti-replay: the signed body carries a
sent_at; bodies outsideWARDEN_MAX_SKEWseconds are rejected as stale. - Idempotent dedup: each batch carries a
batch_id, so a retried POST is recorded once. - Rate limiting on the ingest route (
WARDEN_INGEST_RATE_LIMIT) plus payload guards (WARDEN_MAX_BODY_BYTES/WARDEN_MAX_EVENTS, HTTP 413). - HTTPS enforcement (optional): set
WARDEN_REQUIRE_HTTPS=trueon the parent to reject any non-TLS ingest (HTTP 403); the child logs a one-time warning ifWARDEN_PARENT_URLis nothttps://. The check honours trusted-proxy headers, so a TLS-terminating proxy still works. Off by default. - Rotate a project's secret any time from Manage projects → Rotate secret.
Reminder: a child needs only warden:install --child plus its .env
(WARDEN_PARENT_URL, WARDEN_PROJECT, WARDEN_TOKEN, WARDEN_SECRET) — no code.
Data redaction
Sensitive keys (warden.child.scrub) are redacted from query bindings, request
input, headers, log context and exception messages; stack-trace file paths
are relativized to the app base path.
Dashboard access
Read access is gated by the viewWarden ability; write actions (managing
projects, triggering maintenance) by a separate manageWarden. Pick the model
from the .env with WARDEN_DASHBOARD_AUTH — no code required:
password— a built-in login form, independent of the host app's users (ideal for a dedicated parent).WARDEN_DASHBOARD_PASSWORDgrants view access; the optionalWARDEN_DASHBOARD_ADMIN_PASSWORDgrants management. With no admin password set, any successful login is treated as admin. Passwords are compared timing-safe.email— uses the host app's authenticated user. An e-mail inWARDEN_DASHBOARD_EMAILSgets view access; one inWARDEN_DASHBOARD_ADMIN_EMAILSgets management (when no admin list is set, the viewer list grants both).gate— advanced: defineviewWarden/manageWardenyourself in a service provider. A host-defined gate always wins over the package defaults.
When WARDEN_DASHBOARD_AUTH is unset it resolves to password if a dashboard
password is configured, otherwise gate (local-only) — the historical default.
Quality
composer test # PHPUnit — acceptance criteria from the spec (§15) + dashboard render composer phpstan # PHPStan at level max (Larastan), green
Static analysis runs at level max with no baseline — zero errors. mixed from
config(), json_decode() and query-builder rows is narrowed at the edges with typed
helpers (Support\Cast, Support\Json) and precise array-shape / generic annotations, so
the type information flows all the way through. PHPStan and Larastan are dev-only — they
don't affect the zero runtime-dependency guarantee.
See config/warden.php for the full configuration surface and
docs/ARCHITECTURE.md for the design.
Roadmap
Warden ships incrementally — each release adds one focused capability. Planned next
(see docs/ROADMAP.md for the full picture and positioning):
- Multilingual dashboard — English, Português, Español.
- Alerts center — e-mail plus Slack / Discord / generic webhook channels, managed from the UI.
- Fleet-wide distributed tracing — one request crossing apps becomes a single trace.
- Release / deploy tracking — "errors since this deploy" and regression detection.
- Real-time dashboard, configurable uptime windows, and a configurable alert-rule engine.
License
MIT.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-10



