File size: 5,311 Bytes
054900e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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:  # noqa: BLE001
            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