feat(api): expose application business metrics via RoadRunner - #226
Merged
Conversation
Add application-level Prometheus metrics on top of the RoadRunner metrics
plugin, served on :2112 next to the existing framework metrics.
Metrics:
- app_db_queries_total / app_db_query_duration_seconds - SQL statement
count and latency, fed by a Cycle database logger decorator
- app_auth_logins_total{method,result} and
app_auth_registrations_total{result} - auth outcomes across the
password, Google and passkey flows
- app_users{state} - verified/unverified user counts
- app_users_active{window} - DAU/WAU/MAU derived from users.active_at
- app_charges_created_total{type} / app_charges_deleted_total{type}
- app_wallets_created_total / app_wallets_archived_total
- app_tags_created_total / app_tag_assignments_total
Collectors are declared statically in .rr.yaml (metrics.collect:) so the
plugin registers them once before any worker boots, avoiding a per-worker
declare race. AppMetrics is a thin typed facade over MetricsInterface;
emission failures are swallowed so a telemetry outage can never break a
user request. NullMetrics is bound when APP_ENV=testing.
The DB-derived gauges are not event-driven, so a schedule:work service in
.rr.yaml runs a 5-minute job that recomputes and pushes them. Adds a
non-unique index on users.active_at to keep the active-user COUNT queries
index-backed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GG9BBhSh7sJsKc2PBaacJX
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #226 +/- ##
============================================
+ Coverage 99.68% 99.69% +0.01%
- Complexity 1431 1480 +49
============================================
Files 221 227 +6
Lines 4815 4981 +166
============================================
+ Hits 4800 4966 +166
Misses 15 15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Close the Codecov patch-coverage gaps on the application-metrics change: - AppMetrics: add tests for the swallowed-and-logged failure paths of observe() and set(), matching the existing add() failure test. - NullMetrics: assert every operation is a no-op that never throws. - MetricsBootloader: exercise the MetricsInterface factory closure in both branches (NullMetrics under APP_ENV=testing, SuppressExceptionsMetrics otherwise), and run the registered `refresh-app-user-metrics` scheduler callback end to end against the real repository. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GG9BBhSh7sJsKc2PBaacJX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem statement
The API emitted only RoadRunner framework metrics. There was no visibility into application-level behaviour — auth outcomes, user growth and engagement, transactional activity, or database load — so dashboards and alerts could not be built on anything the product actually does.
Changes
Adds application business metrics on top of the RoadRunner metrics plugin, served on the existing
:2112endpoint.New series:
app_db_queries_totalapp_db_query_duration_secondsapp_auth_logins_totalmethod,resultapp_auth_registrations_totalresultapp_usersstate(verified/unverified)app_users_activewindow(daily/weekly/monthly)app_charges_created_total/app_charges_deleted_totaltypeapp_wallets_created_total/app_wallets_archived_totalapp_tags_created_totalapp_tag_assignments_totalImplementation notes:
.rr.yaml(metrics.collect:) — the plugin registers them once before workers boot, so there is no per-workerdeclare()race flooding the logs.AppMetricsis a thin typed facade overMetricsInterface(App\Service\Metrics). Every emission is wrapped so a metrics outage cannot break a user request;MetricsCollectConfigTestfails the build on any drift betweenAppMetrics::definitions()and the YAML.NullMetricsis bound whenAPP_ENV=testing(no RR RPC relay in tests).Cycle\Database\LoggerFactoryInterfacedecorator that reads only theelapsedvalue — no SQL text is stored.app_users/app_users_activeare not event-driven, so aschedule:workserviceblock in.rr.yamlrunsAppUserMetricsRefresherevery 5 minutes to recompute and push them.users.active_at(migration + entity annotation) keeps the active-userCOUNTqueries index-backed.Emission points wired into the four Auth controllers,
GoogleAuthService,ChargeWalletService,WalletService, andTagService.Verification
metricsv5.1.4 and Spiralroadrunner-metricssources that the implementation uses the documented approach (staticcollect:+MetricsInterfaceadd/set/observe) and not a custom transport.composer checks— new suites green:tests/Unit/Service/Metrics(25),tests/Feature/Metrics(18),UserRepositoryTest(3).phpcs(PSR-12) andpsalm(strict) clean. The only failures are three pre-existing, unrelated tests (JwksControllerTest×2,InternalHeadersMiddlewareTest), reconfirmed by reproducing them against a clean checkout.rr serve: zero collector-declare warnings at boot;schedule:runpopulatesapp_users/app_users_activeend to end;/metricson:2112exposes all twelve series.Note for the deploy
compose.app.ymlcaps the api container atmem_limit: 768mwith 6 HTTP workers budgeted. This change adds a residentschedule:workprocess plus a transient per-minuteschedule:runkernel boot. Worth watching OOM headroom on first deploy, or raising the limit pre-emptively.