Read this in: 한국어 | 日本語 | 简体中文
Invocation-aware observability for Azure Functions Python v2.
Surfaces invocation_id, detects cold starts, warns on host.json misconfig, and outputs Application Insights-ready structured logs — without replacing Python's standard logging.
Part of the Azure Functions Python DX Toolkit → Bring FastAPI-like developer experience to Azure Functions
Azure Functions Python logging has specific failure modes that generic logging libraries don't address:
| Problem | What happens | This library |
|---|---|---|
host.json log level conflict |
Your INFO logs silently disappear in Azure |
Detects and warns at startup |
No invocation_id in logs |
Impossible to correlate logs to a specific execution | Auto-injects from context object |
| Cold start invisible | No signal when a new worker instance starts | Detects automatically on first inject_context() |
| Noisy third-party loggers | azure-core, urllib3 flood your Application Insights |
SamplingFilter / RedactionFilter |
| Local vs cloud output mismatch | Colorized output breaks in production pipelines | Environment-aware formatter switching |
| PII leaking into logs | Sensitive fields logged in exception tracebacks | RedactionFilter with pattern matching |
- Invocation context — auto-injects
invocation_id,function_name,cold_startinto every log - Structured JSON output — Application Insights-ready NDJSON format for production
- Noise control —
SamplingFilterrate-limits chatty third-party loggers - PII protection —
RedactionFiltermasks sensitive fields before they reach log aggregation
Without azure-functions-logging — plain print() output, no context, no structure:
With azure-functions-logging — colorized local dev output and production-ready JSON:
This package does not own:
- Replacing stdlib logging — it wraps and enriches Python's standard
logging, never replaces it - Distributed tracing — use OpenTelemetry or Application Insights SDK for end-to-end trace correlation
- API documentation — use
azure-functions-openapifor API documentation and spec generation
pip install azure-functions-loggingimport azure.functions as func
from azure_functions_logging import get_logger, inject_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:
inject_context(context) # binds invocation_id, function_name, cold_start
logger.info("Request received")
# {"level": "INFO", "invocation_id": "abc-123", "cold_start": true, ...}
return func.HttpResponse("OK")Start the Functions host locally (using the e2e example app):
func startAfter deploying (see docs/deployment.md), the same request produces the same response in both environments.
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"}Verified against a temporary Azure Functions deployment in koreacentral (Python 3.12, Consumption plan). Response captured and URL anonymized.
inject_context(context) should be the first line of every handler. It binds:
invocation_id— unique per execution, correlates all logs for one requestfunction_name— the Azure Functions function nametrace_id— trace context from the platformcold_start—Trueon first invocation of this worker process
def my_function(req, context):
inject_context(context)
logger.info("handler started")
# every log from here carries invocation_id and cold_startWithout inject_context(), these fields are None in every log line.
For less boilerplate, use the with_context decorator instead of calling inject_context() manually:
import azure.functions as func
from azure_functions_logging import get_logger, setup_logging, with_context
setup_logging()
logger = get_logger(__name__)
app = func.FunctionApp()
@app.route(route="hello")
@with_context
def hello(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
logger.info("Request received")
return func.HttpResponse("OK")The decorator finds the context parameter by name, calls inject_context() before your handler runs, and resets context variables in finally after it returns.
Custom parameter name:
@with_context(param="ctx")
def hello(req: func.HttpRequest, ctx: func.Context) -> func.HttpResponse:
...Both sync and async handlers are supported.
Use JSON format when logs feed Application Insights or any aggregation system:
setup_logging(format="json")Output per log line (NDJSON — one JSON object per line):
{"timestamp": "2024-01-15T10:30:00Z", "level": "INFO", "logger": "my_module",
"message": "order accepted", "invocation_id": "abc-123", "function_name": "OrderHandler",
"cold_start": false, "trace_id": "00-abc...", "exception": null,
"extra": {"order_id": "o-999"}}Extra fields appear in extra and are indexable in Application Insights:
logger.info("order accepted", order_id="o-999", tenant_id="t-1")If your host.json suppresses log levels that your app emits, you get this warning at startup:
WARNING: host.json logLevel.default is 'Warning'. Logs below WARNING will be suppressed in Azure.
Recommended host.json baseline:
{
"version": "2.0",
"logging": {
"logLevel": {
"default": "Information",
"Function": "Information"
}
}
}Suppress chatty third-party loggers without removing them:
from azure_functions_logging import SamplingFilter, setup_logging
import logging
setup_logging()
# Only log 1 in 10 azure-core messages
logging.getLogger("azure").addFilter(SamplingFilter(rate=0.1))
# Silence urllib3 completely in production
logging.getLogger("urllib3").setLevel(logging.WARNING)Strip sensitive fields before they reach Application Insights:
from azure_functions_logging import RedactionFilter, setup_logging
import logging
setup_logging()
root = logging.getLogger()
root.addFilter(RedactionFilter(patterns=["password", "token", "secret"]))Any log record where the message or extra fields match a pattern will have those values replaced with [REDACTED].
| Environment | Format | Behavior |
|---|---|---|
| Local terminal | color (default) |
Colorized [TIME] [LEVEL] [LOGGER] message |
| Azure / Core Tools | json |
NDJSON, no ANSI codes, host-managed handlers |
| CI / pipeline | json |
NDJSON, machine-parseable |
setup_logging() detects FUNCTIONS_WORKER_RUNTIME and WEBSITE_INSTANCE_ID to choose the right path automatically. In Azure, it installs context filters without adding handlers (avoids duplicate output from the host pipeline).
Attach request-scoped metadata to every log without passing it through every call:
def process_order(order_id: str) -> None:
order_logger = logger.bind(order_id=order_id, region="eastus")
order_logger.info("processing started") # includes order_id + region
order_logger.info("processing complete") # same metadata, new messageCreate bound loggers per-invocation. Do not cache them at module level.
- You need structured, queryable logs in Application Insights
- You want
invocation_idcorrelation across all logs for a single request - You need cold start detection without custom instrumentation
- You want PII redaction or noise control for third-party loggers
- Your
host.jsonconfig silently suppresses logs and you don't know why
- Full docs: yeongseon.github.io/azure-functions-logging
- Configuration reference
- Troubleshooting guide
- API reference
This package is part of the Azure Functions Python DX Toolkit.
Design principle: azure-functions-logging owns structured logging and invocation-aware observability. It enriches Python's standard logging — it does not replace it. Adjacent concerns belong to azure-functions-openapi (API documentation and spec generation), azure-functions-validation (request/response validation and serialization), and azure-functions-langgraph (LangGraph runtime exposure).
| Package | Role |
|---|---|
| azure-functions-openapi | OpenAPI spec generation and Swagger UI |
| azure-functions-validation | Request/response validation and serialization |
| azure-functions-db | Database bindings for SQL, PostgreSQL, MySQL, SQLite, and Cosmos DB |
| azure-functions-langgraph | LangGraph deployment adapter for Azure Functions |
| azure-functions-scaffold | Project scaffolding CLI |
| azure-functions-logging | Structured logging and observability |
| azure-functions-doctor | Pre-deploy diagnostic CLI |
| azure-functions-durable-graph | Manifest-first graph runtime with Durable Functions (experimental) |
| azure-functions-python-cookbook | Recipes and examples |
This package provides structured logging for Azure Functions with zero modifications to stdlib logging.
LLM-Friendly Resources:
llms.txt— Concise API reference and quick start (repo root)llms-full.txt— Complete API signatures, patterns, and design principles (repo root)
Key Implementation Details for Code Generation:
- Never modify root logger — Only install filters/formatters on handlers
- Context injection is contextvar-based — Not thread-local, works with asyncio
- Idempotent setup — Calling setup_logging() multiple times is safe
- Two environments, two behaviors:
- Azure/Core Tools: Only install filters on existing root handlers (respects host.json)
- Local dev: Add ColorFormatter or JsonFormatter handler to specified logger
- Test-friendly:
inject_context()accepts any object (no hard dependency on azure.functions.Context)with_contextdecorator works with sync and async handlers- Use
context.reset_context_vars()in test teardown if needed
When generating code:
- Import only from
azure_functions_loggingpublic API (no underscores) - Call
setup_logging()at module level or handler startup (not per-request) - Call
inject_context(context)as first line of handler - Use
logger.bind(key=value)for per-request fields (not direct logger.extra) - Apply
RedactionFilterfor PII fields,SamplingFilterfor high-volume logs
Example Pattern:
from azure_functions_logging import setup_logging, get_logger, inject_context
# Module level
setup_logging()
logger = get_logger(__name__)
# Per handler
def my_function(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
inject_context(context)
req_logger = logger.bind(correlation_id=req.params.get("id"))
req_logger.info("Processing")
return func.HttpResponse("OK")This project is an independent community project and is not affiliated with, endorsed by, or maintained by Microsoft.
Azure and Azure Functions are trademarks of Microsoft Corporation.
MIT

