Skip to content

Commit 9a1cfe2

Browse files
committed
refactor: decouple Prometheus metrics endpoint into a separate server
1 parent 98e1d83 commit 9a1cfe2

File tree

8 files changed

+82
-58
lines changed

8 files changed

+82
-58
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ repos:
117117
'fastapi[standard]>=0.109.1',
118118
httpx,
119119
pathspec>=0.12.1,
120+
prometheus-client,
120121
pydantic,
121122
pytest-asyncio,
122123
pytest-mock,
@@ -138,6 +139,7 @@ repos:
138139
'fastapi[standard]>=0.109.1',
139140
httpx,
140141
pathspec>=0.12.1,
142+
prometheus-client,
141143
pydantic,
142144
pytest-asyncio,
143145
pytest-mock,

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@ RUN set -eux; \
4343
USER appuser
4444

4545
EXPOSE 8000
46+
EXPOSE 9090
4647
CMD ["python", "-m", "uvicorn", "server.main:app", "--host", "0.0.0.0", "--port", "8000"]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ click>=8.0.0
22
fastapi[standard]>=0.109.1 # Vulnerable to https://osv.dev/vulnerability/PYSEC-2024-38
33
httpx
44
pathspec>=0.12.1
5+
prometheus-client
56
pydantic
67
python-dotenv
78
slowapi
89
starlette>=0.40.0 # Vulnerable to https://osv.dev/vulnerability/GHSA-f96h-pmfr-66vw
910
tiktoken>=0.7.0 # Support for o200k_base encoding
1011
uvicorn>=0.11.7 # Vulnerable to https://osv.dev/vulnerability/PYSEC-2020-150
11-
prometheus-client

src/server/main.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import os
6+
import threading
67
from pathlib import Path
78

89
from dotenv import load_dotenv
@@ -12,7 +13,8 @@
1213
from slowapi.errors import RateLimitExceeded
1314
from starlette.middleware.trustedhost import TrustedHostMiddleware
1415

15-
from server.routers import dynamic, index, ingest, metrics
16+
from server.metrics_server import start_metrics_server
17+
from server.routers import dynamic, index, ingest
1618
from server.server_config import templates
1719
from server.server_utils import lifespan, limiter, rate_limit_exception_handler
1820

@@ -26,6 +28,17 @@
2628
# Register the custom exception handler for rate limits
2729
app.add_exception_handler(RateLimitExceeded, rate_limit_exception_handler)
2830

31+
# Start metrics server in a separate thread if enabled
32+
if os.getenv("GITINGEST_METRICS_ENABLED", "false").lower() == "true":
33+
metrics_host = os.getenv("GITINGEST_METRICS_HOST", "127.0.0.1")
34+
metrics_port = int(os.getenv("GITINGEST_METRICS_PORT", "9090"))
35+
metrics_thread = threading.Thread(
36+
target=start_metrics_server,
37+
args=(metrics_host, metrics_port),
38+
daemon=True,
39+
)
40+
metrics_thread.start()
41+
2942

3043
# Mount static files dynamically to serve CSS, JS, and other static assets
3144
static_dir = Path(__file__).parent.parent / "static"
@@ -159,8 +172,6 @@ def openapi_json() -> JSONResponse:
159172

160173

161174
# Include routers for modular endpoints
162-
if os.getenv("GITINGEST_PROMETHEUS_TOKEN") is not None:
163-
app.include_router(metrics)
164175
app.include_router(index)
165176
app.include_router(ingest)
166177
app.include_router(dynamic)

src/server/metrics_server.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""Prometheus metrics server running on a separate port."""
2+
3+
import logging
4+
5+
import uvicorn
6+
from fastapi import FastAPI
7+
from fastapi.responses import HTMLResponse
8+
from prometheus_client import REGISTRY, generate_latest
9+
10+
# Create a logger for this module
11+
logger = logging.getLogger(__name__)
12+
13+
# Create a separate FastAPI app for metrics
14+
metrics_app = FastAPI(
15+
title="Gitingest Metrics",
16+
description="Prometheus metrics for Gitingest",
17+
docs_url=None,
18+
redoc_url=None,
19+
)
20+
21+
22+
@metrics_app.get("/metrics")
23+
async def metrics() -> HTMLResponse:
24+
"""Serve Prometheus metrics without authentication.
25+
26+
This endpoint is only accessible from the local network.
27+
28+
Returns
29+
-------
30+
HTMLResponse
31+
Prometheus metrics in text format
32+
33+
"""
34+
return HTMLResponse(
35+
content=generate_latest(REGISTRY),
36+
status_code=200,
37+
media_type="text/plain",
38+
)
39+
40+
41+
def start_metrics_server(host: str = "127.0.0.1", port: int = 9090) -> None:
42+
"""Start the metrics server on a separate port.
43+
44+
Parameters
45+
----------
46+
host : str
47+
The host to bind to (default: 127.0.0.1 for local network only)
48+
port : int
49+
The port to bind to (default: 9090)
50+
51+
Returns
52+
-------
53+
None
54+
55+
"""
56+
logger.info("Starting metrics server on %s:%s", host, port)
57+
uvicorn.run(metrics_app, host=host, port=port)

src/server/routers/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33
from server.routers.dynamic import router as dynamic
44
from server.routers.index import router as index
55
from server.routers.ingest import router as ingest
6-
from server.routers.metrics import router as metrics
76

8-
__all__ = ["dynamic", "index", "ingest", "metrics"]
7+
__all__ = ["dynamic", "index", "ingest"]

src/server/routers/metrics.py

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/server/server_config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
from pathlib import Path
6+
57
from fastapi.templating import Jinja2Templates
68

79
MAX_DISPLAY_SIZE: int = 300_000
@@ -19,4 +21,7 @@
1921
{"name": "ApiAnalytics", "url": "https://github.com/tom-draper/api-analytics"},
2022
]
2123

22-
templates = Jinja2Templates(directory="server/templates")
24+
25+
# Use absolute path to templates directory
26+
templates_dir = Path(__file__).parent / "templates"
27+
templates = Jinja2Templates(directory=templates_dir)

0 commit comments

Comments
 (0)