[Python] Prometheusのラベル値にオブジェクトを渡すときに注意すること

以前書いた記事ではしれっとHTTPステータスを int に詰め直していたのですが、他のところで実装していたときにハマったのでメモを残しておきます。

def on_finish(self) -> None:
    # レイテンシを記録する
    histogram.labels(
        method=self.request.method,
        uri=self.request.path,
        status=int(self.get_status())  # ここの話です
    ).observe(self.request.request_time())

背景

Python 3.5で HTTPStatus 列挙子が http モジュールに追加されました。
HTTPStatusIntEnum を継承していて、int のサブクラスでもあるため、RequestHandlerget_status()HTTPStatus が返ってくる可能性があります。

status=self.get_status() というコードにしていた場合、prometheus/client_python – metrics.py#L161 で文字列に変換されるときに列挙子のメンバー名に変換されます。
そのため、Prometheus のラベルには HTTP ステータスコードではなく列挙子のメンバー名が設定されてしまいます。

>>> from http import HTTPStatus
>>> str(HTTPStatus.BAD_REQUEST)
'HTTPStatus.BAD_REQUEST'

HTTP ステータスコードにするにはどうするか

これを防ぐには、一般的にはラベルの値に Prometheus に表示したい文字列をちゃんと渡してやる必要があります。

def on_finish(self) -> None:
    # 文字列に変換する
    if isinstance(status, HTTPStatus):
        status = str(self.get_status().value)
    else:
        status = str(self.get_status())

    # レイテンシを記録する
    histogram.labels(
        method=self.request.method,
        uri=self.request.path,
        status=status
    ).observe(self.request.request_time())

今回のケースでは、HTTPStatusint に詰め直してやれば、文字列に変換したときに HTTP ステータスコードになるので、実装をシンプルにできます。

>>> int(HTTPStatus.BAD_REQUEST)
400
>>> str(int(HTTPStatus.BAD_REQUEST))
'400'
def on_finish(self) -> None:
    # レイテンシを記録する
    histogram.labels(
        method=self.request.method,
        uri=self.request.path,
        status=int(self.get_status())
    ).observe(self.request.request_time())

結論

Prometheus のラベル値にオブジェクトを渡す場合は意図しない文字列に変換されないかを確認することが必要です。意図を明確にするには文字列に変換して渡してやるのが無難です。

コメントする