diff --git a/src/om1_utils/httpx/__init__.py b/src/om1_utils/httpx/__init__.py index b24a343..5480802 100644 --- a/src/om1_utils/httpx/__init__.py +++ b/src/om1_utils/httpx/__init__.py @@ -3,6 +3,17 @@ import httpx +from ..prometheus import ( + om1_http_proxy_total_last_seconds, + om1_http_proxy_total_seconds, + om1_http_request_duration_last_seconds, + om1_http_request_duration_seconds, + om1_http_upstream_total_last_seconds, + om1_http_upstream_total_seconds, + om1_http_upstream_ttfb_last_seconds, + om1_http_upstream_ttfb_seconds, +) + def get_httpx_event_hooks() -> dict[str, list]: """ @@ -34,7 +45,14 @@ def log_response(response: httpx.Response): response : httpx.Response The HTTP response object to log. """ - start_time = response.request.extensions.get("start_time", 0) + start_time = response.request.extensions.get("start_time", None) + if start_time is None: + logging.warning( + f"HTTP {response.request.method} {response.request.url} - " + "No start_time recorded, skipping metrics" + ) + return + elapsed = (time.perf_counter() - start_time) * 1000 http_version = response.http_version proxy_parse_total_time = response.headers.get("x-proxy-parse-ms", "?") @@ -52,6 +70,55 @@ def log_response(response: httpx.Response): f"Proxy Total Time: {proxy_total_time} ms" ) + method = response.request.method + status_code = str(response.status_code) + host = str(response.request.url.host) + path = str(response.request.url.path) + elapsed_s = elapsed / 1000.0 + + om1_http_request_duration_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).observe(elapsed_s) + om1_http_request_duration_last_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).set(elapsed_s) + + if upstream_total_time != "?": + try: + val = float(upstream_total_time) / 1000.0 + om1_http_upstream_total_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).observe(val) + om1_http_upstream_total_last_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).set(val) + except ValueError: + pass + + if upstream_ttfb_time != "?": + try: + val = float(upstream_ttfb_time) / 1000.0 + om1_http_upstream_ttfb_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).observe(val) + om1_http_upstream_ttfb_last_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).set(val) + except ValueError: + pass + + if proxy_total_time != "?": + try: + val = float(proxy_total_time) / 1000.0 + om1_http_proxy_total_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).observe(val) + om1_http_proxy_total_last_seconds.labels( + host=host, path=path, method=method, status_code=status_code + ).set(val) + except ValueError: + pass + return { "request": [log_request], "response": [log_response], diff --git a/src/om1_utils/prometheus/__init__.py b/src/om1_utils/prometheus/__init__.py new file mode 100644 index 0000000..81372c1 --- /dev/null +++ b/src/om1_utils/prometheus/__init__.py @@ -0,0 +1,49 @@ +from prometheus_client import Gauge, Histogram + +om1_http_request_duration_seconds = Histogram( + "om1_http_request_duration_seconds", + "Total HTTP request duration (client-side) in seconds", + ["host", "path", "method", "status_code"], +) + +om1_http_upstream_total_seconds = Histogram( + "om1_http_upstream_total_seconds", + "Upstream total time in seconds (from x-upstream-total-ms header)", + ["host", "path", "method", "status_code"], +) + +om1_http_upstream_ttfb_seconds = Histogram( + "om1_http_upstream_ttfb_seconds", + "Upstream TTFB in seconds (from x-upstream-ttfb-ms header)", + ["host", "path", "method", "status_code"], +) + +om1_http_proxy_total_seconds = Histogram( + "om1_http_proxy_total_seconds", + "Proxy total time in seconds (from x-proxy-total-ms header)", + ["host", "path", "method", "status_code"], +) + +om1_http_request_duration_last_seconds = Gauge( + "om1_http_request_duration_last_seconds", + "Most recent HTTP request duration (client-side) in seconds", + ["host", "path", "method", "status_code"], +) + +om1_http_upstream_total_last_seconds = Gauge( + "om1_http_upstream_total_last_seconds", + "Most recent upstream total time in seconds", + ["host", "path", "method", "status_code"], +) + +om1_http_upstream_ttfb_last_seconds = Gauge( + "om1_http_upstream_ttfb_last_seconds", + "Most recent upstream TTFB in seconds", + ["host", "path", "method", "status_code"], +) + +om1_http_proxy_total_last_seconds = Gauge( + "om1_http_proxy_total_last_seconds", + "Most recent proxy total time in seconds", + ["host", "path", "method", "status_code"], +)