|
1 | 1 | from fastapi import FastAPI, Request |
2 | | -from fastapi.responses import JSONResponse |
| 2 | +from fastapi.responses import JSONResponse, Response |
3 | 3 | import os |
4 | 4 | import platform |
5 | 5 | import socket |
|
9 | 9 | import time |
10 | 10 | import uuid |
11 | 11 | from contextlib import asynccontextmanager |
| 12 | +from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST |
| 13 | +import asyncio |
| 14 | + |
12 | 15 |
|
13 | 16 | HOST = os.getenv('HOST', '0.0.0.0') |
14 | 17 | PORT = int(os.getenv('PORT', 5000)) |
@@ -39,6 +42,77 @@ def format(self, record): |
39 | 42 | app = FastAPI() |
40 | 43 | start_time = datetime.now() |
41 | 44 |
|
| 45 | +http_requests_total = Counter( |
| 46 | + 'http_requests_total', |
| 47 | + 'Total HTTP requests', |
| 48 | + ['method', 'endpoint', 'status'] |
| 49 | +) |
| 50 | + |
| 51 | +http_request_duration_seconds = Histogram( |
| 52 | + 'http_request_duration_seconds', |
| 53 | + 'HTTP request duration', |
| 54 | + ['method', 'endpoint'] |
| 55 | +) |
| 56 | + |
| 57 | +http_requests_in_progress = Gauge( |
| 58 | + 'http_requests_in_progress', |
| 59 | + 'HTTP requests currently being processed' |
| 60 | +) |
| 61 | + |
| 62 | +# Application-specific metrics |
| 63 | + |
| 64 | +uptime_seconds = Gauge( |
| 65 | + 'app_uptime_seconds', |
| 66 | + 'Application uptime in seconds' |
| 67 | +) |
| 68 | + |
| 69 | +endpoint_response_size_bytes = Histogram( |
| 70 | + 'endpoint_response_size_bytes', |
| 71 | + 'Response payload size in bytes', |
| 72 | + ['endpoint'] |
| 73 | +) |
| 74 | + |
| 75 | + |
| 76 | +@app.middleware("http") |
| 77 | +async def dispatch(request, call_next): |
| 78 | + if request.url.path == "/metrics": |
| 79 | + return await call_next(request) |
| 80 | + |
| 81 | + http_requests_in_progress.inc() |
| 82 | + |
| 83 | + start_time = time.time() |
| 84 | + status_code = 500 |
| 85 | + response = None |
| 86 | + try: |
| 87 | + response = await call_next(request) |
| 88 | + status_code = response.status_code |
| 89 | + except Exception as e: |
| 90 | + status_code = 500 |
| 91 | + http_requests_in_progress.dec() |
| 92 | + raise |
| 93 | + finally: |
| 94 | + duration = time.time() - start_time |
| 95 | + |
| 96 | + http_requests_total.labels( |
| 97 | + method=request.method, |
| 98 | + endpoint=request.url.path, |
| 99 | + status=status_code |
| 100 | + ).inc() |
| 101 | + |
| 102 | + http_request_duration_seconds.labels( |
| 103 | + method=request.method, |
| 104 | + endpoint=request.url.path |
| 105 | + ).observe(duration) |
| 106 | + |
| 107 | + if response and hasattr(response, 'body'): |
| 108 | + response_size = len(response.body) |
| 109 | + endpoint_response_size_bytes.labels( |
| 110 | + endpoint=request.url.path).observe(response_size) |
| 111 | + |
| 112 | + http_requests_in_progress.dec() |
| 113 | + |
| 114 | + return response |
| 115 | + |
42 | 116 |
|
43 | 117 | @app.middleware("http") |
44 | 118 | async def log_requests(request: Request, call_next): |
@@ -88,9 +162,25 @@ async def lifespan(app: FastAPI): |
88 | 162 | logger.info("Application starting up", extra={ |
89 | 163 | "extra_info": {"config": startup_config}}) |
90 | 164 |
|
| 165 | + async def update_uptime(): |
| 166 | + while True: |
| 167 | + uptime_seconds.set(get_uptime()['seconds']) |
| 168 | + await asyncio.sleep(5) # Update every 5 seconds |
| 169 | + |
| 170 | + uptime_task = asyncio.create_task(update_uptime()) |
| 171 | + |
91 | 172 | yield |
92 | 173 |
|
93 | 174 | logger.info("Application shutting down") |
| 175 | + uptime_task.cancel() |
| 176 | + |
| 177 | + |
| 178 | +@app.get('/metrics') |
| 179 | +def metrics(): |
| 180 | + return Response( |
| 181 | + generate_latest(), |
| 182 | + media_type=CONTENT_TYPE_LATEST |
| 183 | + ) |
94 | 184 |
|
95 | 185 |
|
96 | 186 | @app.get("/") |
|
0 commit comments