Azure Functions Python DX Toolkit の一部 — azure-functions-cookbook-python によりドッグフーディング検証済み。
Azure Functions Python v2 のための、呼び出し(invocation)認識可能なオブザーバビリティ。
invocation_id を表面化し、コールドスタートを検出し、host.json の設定不備を警告し、Application Insights にすぐに使える構造化ログを出力します — Python 標準の logging を置き換えることなく。
Azure Functions Python DX Toolkit の一部 → FastAPI のような開発体験を Azure Functions にもたらします。
Azure Functions Python のロギングには、汎用的なロギングライブラリでは扱えない固有の失敗モードがあります:
| 問題 | 何が起こるか | このライブラリ |
|---|---|---|
host.json のログレベル衝突 |
INFO ログが Azure で静かに消える |
起動時に検出して警告 |
ログに invocation_id がない |
特定の実行とログを関連付けられない | context オブジェクトから自動注入 |
| コールドスタートが見えない | 新しい worker インスタンス起動時にシグナルなし | 最初の inject_context() で自動検出 |
| サードパーティロガーがうるさい | azure-core, urllib3 が Application Insights を埋め尽くす |
SamplingFilter / RedactionFilter |
| ローカルとクラウドの出力不一致 | 色付き出力が本番パイプラインを壊す | 環境認識フォーマッタ自動切替 |
| PII がログに漏洩 | extra フィールド経由で機密値が誤って記録される | キーベースの redaction を行う RedactionFilter |
| ログが呼び出しトレースから分離される | Python は OpenTelemetry 呼び出しミドルウェアを内蔵しない唯一の Functions worker ランタイムのため、worker のログレコードが span_id=0 を持つ |
host の W3C trace context をバインドし、OpenTelemetry ログが呼び出しの trace_id / span_id を継承する |
- 呼び出しコンテキスト — すべてのログに
invocation_id,function_name,cold_startを自動注入 - 構造化 JSON 出力 — Application Insights にそのまま使える NDJSON フォーマット
- ノイズ制御 —
SamplingFilterがうるさいサードパーティロガーをレート制限 - PII 保護 —
RedactionFilterが機密フィールドをログ集約に到達する前にマスキング
スコープ免責事項。 このパッケージは Python
logging/ stdout に構造化 JSON を書き込みます。これらのフィールドが Application Insights にどう現れるかは、Azure Functions host、worker、ロギング設定、および ingestion パイプラインに依存します。ライブラリは ingestion やスキーマ マッピングを所有しません —customDimensionsにパースされる形式と、生のmessage内に残る形式の両方が本番環境で有効です。
Python は OpenTelemetry 呼び出しミドルウェアを内蔵しない唯一の Azure Functions worker ランタイムです。 host はすべての呼び出しに W3C
traceparentを発行しますが、Python worker はそれをプロセス内でアクティベートしません — そのため worker のログレコードはspan_id=0でスタンプされ、host の呼び出し span から分離されます。
これは一般的な logging ライブラリが単独では埋められないギャップです。structlog、Loguru、stdlib logging は trace_id / span_id を付与するために OpenTelemetry の LoggingHandler / LoggingInstrumentor に依存しますが、これらはプロセス内に アクティブな span がある場合にのみ 機能します — Python worker はそれを提供しません。結果はどこでも同じです: trace_id=0、span_id=0、呼び出しとの相関なし。
azure-functions-logging はそのギャップを埋めます。activate_trace_context=True([otel] extra が必要)でオプトインすると、ライブラリがハンドラ実行中に host の W3C trace context をバインドし、既存の OpenTelemetry ログレコードが呼び出しの trace_id / span_id を継承します:
from azure_functions_logging import logging_context, setup_logging
setup_logging(activate_trace_context=True) # 必要: pip install azure-functions-logging[otel]
with logging_context(context):
logger.info("processing") # OpenTelemetry レコードが呼び出しの trace_id / span_id を継承これは 相関付けであってトレーシングではありません — ライブラリは span を生成・記録・エクスポートしません。OpenTelemetry や Application Insights SDK を置き換えるのではなく補完し、span の生成は引き続きそれらの責任です。OpenTelemetry トレース相関付けガイドを参照してください。
flowchart TD
A["setup_logging()"] -->|Azure / Core Tools| B[Azure host handler]
A -->|local dev| C[Console/Color handler]
D["inject_context() / with_context / logging_context"] --> E[contextvars]
E --> F{injection mode}
F -->|"default"| G[ContextFilter]
F -->|"use_record_factory=True"| H[LogRecordFactory]
G --> I[FunctionLogger]
H --> I
B --> I
C --> I
I --> J[JsonFormatter / ColorFormatter]
J --> K[Host / stdout → Application Insights]
2つの注入モードは相互排他的です。
use_record_factory=Trueの場合はContextFilterを付加しないでください。
azure-functions-logging を 使わない場合 — 単純な print() 出力、コンテキストなし、構造なし:
import azure.functions as func
app = func.FunctionApp()
@app.route(route="orders")
def process_order(req: func.HttpRequest) -> func.HttpResponse:
print("Processing order") # invocation_id なし、構造なし
print(f"Order: {req.get_json()}") # PII 漏洩可能、ログレベルなし
return func.HttpResponse("OK")ターミナル出力:
Processing order
Order: {'customer': 'Alice', 'total': 99.99}
Invocation ID なし。ログレベルなし。Application Insights での相関が困難。
azure-functions-logging を 使う場合 — 構造化され、クエリ可能、本番環境対応:
import azure.functions as func
from azure_functions_logging import JsonFormatter, get_logger, logging_context, setup_logging
setup_logging(functions_formatter=JsonFormatter())
logger = get_logger(__name__)
app = func.FunctionApp()
@app.route(route="orders")
def process_order(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
with logging_context(context):
logger.info("Processing order", order_id="o-999")
return func.HttpResponse("OK")スタンドアロンで実行した際 (例: python app.py、カラーフォーマッタ) のローカルターミナル出力:
10:30:00 INFO function_app Processing order [invocation_id=abc-123-def, function_name=process_order, cold_start=true]
func start / Azure 環境での本番出力 (functions_formatter が設定されているため Application Insights 用 NDJSON が適用される):
{"timestamp": "2024-01-15T10:30:00+00:00", "level": "INFO", "logger": "function_app",
"message": "Processing order", "invocation_id": "abc-123-def",
"function_name": "process_order", "trace_id": null, "cold_start": true,
"exception": null, "extra": {"order_id": "o-999"}}すべてのログに
invocation_idとcold_startが含まれます。Application Insights でクエリ可能。print()文ゼロ。
注: 正確な Application Insights スキーマは ingestion パイプラインに依存します。一部の配置では JSON フィールドが
customDimensionsにパースされ、他の配置では JSON がmessageカラム内に残ります。両方の形式の例を以下に示します。
traces
| where customDimensions.invocation_id == "abc-123-def"
| project timestamp, message, customDimensions.cold_start, customDimensions.function_name
| order by timestamp asc過去 1 時間のすべてのコールドスタートを検索:
traces
| where customDimensions.cold_start == "true"
| where timestamp > ago(1h)
| summarize count() by bin(timestamp, 5m)traces
| extend payload = parse_json(message)
| where tostring(payload.invocation_id) == "abc-123-def"
| project timestamp, tostring(payload.message), tostring(payload.cold_start), tostring(payload.function_name)
| order by timestamp asc過去 1 時間のすべてのコールドスタートを検索:
traces
| extend payload = parse_json(message)
| where tostring(payload.cold_start) == "true"
| where timestamp > ago(1h)
| summarize count() by bin(timestamp, 5m)このパッケージは以下を所有しません:
- stdlib logging の置き換え — Python 標準の
loggingをラップして強化するだけで、決して置き換えません - 分散トレーシング — Azure Functions ホストの W3C トレースコンテキストをバインドして、既存の OpenTelemetry ログレコードが呼び出し span の
trace_id/span_idを継承するようにしますが、このライブラリ自身が span を作成・記録・エクスポートすることはありません — トレーシングではなく相関(correlation)です。span の生成には OpenTelemetry または Application Insights SDK を使用してください。OpenTelemetry トレース相関 を参照 - API ドキュメント — API ドキュメントと spec 生成には
azure-functions-openapiを使用してください
pip install azure-functions-loggingimport azure.functions as func
from azure_functions_logging import get_logger, logging_context, setup_logging
setup_logging()
logger = get_logger(__name__)
app = func.FunctionApp()
@app.route(route="hello")
def hello(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
with logging_context(context): # invocation_id, function_name, cold_start をバインドし、終了時に以前のコンテキストへ復元
logger.info("Request received")
# ログレコードに invocation_id, function_name, cold_start が付与されます
return func.HttpResponse("OK")logging_context は推奨される主要パターンです。エンター時にコンテキストを注入し、(ハンドラが例外を発生させても)終了時に 常に 前のコンテキストを復元するため、再利用された worker で古いコンテキストが次の呼び出しに漏れることを防ぎます。
低レベル制御またはカスタムミドルウェアと統合する場合は、トークンベース復元を使用してください:
from azure_functions_logging import inject_context, restore_context
# `logger` と `context` がスコープにあると仮定 (Quick Start を参照)。
tokens = inject_context(context)
try:
logger.info("Request received")
finally:
restore_context(tokens)reset_context() は意図的にすべてのコンテキストをクリアしたい場合 (例: テストの teardown) のみ使用してください。
ローカルで Functions host を起動 (e2e サンプルアプリ 使用):
func start --script-root examples/e2e_appデプロイ後 (docs/deployment.md 参照)、同じリクエストは両環境で同じレスポンスを生成します。
curl -s http://localhost:7071/api/logme?correlation_id=demo-123{"logged": true, "correlation_id": "demo-123"}curl -s "https://<your-app>.azurewebsites.net/api/logme?correlation_id=demo-123"{"logged": true, "correlation_id": "demo-123"}koreacentral リージョンの一時的な Azure Functions デプロイで検証 (Python 3.12, Consumption plan)。レスポンスをキャプチャし、URL は匿名化されています。
以下の各機能には ドキュメントサイト に完全なガイドがあります。このセクションは各機能が何をするかを要約して単一の情報源にリンクし、README がドキュメントのコピーではなく簡潔な概要として保たれるようにします。
logging_context(context)(Quick Start 参照)はハンドラの実行期間中 invocation_id、function_name、trace_id、cold_start をバインドし、終了時には常に以前のコンテキストを復元します。より低レベルな制御が必要な場合は inject_context() / restore_context() を、暗黙的に注入するには @with_context デコレータ(同期/非同期ハンドラの両方をサポート)を使用します。
cold_startの意味。cold_start=Trueはモジュールロード後にこの Python ワーカープロセスが初めて観測した呼び出しを意味します。プラットフォームレベルのコールドスタート指標では ありません。
→ 使い方: コンテキスト注入 · API: with_context
setup_logging(functions_formatter=JsonFormatter()) を渡すと、ホスト管理のハンドラで Application Insights 向け NDJSON を出力します(スタンドアロン実行/CI では format="json")。追加フィールドは extra の下に入り、truncate_native_strings=True で長い文字列値を切り詰められます。
→ 使い方: JSON 出力 · API: JsonFormatter
起動時、host.json(または AzureFunctionsJobHost__logging__logLevel__... アプリ設定のオーバーライド)がアプリの発行するレベルを抑制する場合に警告します。host.json は作業ディレクトリ(または AzureWebJobsScriptRoot)から上位に探索して自動発見され、host_json_path= で上書きできます。
→ 設定: host.json 衝突 · トラブルシューティング
SamplingFilter は冗長なサードパーティロガー(azure-core、urllib3 など)のレートを制限し、RedactionFilter は機密キー(パスワード、トークン、シークレット、接続文字列など — 大文字小文字を区別せず、再帰的)をログ集約前にマスクします。どちらもルートハンドラにアタッチし、sensitive_keys=[...] でカスタマイズできます。
→ API: SamplingFilter · API: RedactionFilter
logger.bind(key=value) は、以降のすべてのログにリクエストスコープのメタデータを付与するロガーを返します。呼び出しごとにバインドされたロガーを作成し、モジュールレベルでキャッシュしないでください。
setup_logging(use_record_factory=True) はグローバルな LogRecordFactory をインストールし、レコード生成時にコンテキストを注入することで、ハンドラ/フィルタの構成に関係なく すべての LogRecord がコンテキストを持つようにします。setup_logging() 後にハンドラが追加されたり、ロガーがフィルタチェーンをバイパスする場合に便利です。デフォルトの ContextFilter モードとは相互排他的です。
setup_logging() は FUNCTIONS_WORKER_RUNTIME を検出します:ローカルでは色付きの人間が読みやすい出力、Azure / Core Tools ではホスト管理の NDJSON(コンテキストフィルタのみ — ハンドラ重複なし)、CI では機械可読な JSON。
→ 設定: 環境検出
- Application Insights で構造化されクエリ可能なログが必要なとき
- 単一リクエストのすべてのログにわたる
invocation_id相関が必要なとき - カスタム instrumentation なしでコールドスタート検出が必要なとき
- サードパーティロガーに対して PII redaction やノイズ制御が必要なとき
host.json設定が静かにログを抑制し、その理由がわからないとき
- 完全なドキュメント: yeongseon.github.io/azure-functions-logging-python
- Configuration reference
- Troubleshooting guide
- API reference
このパッケージは Azure Functions Python DX Toolkit の一部です。
設計原則: azure-functions-logging は構造化ロギングと呼び出し認識オブザーバビリティを所有します。Python 標準の logging を強化します — 置き換えません。隣接する関心事は azure-functions-openapi (API ドキュメントと spec 生成)、azure-functions-validation (リクエスト/レスポンス検証とシリアライゼーション)、azure-functions-langgraph (LangGraph ランタイム公開) に属します。
| パッケージ | 役割 |
|---|---|
| azure-functions-openapi-python | OpenAPI spec 生成と Swagger UI |
| azure-functions-validation-python | リクエスト/レスポンス検証とシリアライゼーション |
| azure-functions-db-python | SQLAlchemy ベースの DB 統合ヘルパー(ポーリングベースの擬似トリガー、入力/出力/クライアント注入) |
| azure-functions-langgraph-python | Azure Functions 向け LangGraph デプロイアダプタ |
| azure-functions-scaffold-python | プロジェクトスキャフォールディング CLI |
| azure-functions-logging-python | 構造化ロギングとオブザーバビリティ |
| azure-functions-doctor-python | デプロイ前診断 CLI |
| azure-functions-durable-graph-python | Durable Functions ベースのマニフェストファーストグラフランタイム (experimental) |
| azure-functions-knowledge-python | 知識検索 (RAG) デコレータ |
| azure-functions-cookbook-python | ドッグフード例 — toolkit 全体を実行する実行可能なレシピ |
このパッケージは stdlib logging に変更を加えずに Azure Functions に構造化ロギングを提供します。
LLM フレンドリーなリソース:
llms.txt— 簡潔な API リファレンスと quick start (リポジトリルート)llms-full.txt— 完全な API シグネチャ、パターン、設計原則 (リポジトリルート)
コード生成のための主要な実装詳細:
- ホスト構成を尊重 — Azure / Core Tools ではハンドラを追加せず、ルートロガーのレベルは
host.jsonに委ねます。既存のルートハンドラとルートロガー自身にContextFilterをインストールします(ルートロガーでの直接呼び出しはコンテキストを仲介する)。名前付きの子ロガーから伝搬されて後から追加されたハンドラへ到達するレコードにもコンテキストを保証したい場合はsetup_logging()にuse_record_factory=Trueを渡してください。ローカル単独モードではsetup_logging(logger_name=None)がルートロガーを構成します(レベル設定、ハンドラがなければStreamHandler追加)。 - コンテキスト注入は contextvar ベース — thread-local ではなく、asyncio で動作
- 冪等な setup —
setup_logging()を複数回呼び出しても安全 - 2 つの環境、2 つの動作:
- Azure/Core Tools: 既存の root ハンドラと root ロガー自身に
ContextFilterをインストールします。ハンドラの追加や root レベルの変更は行いません (host.jsonを尊重)。 - スタンドアロンのローカル実行: 対象/ルートロガーのレベルを設定します。ハンドラが 1 つも存在しない場合に限り
StreamHandler(ColorFormatter または JsonFormatter) を追加し、そうでなければ既存のハンドラにフィルタのみ付加します。
- Azure/Core Tools: 既存の root ハンドラと root ロガー自身に
- テストフレンドリー:
inject_context()は任意のオブジェクトを受け入れる (azure.functions.Context への強い依存なし)with_contextデコレータは同期および非同期ハンドラで動作- 必要に応じてテスト teardown で
reset_context()を使用
コード生成時:
azure_functions_loggingの公開 API からのみインポート (アンダースコアなし)setup_logging()をモジュールレベルまたはハンドラ起動時に呼び出す (リクエストごとではない)- ハンドラでは
with logging_context(context):を優先; 生のinject_context(context)はtry/finally restore_context(tokens)とのみ併用 - リクエストごとのフィールドには
logger.bind(key=value)を使用 (logger.extra への直接アクセスではない) - 暗黙的なハンドラごとのコンテキスト注入を好む場合は
with_contextデコレータを使用 - 関数の
@with_contextメタデータを検査するにはget_logging_metadata(func)を呼び出す (dict[str, Any] | Noneを返却) - PII フィールドには
RedactionFilter、大量ログにはSamplingFilterを適用
例パターン:
from azure_functions_logging import get_logger, logging_context, setup_logging
# モジュールレベル
setup_logging()
logger = get_logger(__name__)
# ハンドラごと
def my_function(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
with logging_context(context):
req_logger = logger.bind(correlation_id=req.params.get("id"))
req_logger.info("Processing")
return func.HttpResponse("OK")このプロジェクトは独立したコミュニティプロジェクトであり、Microsoft と提携、承認、保守関係にはありません。
Azure および Azure Functions は Microsoft Corporation の商標です。
MIT