import asyncio import contextlib import os import aiofiles import orjson from environs import Env env = Env() env.read_env() unit_log_file: str = env.str("UNIT_LOG_FILE") async def process_log_entry(log_entry): with contextlib.suppress(Exception): log_data = orjson.loads(log_entry) request_time = float(log_data["request_time"]) url = log_data["url"] http_method = log_data["http_method"] key = (url, http_method) return key, {"count": 1, "sum": request_time} return None async def read_and_process_logs(): metrics_data = {} if os.path.exists(unit_log_file): async with aiofiles.open(unit_log_file, mode="r+") as f: lines = await f.readlines() await f.truncate(0) tasks = [process_log_entry(line) for line in lines] results = await asyncio.gather(*tasks) for result in results: if not result: continue key, data = result if key in metrics_data: metrics_data[key]["count"] += data["count"] metrics_data[key]["sum"] += data["sum"] else: metrics_data[key] = data return metrics_data async def generate_metrics_text(): lines = [ "# HELP request_duration_seconds_total Request duration in seconds", "# TYPE request_duration_seconds_total counter", "# HELP request_duration_seconds_count Number of requests", "# TYPE request_duration_seconds_count counter", ] metrics_data = await read_and_process_logs() for key, data in metrics_data.items(): url, http_method = key count = data["count"] sum_values = data["sum"] labels = f'url="{url}",http_method="{http_method}"' lines.append(f"request_duration_seconds_count{{{labels}}} {count}") lines.append(f"request_duration_seconds_total{{{labels}}} {sum_values}") return "\n".join(lines) + "\n" async def app(scope, receive, send): if scope["type"] == "http": if scope["path"] == "/metrics": headers = [(b"content-type", b"text/plain; charset=utf-8")] metrics_text = await generate_metrics_text() body = metrics_text.encode("utf-8") await send({"type": "http.response.start", "status": 200, "headers": headers}) await send({"type": "http.response.body", "body": body}) else: await send( { "type": "http.response.start", "status": 404, "headers": [(b"content-type", b"text/plain; charset=utf-8")], } ) await send({"type": "http.response.body", "body": b"Not Found"})