Part of the Azure Functions Python DX Toolkit — dogfood-tested by azure-functions-cookbook-python.
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
Scope disclaimer. This package writes structured JSON to Python
logging/ stdout. How those fields appear in Application Insights depends on the Azure Functions host, worker, logging configuration, and ingestion pipeline. The library does not own ingestion or schema mapping — bothcustomDimensions-parsed and raw-messageshapes are valid in production.
Without azure-functions-logging — plain print() output, no context, no structure:
import azure.functions as func
app = func.FunctionApp()
@app.route(route="orders")
def process_order(req: func.HttpRequest) -> func.HttpResponse:
print("Processing order") # no invocation_id, no structure
print(f"Order: {req.get_json()}") # PII may leak, no log level
return func.HttpResponse("OK")Terminal output:
Processing order
Order: {'customer': 'Alice', 'total': 99.99}
No invocation ID. No log level. Hard to correlate in Application Insights.
With azure-functions-logging — structured, queryable, production-ready:
import 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="orders")
def process_order(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
inject_context(context)
logger.info("Processing order", order_id="o-999")
return func.HttpResponse("OK")Local terminal output (colorized):
10:30:00 INFO function_app Processing order [invocation_id=abc-123-def, function_name=process_order, cold_start=true]
Production output (NDJSON for Application Insights):
{"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"}}Every log carries
invocation_idandcold_start. Queryable in Application Insights. Zeroprint()statements.
Note: The exact Application Insights schema depends on your ingestion pipeline. In some deployments JSON fields are parsed into
customDimensions; in others the JSON stays inside themessagecolumn. Examples for both shapes are below.
traces
| where customDimensions.invocation_id == "abc-123-def"
| project timestamp, message, customDimensions.cold_start, customDimensions.function_name
| order by timestamp ascFind all cold starts in the last hour:
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 ascFind all cold starts in the last hour:
traces
| extend payload = parse_json(message)
| where tostring(payload.cold_start) == "true"
| where timestamp > ago(1h)
| summarize count() by bin(timestamp, 5m)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, 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): # binds invocation_id, function_name, cold_start; resets on exit
logger.info("Request received")
# {"level": "INFO", "invocation_id": "abc-123", "cold_start": true, ...}
return func.HttpResponse("OK")logging_context is the recommended primary pattern: it injects context on enter and always resets on exit (even when the handler raises), which prevents stale context from leaking into the next invocation on a reused worker.
For lower-level control or when integrating with custom middleware, inject_context(context) and reset_context() are exposed individually:
inject_context(context)
try:
logger.info("Request received")
finally:
reset_context()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
cold_startsemantics.cold_start=Truemeans the first invocation observed by this Python worker process after module load. It is not a platform-level cold start metric and does not correspond to App Service plan / instance allocation cold starts reported by Azure Functions metrics. Subsequent invocations on the same worker emitcold_start=Falseuntil the worker is recycled.
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:00+00:00", "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-python
- 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-python | OpenAPI spec generation and Swagger UI |
| azure-functions-validation-python | Request/response validation and serialization |
| azure-functions-db-python | Database bindings for SQL, PostgreSQL, MySQL, SQLite, and Cosmos DB |
| azure-functions-langgraph-python | LangGraph deployment adapter for Azure Functions |
| azure-functions-scaffold-python | Project scaffolding CLI |
| azure-functions-logging-python | Structured logging and observability |
| azure-functions-doctor-python | Pre-deploy diagnostic CLI |
| azure-functions-durable-graph-python | Manifest-first graph runtime with Durable Functions (experimental) |
| azure-functions-knowledge-python | Knowledge retrieval (RAG) decorators |
| azure-functions-cookbook-python | Dogfood examples — runnable recipes that exercise the full toolkit |
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