roundly-consulting/metrics-for-laravel
Metrics for Laravel offers a fluent, lightweight way to define and record application metrics, then inspect trends through dashboards and storage integrations. Track business and usage data without adding heavy monitoring overhead.
Metrics::trend()->range('7d')->daily() makes it cheap to answer "is this metric trending the right way?" during sprint reviews, beta readouts, and post-launch checks — the kind of question that otherwise turns into an ad-hoc SQL request.compareTo() and withChangeAgainstPreviousPeriod() support period-over-period comparisons (week-over-week adoption, cohort deltas), which maps directly to ship/no-ship and rollout-percentage decisions.target() and shouldBeAvoided() encode success criteria and anti-metrics in code, so the definition of "good" lives with the product surface rather than in a slide deck.prefix(), suffix(), precision(), name(), description(), and timezone handling mean the numbers shown to stakeholders are formatted and scoped the same way everywhere — useful when a metric appears in an admin panel, a customer-facing report, and an executive summary.Metrics::fake()->register(...) lets the team assert metric definitions and registration in the test suite, which is a governance win: metrics become code-reviewed artifacts rather than dashboards nobody owns.Adopt when:
static rather than self, so chains no longer degrade to the abstract Metric/Trend/Value/Progress base type. PHPStan/Psalm and IDE autocompletion now follow the full chain, and custom metric subclasses keep their own type after ->name() or ->range(). Previously Metrics::trend()->range('7d')->daily() failed static analysis with "undefined method Metric::daily()" — a real friction cost if your CI enforces static analysis.Look elsewhere when:
Migration note for 1.1.2: this is a patch release but carries a narrow breaking change for anyone who subclasses the metric types. Any override of range(), timezone(), name(), description(), prefix(), suffix(), precision(), the unit setters (daily() and siblings), compareTo(), withChangeAgainstPreviousPeriod(), target(), shouldBeAvoided(), or the manager's register()/unregister() that declares : self must be changed to : static. Budget a short audit of custom metric classes before upgrading; the payoff is that Metrics::fake()->register(...) stays a MetricsFake and chained calls survive static analysis.
For executives: this is infrastructure for answering "how is the product doing?" in code rather than in spreadsheets. It lets us define the handful of numbers that matter — adoption, conversion, progress against target, change versus last period — once, in the same repository and review process as the features themselves. That means metrics get reviewed, tested, and versioned like any other product decision, and the numbers on our dashboards can be traced back to a reviewed definition instead of someone's ad-hoc query.
For engineering: a small, framework-native package with a fluent, strongly typed API for defining metrics (trend, value, progress) with ranges, timezones, units, formatting, targets, and period-over-period comparison. Release 1.1.2 fixes the fluent setter return types to static, which resolves the static-analysis failures that made chained calls (Metrics::trend()->range('7d')->daily()) and custom metric subclasses awkward under PHPStan/Psalm — chains now retain their concrete type and IDE completion works end to end. Test support via Metrics::fake() is preserved. One caveat: any local override of these setters declared with : self must become : static; a small, mechanical change we should make in the same PR as the upgrade.
Why now, why us: low integration cost, no vendor contract, no new datastore. The main risk is query load on the primary database if metric reads are frequent or unbounded — worth a short spike on caching/read-replica strategy before we put any of this in front of customers. Recommended next step: define three to five candidate metrics for the next roadmap cycle, ship them behind the admin surface, and validate that the definitions are useful before expanding.
How can I help you explore Laravel packages today?