| from __future__ import annotations |
| import asyncio |
| from typing import TYPE_CHECKING |
|
|
| import prometheus_client |
| from aiohttp.web_exceptions import HTTPException |
| from aiohttp.web_middlewares import middleware |
|
|
| if TYPE_CHECKING: |
| from aiohttp.typedefs import Handler, Middleware |
| from aiohttp.web_request import Request |
| from aiohttp.web_response import StreamResponse |
|
|
| METRICS_PREFIX = "tgbot" |
|
|
|
|
| def prometheus_middleware_factory( |
| metrics_prefix: str = METRICS_PREFIX, |
| registry: prometheus_client.CollectorRegistry | None = None, |
| ) -> Middleware: |
| used_registry = registry or prometheus_client.REGISTRY |
|
|
| requests_metrics = prometheus_client.Counter( |
| name=f"{metrics_prefix}_requests", |
| documentation="Total requests by method, scheme, remote and path template.", |
| labelnames=["method", "scheme", "remote", "path_template"], |
| registry=used_registry, |
| ) |
|
|
| responses_metrics = prometheus_client.Counter( |
| name=f"{metrics_prefix}_responses", |
| documentation="Total responses by method, scheme, remote, path template and status code.", |
| labelnames=["method", "scheme", "remote", "path_template", "status_code"], |
| registry=used_registry, |
| ) |
|
|
| requests_processing_time_metrics = prometheus_client.Histogram( |
| name=f"{metrics_prefix}_request_duration", |
| documentation="Histogram of requests processing time by method, " |
| "scheme, remote, path template and status code (in seconds)", |
| labelnames=["method", "scheme", "remote", "path_template", "status_code"], |
| unit="seconds", |
| registry=used_registry, |
| ) |
|
|
| requests_in_progress_metrics = prometheus_client.Gauge( |
| name=f"{metrics_prefix}_requests_in_progress", |
| documentation="Gauge of requests by method, scheme, remote and path template currently being processed.", |
| labelnames=["method", "scheme", "remote", "path_template"], |
| registry=used_registry, |
| ) |
|
|
| exceptions_metrics = prometheus_client.Counter( |
| name=f"{metrics_prefix}_exceptions", |
| documentation="Total exceptions raised by path, scheme, remote, path template and exception type.", |
| labelnames=["method", "scheme", "remote", "path_template", "exception_type"], |
| registry=used_registry, |
| ) |
|
|
| @middleware |
| async def prometheus_middleware(request: Request, handler: Handler) -> StreamResponse: |
| loop = asyncio.get_running_loop() or asyncio.get_event_loop() |
|
|
| try: |
| path_template = getattr( |
| getattr( |
| request.match_info.route, |
| "resource", |
| None, |
| ), |
| "canonical", |
| "__not_matched__", |
| ) |
| except AttributeError: |
| path_template = "__not_matched__" |
|
|
| requests_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| ).inc() |
| requests_in_progress_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| ).inc() |
|
|
| request_start_time = loop.time() |
| try: |
| response = await handler(request) |
| request_end_time = loop.time() |
|
|
| except Exception as e: |
| request_end_time = loop.time() |
| status = e.status if isinstance(e, HTTPException) else 500 |
|
|
| responses_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| status_code=status, |
| ).inc() |
| exceptions_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| exception_type=type(e).__name__, |
| ).inc() |
| requests_processing_time_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| status_code=status, |
| ).observe(request_end_time - request_start_time) |
| raise e from None |
| else: |
| responses_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| status_code=response.status, |
| ).inc() |
| requests_processing_time_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| status_code=response.status, |
| ).observe(request_end_time - request_start_time) |
| finally: |
| requests_in_progress_metrics.labels( |
| method=request.method, |
| scheme=request.scheme, |
| remote=request.remote, |
| path_template=path_template, |
| ).dec() |
| return response |
|
|
| return prometheus_middleware |
|
|