kirschbaum-development/monitor
Monitor is a Laravel package from Kirschbaum Development that helps you keep tabs on your application's health and performance. It offers a lightweight, configurable way to add monitoring and catch issues before they affect your users or services. Designed for simple setup and integration into exist...
recover), what it guarantees (ensure), how long it may take (within), how many retries it gets (attempts), and who gets paged (escalate). monitor:points --check in CI can fail the build on duplicates, names off the agreed pattern, a point with no escalation and no catch-all, and any class in a critical namespace that isn't a control point. That converts "did we cover the risky paths this release?" from a retro argument into a green/red gate a PM can point at in release sign-off. The SARIF output means the findings land in the existing code-scanning UI rather than a new dashboard nobody opens.once('invoice:'.$id, ttl: 3600)), and escalation ship as one fixed, documented pipeline (breaker → retry → transaction). The fixed order is the actual product decision — a breaker sees one failure per run, so your on-call math isn't inflated by retries. That's a defensible "adopt, don't hand-roll" memo against the try/catch archaeology most services accumulate, and far cheaper than funding a platform squad to rebuild it.recover(CardDeclined::class, fn ($e) => ChargeResult::declined($e->code)) puts "declined cards return a typed result; an outage pages payments on-call" into a diff instead of an implicit catch block. null, false, and 0 are valid handler results, so "show a placeholder avatar" vs. "fail the page" becomes a per-surface choice someone can review and reverse. ensure() runs after every policy and is never retried — the correct semantics for "it technically succeeded but the result is wrong," which is where most money bugs live.Outcome plus eleven typed events plus an optional monitor_outcomes table give you per-point status, attempts, duration, limits breached, and a timeline, queryable by domain. "Which of our critical flows do we actually retry the most, and which ones breach their time budget?" becomes a query, not an anecdote — a legitimate input to quarterly roadmap sequencing (fix the payment path before the notification path). The store is off by default and writes off the request path, so measurement doesn't create a new latency or availability risk.throttleEscalation() caps page volume to one per point per window. That's how a PM defends "we don't want 4,000 Slack pings during a Stripe incident" without arguing about it mid-incident. Http::breaker('stripe') extends the same circuit to raw HTTP calls, so the policy holds even outside a formal control point.list_points/explain_point/outcomes/escalations, a generation command (make:control-point emits the class and a fake-based test), a Pest expectation, and a PHPStan rule all attack the same adoption cost: the second and third engineer declaring a point correctly without a reviewer explaining the convention. This is a real lever on team throughput, not a novelty — but see adoption caveats below.Strong fit when:
monitor.uncontrolled rule and toBeControlled() expectation only pay off if those gates exist.monitor:points), monitor:explain, and the generated tests mean the "why" is machine-readable rather than tribal.Look elsewhere when:
spatie/laravel-health or Laravel's built-in health route.--check gate as a merge blocker, you get a dependency and a half-used framework.once() is a cache-backed TTL idempotency key, not a durable outbox. If correctness depends on guaranteed single execution across failures, you need a different architecture.To executives (one paragraph): A small, MIT-licensed Laravel package that makes our riskiest operations — charging cards, filing with a regulator, calling a third party — declare up front what they tolerate, what they guarantee, how long they may take, and who gets paged. The practical value is a CI gate: we can state, release by release, that every money-moving or promise-breaking path has a declared failure story, instead of discovering gaps in a postmortem. It also replaces hand-rolled retry and circuit-breaker code with one documented pipeline, and records every outcome so hardening work can be prioritised by evidence. No new infrastructure, no per-seat cost. The honest caveat: it is young and thinly adopted, so we adopt it behind a pinned version with a contained blast radius, and we do not treat it as a replacement for error monitoring.
To engineering: One fixed pipeline (breaker → retry → transaction) instead of bespoke try/catch. Two entry points per point — run() returns the value or throws, attempt() returns an immutable Outcome and never throws — so business logic and diagnostics don't fight. Nesting carries parent run id and stack; jobs start clean with dispatched_from_run instead of leaking a foreign parent across processes. Monitor::fake() is a proper framework Fake with positive and negative assertions and canning. make:control-point generates the class and a test that already passes. Events are the source of truth; logs and the DB store are just listeners. Provider does nothing at boot; store flushes after the response, never on the request path. PHPStan rule and Pest expectation mean enforcement is static, not a code-review opinion.
To security / compliance: Context and exception messages pass through Redactor while identifiers are preserved, so records stay joinable without leaking PII; redaction failure degrades and never blocks a control point. The record schema is versioned and published (monitor/1), with origin and domain as first-class fields for audit queries. The MCP server is read-only, off by default, and redacts per response — safe to leave disabled until someone explicitly wants it.
To whoever owns the risk: the ask is a two-week spike on one domain (payments), a pinned version, and one named owner of the --check gate. If the spike doesn't produce cleaner failure semantics than what exists, we drop the dependency — it's a library, not a migration.
How can I help you explore Laravel packages today?