File: statsd.py

package info (click to toggle)
python-urllib3 2.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,340 kB
  • sloc: python: 26,167; makefile: 122; javascript: 92; sh: 11
file content (95 lines) | stat: -rw-r--r-- 3,832 bytes parent folder | download | duplicates (2)
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
from __future__ import annotations

from typing import Any, TYPE_CHECKING

from .logging import Logger

if TYPE_CHECKING:
    from .config import Config
    from .typing import ResponseSummary, WWWScope

METRIC_VAR = "metric"
VALUE_VAR = "value"
MTYPE_VAR = "mtype"
GAUGE_TYPE = "gauge"
COUNTER_TYPE = "counter"
HISTOGRAM_TYPE = "histogram"


class StatsdLogger(Logger):
    def __init__(self, config: "Config") -> None:
        super().__init__(config)
        self.dogstatsd_tags = config.dogstatsd_tags
        self.prefix = config.statsd_prefix
        if len(self.prefix) and self.prefix[-1] != ".":
            self.prefix += "."

    async def critical(self, message: str, *args: Any, **kwargs: Any) -> None:
        await super().critical(message, *args, **kwargs)
        await self.increment("hypercorn.log.critical", 1)

    async def error(self, message: str, *args: Any, **kwargs: Any) -> None:
        await super().error(message, *args, **kwargs)
        await self.increment("hypercorn.log.error", 1)

    async def warning(self, message: str, *args: Any, **kwargs: Any) -> None:
        await super().warning(message, *args, **kwargs)
        await self.increment("hypercorn.log.warning", 1)

    async def info(self, message: str, *args: Any, **kwargs: Any) -> None:
        await super().info(message, *args, **kwargs)

    async def debug(self, message: str, *args: Any, **kwargs: Any) -> None:
        await super().debug(message, *args, **kwargs)

    async def exception(self, message: str, *args: Any, **kwargs: Any) -> None:
        await super().exception(message, *args, **kwargs)
        await self.increment("hypercorn.log.exception", 1)

    async def log(self, level: int, message: str, *args: Any, **kwargs: Any) -> None:
        try:
            extra = kwargs.get("extra", None)
            if extra is not None:
                metric = extra.get(METRIC_VAR, None)
                value = extra.get(VALUE_VAR, None)
                type_ = extra.get(MTYPE_VAR, None)
                if metric and value and type_:
                    if type_ == GAUGE_TYPE:
                        await self.gauge(metric, value)
                    elif type_ == COUNTER_TYPE:
                        await self.increment(metric, value)
                    elif type_ == HISTOGRAM_TYPE:
                        await self.histogram(metric, value)

            if message:
                await super().log(level, message, *args, **kwargs)
        except Exception:
            await super().warning("Failed to log to statsd", exc_info=True)

    async def access(
        self, request: "WWWScope", response: "ResponseSummary", request_time: float
    ) -> None:
        await super().access(request, response, request_time)
        await self.histogram("hypercorn.request.duration", request_time * 1_000)
        await self.increment("hypercorn.requests", 1)
        await self.increment(f"hypercorn.request.status.{response['status']}", 1)

    async def gauge(self, name: str, value: int) -> None:
        await self._send(f"{self.prefix}{name}:{value}|g")

    async def increment(self, name: str, value: int, sampling_rate: float = 1.0) -> None:
        await self._send(f"{self.prefix}{name}:{value}|c|@{sampling_rate}")

    async def decrement(self, name: str, value: int, sampling_rate: float = 1.0) -> None:
        await self._send(f"{self.prefix}{name}:-{value}|c|@{sampling_rate}")

    async def histogram(self, name: str, value: float) -> None:
        await self._send(f"{self.prefix}{name}:{value}|ms")

    async def _send(self, message: str) -> None:
        if self.dogstatsd_tags:
            message = f"{message}|#{self.dogstatsd_tags}"
        await self._socket_send(message.encode("ascii"))

    async def _socket_send(self, message: bytes) -> None:
        raise NotImplementedError()