feat: Prometheus instrumentation + business metrics #25
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "24-api-observability"
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
Replace the stub
/metricsendpoint (hardcodedup 1) with real Prometheus instrumentation viaprometheus-fastapi-instrumentator. Adds four custom business counters for tracking receipt uploads, code saves, code redemptions, and nearby queries.Changes
pyproject.toml-- addedprometheus-fastapi-instrumentator>=7.0andprometheus-client>=0.21dependenciessrc/mcd_tracker_api/metrics.py-- new module: instrumentator setup + four business Counter objectssrc/mcd_tracker_api/main.py-- wiresetup_metrics(app)after routers are registeredsrc/mcd_tracker_api/routes/health.py-- removed manual/metricsstub (now handled by instrumentator)src/mcd_tracker_api/routes/receipts.py-- incrementmcd_receipts_uploaded_totalon successful uploadsrc/mcd_tracker_api/routes/locations.py-- incrementmcd_codes_saved_totalon code log,mcd_codes_redeemed_totalon redeemsrc/mcd_tracker_api/routes/nearby.py-- incrementmcd_nearby_queries_totalon each nearby querytests/test_health.py-- updated metrics test to assert real Prometheus format + all four custom countersTest Plan
/metricsreturns Prometheus exposition format with HTTP request metrics/metricsoutputReview Checklist
Related
QA Review -- PR #25
Diff Analysis (8 files, +64/-13)
pyproject.toml -- Two new dependencies added correctly. No version conflicts.
src/mcd_tracker_api/metrics.py (new) -- Clean module structure. Four Counter objects with descriptive help strings. Instrumentator configured with
should_ignore_untemplated=True, excluded_handlers=["/healthz", "/metrics"]matching the pal-e-docs pattern exactly.setup_metrics()function chains.instrument(app).expose(app, include_in_schema=False)correctly.src/mcd_tracker_api/main.py --
setup_metrics(app)called after all routers are registered, which is the correct ordering for the instrumentator to discover all routes.src/mcd_tracker_api/routes/health.py -- Manual
/metricsstub and unusedPlainTextResponseimport cleanly removed./healthzendpoint unchanged.src/mcd_tracker_api/routes/receipts.py --
RECEIPTS_UPLOADED.inc()placed afterdb.commit()+db.refresh()succeeds. Correct -- only counts successful uploads.src/mcd_tracker_api/routes/locations.py --
CODES_SAVED.inc()after commit inlog_code(),CODES_REDEEMED.inc()after commit inredeem_code(). Both placed after DB persistence confirms success.src/mcd_tracker_api/routes/nearby.py --
NEARBY_QUERIES.inc()at the top of the handler (before DB queries). This is intentional -- counts all query attempts, not just successful ones. Appropriate for a query volume metric.tests/test_health.py -- Test renamed from
test_metricstotest_metrics_returns_prometheus_format. Asserts real Prometheus format and all four custom counter names appear in output.Compliance
Findings
No issues found.
VERDICT: APPROVE
PR #25 Review
DOMAIN REVIEW
Tech stack: Python 3.12 / FastAPI / SQLAlchemy / prometheus-client / prometheus-fastapi-instrumentator
Instrumentator configuration -- Follows the pal-e-docs pattern exactly:
setup_metrics(app)is called after all routers are registered inmain.py(line 68), which is the correct ordering -- the instrumentator needs to see the registered routes.Custom counters -- All four use the
_totalsuffix per Prometheus naming conventions for Counter metrics. Help strings are descriptive. Counters are module-level singletons imported by the route modules -- clean pattern, no DRY concerns.Counter placement -- All four
.inc()calls are positioned AFTERdb.commit()+db.refresh()succeeds:RECEIPTS_UPLOADED.inc()inreceipts.py:85-- after commit on line 82CODES_SAVED.inc()inlocations.py:176-- after commit on line 173CODES_REDEEMED.inc()inlocations.py:277-- after commit on line 274NEARBY_QUERIES.inc()innearby.py:227-- at function entry (correct; this is a query counter, not a success counter -- counts intent, not DB mutation)This means counters accurately reflect successful operations for the three mutation endpoints, and query volume for the nearby endpoint. Good design.
Stub removal -- The old hardcoded
/metricsstub inhealth.py(returningmcd_tracker_api_up 1) is cleanly removed. ThePlainTextResponseimport is also removed since it's no longer used.Dependencies --
prometheus-fastapi-instrumentator>=7.0andprometheus-client>=0.21added topyproject.toml. Both are well-maintained packages. Version floors are reasonable.Test coverage --
test_metrics_returns_prometheus_formatintests/test_health.py:http_requestorHELPheader)CI --
.woodpecker.yamlrunsruff check,ruff format --check, andpytestwith a Postgres service container. The newprometheus-*dependencies will be installed viapip install .[dev].BLOCKERS
None.
NITS
Missing type annotation on
setup_metrics(src/mcd_tracker_api/metrics.py:38): The function signature isdef setup_metrics(app):but should bedef setup_metrics(app: FastAPI) -> None:per PEP 484. This would require importingFastAPIfromfastapiin the metrics module. Minor but consistent with the rest of the codebase which uses type hints.Loose assertion for HTTP metrics (
tests/test_health.py:36): The checkassert "http_request" in body or "HELP" in bodyis intentionally broad (any HELP comment would pass), but this is acceptable since the primary purpose of the test is validating the four custom counters are registered. The instrumentator's own test suite covers HTTP metric correctness.SOP COMPLIANCE
24-api-observabilityfor issue #24)Closes #24)PROCESS OBSERVATIONS
Clean, focused PR. 64 additions, 13 deletions across 8 files -- tight scope. The stub-to-real-instrumentation migration is a one-shot change with no intermediate state concerns. All four acceptance criteria from issue #24 are met: instrumentator wired,
/metricsreturns real Prometheus format, four custom business counters present, ServiceMonitor untouched. CI pipeline will validate lint + format + tests on PR event.VERDICT: APPROVED