Compare commits
1 commit
main
...
feat/metri
| Author | SHA1 | Date | |
|---|---|---|---|
| 648640e1c8 |
2 changed files with 84 additions and 1 deletions
28
api.py
28
api.py
|
|
@ -198,6 +198,7 @@ app.add_middleware(
|
|||
free_paths=[
|
||||
"/health",
|
||||
"/live",
|
||||
"/metrics",
|
||||
"/ready",
|
||||
"/docs",
|
||||
"/openapi.json",
|
||||
|
|
@ -249,7 +250,7 @@ def add_error_handlers(app: FastAPI) -> None:
|
|||
add_error_handlers(app)
|
||||
|
||||
# Public paths that don't need authentication
|
||||
_AUTH_PUBLIC_PATHS: set[str] = {"/health", "/live", "/ready", "/docs", "/openapi.json"}
|
||||
_AUTH_PUBLIC_PATHS: set[str] = {"/health", "/live", "/metrics", "/ready", "/docs", "/openapi.json"}
|
||||
|
||||
|
||||
# Middleware: auth + rate limit + timing + request logging
|
||||
|
|
@ -387,6 +388,31 @@ class PryHttpMiddleware:
|
|||
|
||||
app.add_middleware(PryHttpMiddleware)
|
||||
|
||||
# ── Metrics ──
|
||||
|
||||
|
||||
@app.get("/metrics", tags=["System"], summary="Prometheus metrics endpoint")
|
||||
async def metrics() -> Response:
|
||||
"""Export Prometheus metrics.
|
||||
|
||||
When prometheus_client is installed, returns the native Prometheus
|
||||
text exposition format. Otherwise returns a JSON fallback with the
|
||||
current counter values.
|
||||
"""
|
||||
from observability import _has_prometheus, get_metrics_output
|
||||
|
||||
if _has_prometheus:
|
||||
body, content_type = get_metrics_output()
|
||||
return Response(content=body, media_type=content_type)
|
||||
return JSONResponse(
|
||||
content={
|
||||
"prometheus_available": False,
|
||||
"message": "Install prometheus-client for full metrics",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
57
tests/test_metrics.py
Normal file
57
tests/test_metrics.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
# Copyright (c) 2026 Rug Munch Media LLC
|
||||
#
|
||||
# Part of Pry - https://git.rugmunch.io/RugMunchMedia/pryscraper
|
||||
# Licensed under MIT. See LICENSE.
|
||||
"""Tests for the /metrics endpoint and Prometheus instrumentation."""
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from api import app
|
||||
from observability import SCRAPE_COUNT, track_scrape
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client() -> TestClient:
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_metrics_returns_200(client: TestClient) -> None:
|
||||
resp = client.get("/metrics")
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
def test_metrics_content_type_prometheus(client: TestClient) -> None:
|
||||
resp = client.get("/metrics")
|
||||
assert resp.headers.get("content-type", "").startswith("text/plain")
|
||||
|
||||
|
||||
def test_metrics_contains_expected_metric_names(client: TestClient) -> None:
|
||||
"""Verify HELP lines for pry_requests_total and pry_scrapes_total appear."""
|
||||
resp = client.get("/metrics")
|
||||
assert "# HELP pry_requests_total" in resp.text
|
||||
assert "# HELP pry_scrapes_total" in resp.text
|
||||
|
||||
|
||||
def test_metrics_contains_scrapes_counter_after_track(client: TestClient) -> None:
|
||||
with track_scrape(method="test_meth"):
|
||||
pass
|
||||
resp = client.get("/metrics")
|
||||
assert "pry_scrapes_total" in resp.text
|
||||
|
||||
|
||||
def test_scrape_counters_increment_after_track() -> None:
|
||||
if SCRAPE_COUNT is None:
|
||||
pytest.skip("prometheus_client not available")
|
||||
|
||||
with track_scrape(method="test_metrics_inc"):
|
||||
pass
|
||||
|
||||
found = False
|
||||
for sample in SCRAPE_COUNT.collect():
|
||||
for s in sample.samples:
|
||||
if s.labels.get("method") == "test_metrics_inc":
|
||||
assert s.value >= 1
|
||||
found = True
|
||||
assert found
|
||||
Loading…
Add table
Add a link
Reference in a new issue