EmotionTechテックブログ

株式会社エモーションテックのProduct Teamのメンバーが、日々の取り組みや技術的なことを発信していくブログです。

FastAPIを使い始めたので、ContextVarsでログ内容をカスタマイズするよ

はじめに

こんにちは、エモーションテックのテックリードのかどたみです。

エモーションテックではRustのactix-web、Node.jsのNestJSに続き、マイクロサービスを担う言語としてPythonフレームワークとしてFastAPIを利用しています。

今回はFastAPIを運用に乗せるために設定しているカスタムログについて簡単にまとめてみました。

この記事はエモーションテック Advent Calendar 2023 の6日目の記事です。

採用背景

弊社は顧客や従業員・株主の体験向上をサポートするサービスを提供しています。ソフトウェアのプロダクトだけでなく、対象者それぞれの感情を解析し、統計処理を行った結果をお客様にレポーティングするコンサルティングもサービスの1つです。

そのようなレポートは、様々なデータに対しPythonで処理をして提供しているのですが、もちろんソフトウェアのプロダクトでも分析結果を生成したいのでプロダクトチームのエンジニアとデータサイエンティストが協力して分析用のPythonライブラリも開発しています。

この分析用ライブラリを使うためのAPIをマイクロサービスとして立ち上げるためにPythonAPIサーバーを立てることになりました。

ライブラリの作成に携わったエンジニアがすでに経験していたことと、他のフレームワーク同様軽量であることを理由にFastAPIが選ばれました。今回作成するものはパラメータを受け取って分析結果を返却するだけのAPIなので、DBライブラリなどのサポートは選定基準に入っていません。

使ってみてどう?

RustやTypeScriptに慣れてきたところで使ってみるとやはり型の恩恵がないことは気になりますが、Rustに比べてtestが書きやすい、カバレッジが測りやすい、CIにかかる時間が短いなどのメリットも感じます。

数値処理系や機械学習系のライブラリも豊富なので弊社のサービスとの親和性上使わない選択肢はないですが、しっかり使い所を見極めて行きたいと思います。

ログのカスタマイズ

さて、本題です。NestJSでの対応Rustでの対応と同様にマイクロサービスを運用する上でのログをベストプラクティスを参考にカスタマイズしました。

他のマイクロサービス同様以下の内容を出力したいので、リクエストごとに情報を格納し、どこからでも呼び出せる仕組みを作成します。

  • ログレベルや出力時間、ステータスコードといった基本情報
  • フロントエンドからのリクエストで一意になるID
  • 各マイクロサービスが受け取るリクエストで一意になるID(execution_id)
  • 実行ユーザーの情報
  • 実行開始時刻

ContextVars

ContextVarsは「コンテキストローカルな状態を管理し、保持し、アクセスするための API を提供」してくれる機能です。これを利用してログに必要なデータを管理します。

実装方法

まず、以下のようにContextVarsを定義します。

import contextvars
request_context: contextvars.ContextVar = contextvars.ContextVar("request_context")

これに対し、値をセットするとどこからでもセットした値を参照することが可能です。 リクエストごとにexecution_idを振りたいのでMiddlewareを定義します。

class LoggingApiMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: Any) -> JSONResponse:
        start = time.time()
        # ログで利用したいデータをセットする
        request_context.set(
                {
                "execution_id": str(uuid.uuid4()),
                "started_at": datetime.datetime.fromtimestamp(start).isoformat(),
                "request": request,
                }
        )
        ctx = contextvars.copy_context()
        response = await ctx.run(call_next, request)
        return response

このコードのctx.run(call_next, request)によって実際の処理の中でsetしたcontextが利用できます。

使い方

とっても簡単です。 APIの処理の途中でrequest_contextを呼び出すだけで、リクエストごとに則した値をログに出力することができます。

ctx = request_context.get()
logger.info(ctx["execution_id"])

もちろんカスタマイズしたLoggerの中でも呼び出せるので、ContextVarsに登録されている値を適切に整形して構造化ログに使うこともできます。

さいごに

いかがでしたでしょうか?今回はFastAPIでマイクロサービスを運用する際のログ内容のカスタマイズ方法について紹介しました。運用面でのこのような設定は面倒でもありますが、適切に設定すれば効果は必ず返ってくるはずです。この記事を読んでくださった方の面倒な設定が少しでも楽になれば幸いです。 エモーションテックでは顧客体験、従業員体験の改善をサポートし、世の中の体験を変えるプロダクトを開発しています。プロダクトに興味のある方、Pythonを使ってプロダクト開発したい方、ぜひ採用ページからご応募をお願いいたします。

hrmos.co

hrmos.co