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

Metrics Laravel Package

beberlei/metrics

A lightweight PHP metrics library for instrumenting applications with counters, gauges, and timings. Pluggable collectors send measurements to backends like StatsD and Graphite, keeping monitoring code decoupled from your application logic.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Bottom line (revised for v3.0.0): the release materially retires the two strategic objections from the prior assessment — the OpenTelemetry drift and the missing tag/histogram vocabulary — and converts the fail-safe requirement from "we must build a decorator" into a stated contract. What it adds in exchange is a hard PHP 8.4 floor, a hard-break 2.x→3.x upgrade, and final classes that eliminate subclassing as an extension path. Net: the case for adopting it behind an app-owned interface is stronger than before, but the go/no-go now pivots on a single cheap question — is our target runtime on PHP 8.4? If yes, this is a credible primary metrics facade. If we are on 8.2/8.3 with no near-term 8.4 plan, the package is simply unavailable to us and the prior assessment's caution stands by default rather than by judgment.

Architecture fit

  • The Laravel glue story is unchanged and still entirely ours: no service provider, no config file, no facade, no test helper ships. The team invested in a Symfony bundle (autowiring aliases via #[Target], kernel.reset tagging, lazy collector services) rather than a framework-neutral PSR container binding — a signal that Symfony is the first-class integration target and Laravel will remain DIY.
  • CollectorInterface is now strictly typed, void-returning, and tag-native per call (array $tags = []). This resolves the earlier "weak/ambiguous tag support" finding: per-tenant and per-route dimensions are expressible without backend-specific hacks, and timing() preserves fractional milliseconds.
  • final on all collectors and on Factory changes our extension strategy. Decoration-by-inheritance is off the table; composition is the only path. That promotes the app-owned Metrics interface from "highest-leverage nicety" to structural necessity, and it means our wrapper — not the upstream class — is the place we add cardinality guards, sampling, and Laravel-specific context.
  • The OpenTelemetry collector is a bridge, not native OTel. It maps measure/increment/decrement → UpDownCounter, timing → Histogram, gauge → Gauge on a MeterProviderInterface. That is instrument-level mapping only: aggregation, views, resource attributes, exporters, exemplars and span correlation remain the OTel SDK's job and are not supplied here. It removes the "parallel track that must be replaced" objection for the API surface, but does not give us a finished OTel pipeline.
  • Chain collector is quietly valuable for us: it enables dual-write during a backend migration (e.g. StatsD + OTel) without touching call sites. Note the documented asymmetry — gauge() is only forwarded to collectors that support it.
  • Prometheus is now on the maintained promphp client and InfluxDB v1 on influxdb/influxdb-php, replacing two abandoned SDKs. This is a genuine hygiene improvement that reduces our transitive-dependency risk.

Integration feasibility

  • Composer-installable, MIT, psr/log ^3.0. The psr/log floor is compatible with Laravel 10/11/12 but rules out Laravel 9 — check before assuming a clean install across the fleet.
  • PHP >= 8.4 is the gating item and it is not a formality. Laravel's own supported matrix sits below that. Until our runtime is on 8.4, this package cannot be installed at all, and the decision is deferred, not evaluated. Conversely, the old "pinned to an older language floor" concern is fully inverted: the risk is now a high floor that couples us to upstream's language policy.
  • Two carry-over spike items, one now closed:
    1. Closed by documentation: "Collectors never let an error or exception from the underlying client reach the instrumented application" is now an explicit guarantee. Still verify empirically per collector — but the decorator requirement from the prior assessment is downgraded to a verification step.
    2. Still open: how Factory::create() reads configuration. Factory is now final, so we cannot subclass around a getenv()-only implementation; if it ignores config(), config-cached production containers silently resolve to a no-op. Mitigable by constructing collectors explicitly in our provider rather than via the factory, but confirm in the spike.
    3. New: confirm that the AWS CloudWatch and InfluxDB v2 collectors are optional/suggested dependencies. If aws/aws-sdk-php is a hard require, we inherit a large vendor tree into every service for a collector we will not use. This is a one-line composer.json check and a real objection if it fails.
  • No Laravel equivalent of kernel.reset. The Symfony bundle resets collector state at request/message boundaries; Laravel has no such hook. Under Octane or long-lived queue workers, any buffering collector (Telegraf explicitly buffers, DoctrineDBAL flushes) must be reset or flushed by a listener we write. This is now a named, concrete piece of glue, not a generic caution.

Technical risk

  • Maintainer concentration is unchanged in kind but improved in signal: v3.0.0 is a decisive modernization burst — new backends, PHPStan level 8, PHPUnit 13, GitHub Actions replacing Travis, a CI-tested demo stack. The pattern (drop legacy PHP, remove Zabbix/Librato, tighten types, final everything) reads as one maintainer executing a considered roadmap rather than a drive-by. It still ships with no published support or EOL policy, and a release this large is often followed by a quiet period. Treat the next two quarterly reviews as the tell.
  • Exit cost has risen, though it remains low in absolute terms. The prior "vendor/absorb in a day" estimate no longer holds: strict types, per-call tags, and final across the surface make a fork a heavier lift. The app-owned interface is what keeps the exit cost at ~1–2 engineer-days, and it is now doing more work than before.
  • Strategic drift is largely resolved. An OTel path now exists in-package. The residual risk is narrower and easier to state: adopting the OTel collector means we are configuring and operating the OTel PHP SDK ourselves (exporter, batching, resource attributes, views), and a later move to native OTel instrumentation would still change call-site semantics — attributes, baggage, exemplars — even if the backend stays put.
  • Cardinality risk went up, not down. Tags are now cheap and per-call. Nothing in the library constrains user.{id} dimensions, and the OTel and Prometheus paths are both unforgiving. This remains a review-convention and per-service-budget problem that we own entirely.
  • Latency is not covered by the fail-safe guarantee. Swallowing exceptions does not swallow the cost of a network call. CloudWatch PutMetricData over the AWS SDK includes retry/backoff, and Chain multiplies the fan-out. A collector that never throws but occasionally blocks for 800ms is still an availability problem. Async delivery or a hard latency budget is required for any collector on the request path.
  • New upgrade risk for existing 2.x users: a documented but total break (Collector → CollectorInterface, Null class removed, TaggableCollector removed, several bundle service aliases and the XML services file gone, Zabbix/Librato deleted, InfluxDB renamed to InfluxDbV1). The removal of Zabbix and Librato has no replacement — if any team depends on either, that team is stranded on 2.x indefinitely.

Key questions

  • Is the target runtime on PHP 8.4, and if not, when? This single answer determines whether the rest is worth discussing.
  • Do we intend the OpenTelemetry collector as the primary path? If so, who owns the OTel SDK, exporter and resource-attribute configuration — is that in scope here or a separate piece of work?
  • Are aws/aws-sdk-php and influxdata/influxdb-client-php optional in composer.json, or do we drag them into every service?
  • Does anyone in the org currently use Zabbix, Librato, or the DoctrineDBAL collector? The first two have no migration target.
  • Does Factory::create() respect Laravel config()/config:cache, or does it read getenv() directly?
  • Has upstream published a support/EOL policy, and is there a 2.x LTS bridge for teams that cannot reach 8.4?
  • Who owns dashboards and alert thresholds? The shipped demo stack provisions a Grafana dashboard per backend, which lowers the cost of answering this — but does not assign the owner.

Integration Approach

Stack fit

  • Still additive and isolated: one service provider, one config/metrics.php, one facade, one no-op binding for tests and local dev. The difference from the prior plan is that the no-op must now be our own class implementing CollectorInterface — the deprecated Null class is gone, and the "empty config registers a null collector" behaviour is a Symfony-bundle convenience, not a plain-PHP one.
  • Define an app-owned Metrics interface and keep beberlei/metrics strictly behind it. This is now load-bearing rather than merely prudent: final classes mean our wrapper is the only extension point, per-call tags mean we want to normalize tag keys in one place, and the OTel bridge means we want the freedom to swap the instrument mapping without touching call sites.
  • Use Chain deliberately for the migration window only — dual-write to the incumbent and the new backend, then collapse to one. Remember the gauge() forwarding asymmetry when the two paths are compared.

Migration path

  • From 2.x: this is a rewrite of the glue and any custom collectors, not a version bump. There is no bridge package. Budget a dedicated slice, follow UPGRADE.md (which is unusually good — before/after examples per break), and inventory Zabbix/Librato usage first, because those users have nowhere to go.
  • DoctrineDBAL users now have a data migration. The created column must store a full datetime (Y-m-d H:i:s) and measurement must accept floats. This is the first time this package has implied schema work — flag it explicitly if the DBAL collector is in use anywhere.
  • Greenfield: no data migration; sweep any ad-hoc Log::info('counter...') or direct backend SDK calls into the facade opportunistically.
  • Exit path if abandoned: implement our interface against OTel or a backend SDK directly. Budget 1–2 engineer-days — higher than the 2.x estimate because the interface is richer, still small.
  • Backend swap is now cheap in the good case: because the interface sits above the collectors, moving StatsD → OTel is a container binding plus a Chain window, not a code sweep.

Compatibility

  • Gate the whole effort on PHP 8.4 and psr/log ^3.0 against the real fleet matrix, in a container matching production. Test config:cache behaviour specifically, not php artisan serve.
  • Add an Octane and queue-worker check as a named deliverable, not a line item: we must write the reset/flush listener that Symfony's kernel.reset tag gives for free. Verify no stale or unbuffered collector survives between requests.
  • Confirm the no-op binding is genuinely inert so CI and local dev never emit to a shared backend.
  • Verify the fail-safe claim per collector — especially OTel exporter shutdown and CloudWatch SDK retry paths, where "does not throw" and "does not block" are different properties.

Sequencing

  1. PHP 8.4 readiness check. Cheapest possible kill. If we are not on 8.4, stop and revisit at the next runtime upgrade; do not design glue against a package we cannot install.
  2. Backend decision, now with a real choice. OTel is available and is where the industry and (presumably) the org are heading — leaning OTel now avoids a second migration later. Pick agent, endpoint and retention before writing glue.
  3. Two-day spike, re-scoped: install; resolve the config() vs getenv() question; confirm CloudWatch/Influx deps are optional; prove a metric lands end-to-end; prove a dead backend neither throws nor stalls a request; prove Octane request boundaries reset collector state. Gate on the last three.
  4. Glue + conventions: provider, config, facade, no-op fake, an Octane/queue flush listener, and a written metric-naming/tag/cardinality standard.
  5. Pilot 3–5 high-value signals on one service, reusing the shipped Grafana dashboard JSON as a starting point rather than authoring from zero.
  6. Review, then widen. No broad instrumentation until a dashboard someone actually watches exists.

Operational Impact

Maintenance

  • Low ongoing code maintenance; the provider and wrapper are ours and small. The recurring cost remains metrics hygiene — naming, tag cardinality, stale-metric cleanup — which the library still does nothing to enforce, and which per-call tags make easier to get wrong.
  • Upstream health is better than at last review: modern tooling, CI-tested demo stack, an explicit upgrade guide. Counterweight: a demonstrated willingness to take hard breaks, and a PHP floor that tracks the newest language release. We now inherit their runtime cadence as a constraint on ours.
  • Because collectors are final, upstream hotfixes cannot be applied by subclassing. Our wrapper is the patch surface; keep it thin enough to reason about during an incident.
  • Review upstream quarterly. The prior "quiet for >18 months → absorb or replace" trigger still applies, but the absorb step is now more expensive than previously budgeted.

Support

  • Community-only, no vendor SLA, no Laravel documentation. Internal runbook and an owned channel remain required; on-call must know the "metrics degraded, application healthy" play.
  • The shipped examples/ application — every collector wired behind Docker, provisioning a Grafana dashboard per backend, running in CI — is a genuine ramp-up and debugging asset. It is Symfony-shaped, so expect a translation step when reading it against a Laravel provider.
  • Bus factor is mitigated by the interface layer; ensure at least two engineers can explain the wiring and the flush/reset behaviour.

Scaling

  • The OTel path is the best-scaling option here: batching and export are handled by the OTel SDK's exporter rather than per-request pushes, and it aligns with wherever the org's observability spend is going.
  • StatsD/Telegraf remains fire-and-forget and scales with the app; Telegraf now drops buffered metrics after a failed flush, which is the correct behaviour but means a backend blip shows up as a gap, not as errors — alert on ingest silence.
  • CloudWatch PutMetricData is rate-limited and per-request unsuitable at our volume; reserve it for aggregated or batch signals. InfluxDB v2 writes per point — confirm batching before putting it on a hot path.
  • Cardinality is still the binding constraint and it is human. Tags being first-class makes this the area most likely to degrade: enforce naming and tag conventions in review and set a per-service cardinality budget.

Failure modes

  • Backend unreachable → contractually a silent no-op. Verify per collector, and note the residual: errors are swallowed, latency is not. A retrying SDK on the request path can still degrade p99 without a single exception. Move collectors off the hot path or set an explicit timeout/async policy.
  • Misconfigured after config:cache → silent no-op, dashboards empty while the app looks healthy. Unchanged from prior review and now harder to patch around because Factory is final. Mitigate with a boot-time smoke assertion and an alert on ingest silence.
  • Runtime upgrade collision → a future PHP or Laravel bump forces the package version, or vice versa. Track our PHP floor against theirs as a standing dependency.
  • Cardinality explosion → backend-side cost or outage, caught only by review and budgets; more likely now that tags are per-call.
  • Metric or tag-key rename without dashboard update → silent blind spot. Treat metric names and tag keys as an interface with a deprecation window.
  • Octane state leakage → buffered collectors surviving across requests without a reset listener produce corrupted or duplicated data that looks plausible. Caught only by the spike, which is why it is a gate.

Ramp-up

  • The API is self-evident and the demo application shortens the "what should this look like" question considerably; an experienced Laravel developer wires it in under a day.
  • Add roughly a day for the Laravel-specific pieces the bundle does not give us: the no-op fake and the Octane/queue reset listener.
  • Budget 2–3 days of team ramp-up for conventions, the dashboard handoff and "who reads these numbers" — still the real cost, and now larger because tag design is a genuine decision rather than an afterthought.
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.
dakiquang/laravel-oauth-refresh-lock
woda/laravel-worktrees
dalefranks/ramasite
faghy/laracashbook
roundly-consulting/google-places-for-laravel
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
adachsoft/changelog-linter