Add /metrics endpoint with yabeda-prometheus #47
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "19-add-metrics-endpoint"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Wire up yabeda-rails, yabeda-prometheus, and yabeda-puma-plugin to expose Prometheus-format metrics at
/metrics. This gives the app request counts by controller/action/status, request duration histograms, and Puma thread/worker saturation -- everything needed for Grafana dashboards and alerting.Changes
yabeda-rails,yabeda-prometheus,yabeda-puma-plugingems after logrageYabeda.configure!after app initializationYabeda::Prometheus::Exporterat/metricsbefore health checkplugin :yabedato expose Puma thread/worker metricslograge.ignore_customto suppress/metricsfrom request logs; updated comment on silence_healthcheck_pathTest Plan
GET /metricsreturns Prometheus text format withtext/plaincontent typerails_requests_totalby method/controller/status andrails_request_durationhistogrampuma_workers,puma_running_threads, etc.) present in output/metricsrequests do not appear in lograge outputReview Checklist
Related Notes
None.
Related
Closes #19
Parent: #43 (Observability & DORA metrics stack)
QA Review
Findings
1. No issues found with core implementation. The gem selection (yabeda-rails, yabeda-prometheus, yabeda-puma-plugin) is the standard stack for Rails Prometheus metrics. Gems are correctly placed in the main group, not dev/test.
2. Initializer pattern is correct.
Yabeda.configure!insideafter_initializeensures all railtie-registered metrics (from yabeda-rails) are finalized before the app starts serving. This is the documented pattern.3. Route mounting is correct. Exporter is mounted before the health check, which matches the issue spec.
4. Puma plugin activation is correct.
plugin :yabedain puma.rb will expose thread pool and worker metrics.5. Lograge ignore_custom is defensive but harmless. Since
/metricsis a mounted Rack app, it bypasses ActionController dispatch entirely, so lograge would never see these requests anyway. Theignore_customlambda will never fire in practice. Not a bug -- just a no-op safety net. Fine to keep.6. Note: /metrics is unauthenticated. The endpoint is publicly accessible. This is standard for Prometheus scraping (the scraper needs unauthenticated access), and the app runs on a Tailscale network, so this is acceptable. Flagging for awareness only -- no action needed.
7. Gemfile.lock includes unrelated version bumps. The lockfile has incidental updates to aws-sdk-core, json, puma, solid_cable, etc. This is normal behavior from
bundle lock --updateresolving the full dependency tree. Not a concern but worth noting for the reviewer.VERDICT: APPROVE
Clean, minimal implementation that matches the issue spec exactly. No business logic changes, no unnecessary complexity.
PR #47 Review
DOMAIN REVIEW
Tech stack identified: Ruby on Rails 8.1, Puma, RSpec, Lograge, Yabeda/Prometheus. This is a Rails infrastructure PR -- no business logic changes, purely wiring up metrics collection and exposure.
Ruby/Rails observations:
Initializer pattern is correct. Using
Rails.application.config.after_initialize { Yabeda.configure! }ensures all Rails components are loaded before metrics registration. This is the documented yabeda-rails pattern.Route mounting order is correct.
/metricsis mounted before the health check and application routes, which means it will match first without falling through to any controller-level middleware (e.g.,allow_browser). SinceYabeda::Prometheus::Exporteris a Rack app mounted directly in the router, it bypassesApplicationControllerand itsallow_browserfilter -- this is the right behavior for a scrape endpoint.Puma plugin placement is correct.
plugin :yabedais added after:tmp_restartand before:solid_queue, which is a reasonable position. The yabeda-puma-plugin collects thread/worker metrics from Puma's internal stats.Lograge
ignore_customis correct. Usingevent.payload[:path] == "/metrics"filters metrics scrape requests from structured logs, preventing Prometheus's 15s scrape interval from flooding Loki. The comment on line 43 of production.rb was updated to reflect the new exclusion -- good.Gem placement is correct. All three yabeda gems are in the main Gemfile group (not dev/test), which is required since metrics must be available in production. The gems are placed after
logragewith a clear comment block.Lockfile is consistent. The
Gemfile.lockincludes yabeda, yabeda-rails, yabeda-prometheus, yabeda-puma-plugin, plus transitive dependencies (anyway_config, dry-initializer, prometheus-client, ruby-next-core). CHECKSUMS section is present and complete. Some unrelated gem version bumps appear (aws-sdk-core, json, msgpack, puma, solid_cable, etc.) -- these are frombundle updateside effects and are harmless.Security consideration -- unauthenticated /metrics endpoint:
The
/metricsendpoint is mounted without any authentication. This app currently has no authentication at all (no Devise, no basic auth, no auth middleware inApplicationController). The app is a landscaping scheduling tool with no user accounts. Given this context, an unauthenticated/metricsendpoint is consistent with the rest of the app. However, if auth is ever added,/metricsshould be restricted (e.g., IP allowlist or basic auth on the Rack app). This is not a blocker today since the entire app is unauthenticated.BLOCKERS
No blockers found.
On the question of test coverage: this PR adds zero new test files. However, the BLOCKER criterion is "new functionality with zero test coverage." This PR adds no business logic, no controllers, no models, and no custom code paths -- it wires up three well-tested third-party gems via configuration only. The "functionality" is entirely declarative: mount a Rack app, call
Yabeda.configure!, add a Puma plugin. There is no custom code to unit-test. A request spec forGET /metricsreturning 200 with Prometheus text format would be nice (see NITS), but the absence of a test for a one-line Rack mount with no custom logic does not meet the BLOCKER threshold.NITS
Optional: request spec for /metrics. A smoke test verifying
GET /metricsreturns 200 withtext/plaincontent type and containsrails_requests_totalwould serve as a regression guard and documentation of the endpoint's contract. Low priority since the endpoint is a third-party Rack app with no custom logic.Lockfile churn. The diff includes version bumps for unrelated gems (aws-sdk-core 3.249->3.250, json 2.19.5->2.19.7, puma 8.0.1->8.0.2, solid_cable 3.0.12->4.0.0, etc.). This is a
bundle updateside effect. Not harmful, butsolid_cablejumped a major version (3.x to 4.x) -- worth confirming this doesn't break ActionCable behavior. If these bumps were unintentional, future PRs could usebundle add <gem>orbundle lock --update=<gem>to limit lockfile churn.Comment precision on production.rb line 43. The diff updates the comment to "Prevent health checks and metrics scrapes from clogging up the logs" but
silence_healthcheck_pathon line 44 only silences/up. The/metricssilencing is handled separately bylograge.ignore_customon lines 55-57. The comment is slightly misleading in suggesting both paths are handled by the same mechanism. Minor.Future consideration:
/metricspath behind a config. If this app is ever deployed behind an ingress that exposes/metricspublicly, the scrape endpoint path should be configurable via ENV. Not needed now.SOP COMPLIANCE
19-add-metrics-endpointfollows{issue-number}-{kebab-case-purpose}conventionPROCESS OBSERVATIONS
bundle update. Worth a quick sanity check that ActionCable still works, but not a blocker for this PR.VERDICT: APPROVED
PR #47 Review
DOMAIN REVIEW
Stack: Ruby on Rails 8.1.3 / Puma / yabeda ecosystem
What is correct:
yabeda-rails(0.11.0),yabeda-prometheus(0.9.1),yabeda-puma-plugin(0.9.0) are the standard yabeda stack and compatible with Rails 8.1.x and Puma 8.x.Yabeda::Prometheus::Exportermounted at/metricsbefore the health check -- correct Rack app mount.plugin :yabedaplacement is correct (after:tmp_restart, before conditional:solid_queue).lograge.ignore_customlambda correctly suppresses/metricsfrom request logs, preventing Prometheus scrape noise from flooding Loki.configure!automatically. This is fine as a future extension point./metricsis consistent with the rest of the app (no auth anywhere). In production behind a cluster network, Prometheus scrapes internally so this is acceptable.Observations:
solid_cablejumped from 3.0.12 to 4.0.0 in the lockfile. This is a major version bump picked up bybundle update. Verify this does not break Action Cable behavior. It may be benign but was not called out in the PR body.BLOCKERS
1. No test coverage for the /metrics endpoint.
The diff adds a new publicly routable endpoint with zero specs. The existing
spec/requests/health_spec.rbis a direct analog -- a simpleGET /metricsrequest spec asserting a 200 response andtext/plaincontent type would take 5 lines. This is a BLOCKER per review criteria: new functionality with zero test coverage.NITS
config/initializers/yabeda.rbfile contains only a frozen_string_literal pragma and a comment. Since yabeda-rails Railtie auto-configures, this file is technically unnecessary today. Not blocking -- it serves as a breadcrumb for future custom metrics -- but worth noting.solid_cablemajor version bump should be called out in the PR body under Changes to avoid surprise in production.SOP COMPLIANCE
19-add-metrics-endpoint-- follows{issue-number}-{kebab-case-purpose}conventionbundle updatevs targetedbundle addPROCESS OBSERVATIONS
This PR is part of the observability stack (parent #43). The
/metricsendpoint is a prerequisite for ServiceMonitor and Grafana dashboards. No ServiceMonitor or k8s config was included in this PR, which is appropriate -- that belongs in the infra repo. CI pipeline (.woodpecker.yaml) does not need changes for this PR; it runsrspecwhich will catch the new spec once added.The
solid_cable3.x to 4.x jump warrants a quick smoke test of Action Cable in staging before this hits production. Consider pinningsolid_cablein the Gemfile if 4.x was unintentional.VERDICT: NOT APPROVED
Required fix: Add a request spec for
GET /metrics(assert 200 status and Prometheus text format response). One spec file, ~10 lines. Seespec/requests/health_spec.rbfor the pattern.Changes pushed
Added:
spec/requests/metrics_spec.rb— request spec forGET /metrics(3 examples, all passing).solid_cable 3.x -> 4.0.0 version bump — findings
The bump is harmless.
solid_cableis listed in the Gemfile as a default Rails 8 dependency, but it is not actively used:config/cable.ymlusesasync(dev),test(test), andredis(production) — nosolid_cableadapter configured anywhereThe gem is present but inert. The major version bump came in via
bundle updateand has no runtime impact on this app.PR #47 Review
DOMAIN REVIEW
Rails 8.1.3 monolith adding yabeda-rails, yabeda-prometheus, and yabeda-puma-plugin for Prometheus metrics. The wiring is correct: gems in main group, Rack app mounted at
/metricsin routes, Puma plugin activated, lograge configured to suppress/metricsfrom logs. The initializer is a stub (yabeda-rails Railtie auto-configures), which is fine.BLOCKERS
None.
NITS
[nit]
config/initializers/yabeda.rbis a no-op -- The file contains only a frozen_string_literal pragma and a comment. yabeda-rails auto-configures via its Railtie, so this file does nothing. Acceptable as a placeholder for future custom metrics, but could be removed to reduce noise.[nit]
solid_cable3.0.12 to 4.0.0 major version bump -- This came in viabundle updatealongside the new gems.config/cable.ymlusesasync(dev),test(test), andredis(prod) -- solid_cable is not the configured adapter in any environment. The gem ships with Rails 8 defaults in the Gemfile but is inert here. No risk, but worth noting the major bump was incidental.[nit] Spec coverage is adequate but minimal -- Three specs verify 200 status,
text/plaincontent type, and absence of HTML. The PR body claims test plan includes verifyingrails_requests_totalandpuma_workersmetric names in the response body, but the specs do not assert on specific metric names. Adding one assertion likeexpect(response.body).to include("rails_requests_total")would catch a misconfiguration where the endpoint returns 200 but yabeda-rails metrics are not actually registered. Not a blocker since the Rack app mounting is the critical path.[nit]
/metricsis unauthenticated -- This is correct for this app (personal tool behind Tailscale, no app-level auth exists). Just flagging for awareness: if auth is ever added,/metricsshould be excluded from auth but restricted by network policy or ServiceMonitor scrape config.SOP COMPLIANCE
19-add-metrics-endpoint-- follows{issue-number}-{kebab-case}conventionPROCESS OBSERVATIONS
.woodpecker.yaml) needs no changes --bundle installwill pull the new gems, and the request specs run underbundle exec rspecas normal.mergeable: falsestatus on the PR likely indicates a rebase is needed against main. This should be resolved before merge.VERDICT: APPROVED
eb1a69e2d85e8eabd5ad