Usage Guide¶
This guide covers practical, production-oriented usage of azure-functions-logging across local development and Azure Functions runtime deployment.
Quick Baseline¶
Start every project with this structure:
from azure_functions_logging import get_logger, setup_logging
setup_logging()
logger = get_logger(__name__)
From here, add JSON output, context injection, and binding based on your environment and observability needs.
1) Basic Setup¶
Local Development Defaults¶
from azure_functions_logging import get_logger, setup_logging
setup_logging()
logger = get_logger(__name__)
logger.info("application started")
Default behavior:
- Level is
INFO. - Format is
color. - Setup is idempotent.
Explicit Level Configuration¶
import logging
from azure_functions_logging import get_logger, setup_logging
setup_logging(level=logging.DEBUG)
logger = get_logger("demo")
logger.debug("debug event")
logger.info("info event")
Explicit Logger Targeting¶
from azure_functions_logging import get_logger, setup_logging
setup_logging(logger_name="payments")
logger = get_logger("payments.http")
logger.info("named logger configured")
Tip
If your application is small to medium-sized, root logger setup with logger_name=None is the simplest and safest default.
2) JSON Output for Production¶
Use JSON when logs are consumed by systems, not just humans.
import logging
from azure_functions_logging import get_logger, setup_logging
setup_logging(level=logging.INFO, format="json")
logger = get_logger("orders")
logger.info("service started", service="orders", region="eastus")
Typical JSON event shape:
{"timestamp":"...","level":"INFO","logger":"orders","message":"service started","invocation_id":null,"function_name":null,"trace_id":null,"cold_start":null,"exception":null,"extra":{"service":"orders","region":"eastus"}}
JSON best practices:
- Keep
messageshort and stable. - Put event dimensions in structured keys.
- Use stable key naming conventions (
tenant_id,request_id,operation).
3) Context Injection in Azure Functions¶
Call inject_context(context) once per invocation.
import azure.functions as func
from azure_functions_logging import get_logger, inject_context, setup_logging
setup_logging(format="json")
logger = get_logger(__name__)
app = func.FunctionApp()
@app.route(route="hello")
def hello(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
inject_context(context)
logger.info("request started")
return func.HttpResponse("ok")
Fields populated from context:
invocation_idfunction_nametrace_idcold_start
Why it matters:
- Correlates all logs for one invocation.
- Improves debugging and incident triage.
- Requires no manual propagation across call chains.
4) Context Binding with FunctionLogger.bind()¶
Use bind() for request-scoped keys that should appear on many events.
from azure_functions_logging import get_logger
logger = get_logger("checkout")
request_logger = logger.bind(request_id="r-1", user_id="u-1")
request_logger.info("validate cart")
request_logger.info("authorize payment")
request_logger.info("create order")
Binding behavior:
- Immutable: original logger unchanged.
- Merge-friendly: chaining adds keys.
- Predictable: no hidden global mutable context.
Chaining Example¶
base = get_logger("api")
l1 = base.bind(tenant_id="tenant-a")
l2 = l1.bind(operation="import")
l2.info("import started")
Clearing Bound Context¶
bound = get_logger("demo").bind(session_id="s-123")
bound.info("before clear")
bound.clear_context()
bound.info("after clear")
Warning
Do not store request-scoped bound loggers as module-level singletons. Create them per invocation.
5) Cold Start Detection¶
Cold start is automatic and exposed in logs through cold_start.
Behavior:
- First invocation after process startup:
cold_start=True - Subsequent invocations in same process:
cold_start=False
No manual state tracking needed.
Example with Duration¶
import time
from azure_functions_logging import get_logger
logger = get_logger("perf")
start = time.perf_counter()
# handler logic
duration_ms = int((time.perf_counter() - start) * 1000)
logger.info("request completed", duration_ms=duration_ms)
In JSON mode, this allows easy latency split by cold vs warm path.
6) host.json Conflict Awareness¶
In Azure/Core Tools contexts, host-level log policy can suppress app logs.
If host defaults are stricter than your configured level, the package emits a warning to surface this mismatch.
Potential conflict example:
If app setup is INFO, lower-severity events can be hidden by host policy.
Recommended baseline:
7) Environment Behavior¶
setup_logging() chooses behavior by runtime detection.
Local standalone execution¶
- Sets logger level.
- Adds stream handler if missing.
- Installs context filter.
- Applies selected formatter.
Azure Functions / Core Tools¶
- Does not add handlers.
- Installs context filter for metadata enrichment.
- Leaves host handler pipeline intact.
This design prevents duplicate logs in runtime-managed environments.
8) Complete End-to-End Pattern¶
import json
import logging
import azure.functions as func
from azure_functions_logging import get_logger, inject_context, setup_logging
setup_logging(level=logging.INFO, format="json")
logger = get_logger(__name__)
app = func.FunctionApp()
@app.route(route="process")
def process(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
inject_context(context)
request_logger = logger.bind(method=req.method, route="/process")
request_logger.info("request received")
try:
payload = req.get_json()
user_id = payload.get("user_id")
user_logger = request_logger.bind(user_id=user_id)
user_logger.info("payload accepted")
result = {"status": "ok"}
user_logger.info("request completed", status="success")
return func.HttpResponse(json.dumps(result), mimetype="application/json")
except Exception:
request_logger.exception("request failed")
return func.HttpResponse("internal error", status_code=500)
9) Advanced Patterns¶
A) Suppress Noisy Dependency Logs¶
import logging
from azure_functions_logging import setup_logging
setup_logging(level=logging.INFO, format="json")
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING)
B) Feature Flags in Logs¶
feature_logger = get_logger("features").bind(feature="recommendations", version="v2")
feature_logger.info("feature evaluated", outcome="enabled")
C) Operation Timing¶
import time
op_logger = get_logger("timing").bind(operation="sync")
start = time.perf_counter()
# ... work ...
elapsed_ms = int((time.perf_counter() - start) * 1000)
op_logger.info("operation finished", elapsed_ms=elapsed_ms)
10) Operational Checklist¶
Before production rollout:
setup_logging()called once in startup path.format="json"enabled where logs are machine-consumed.inject_context(context)present in every handler.- Request-level metadata attached through
bind(). host.jsondefaults reviewed against required visibility.- Dependency logger noise controlled with explicit levels.
11) Common Pitfalls¶
- Multiple setup owners in the same process.
- Missing context injection before first log event.
- Expecting app-level level to override restrictive host policy.
- Reusing one bound logger across unrelated invocations.
- Emitting unstructured free-text fields that are hard to query.