Skip to content

Example: Context Binding

FunctionLogger.bind() attaches persistent key-value context to a logger and returns a new logger instance.

Goal

Use bind() for request-scoped metadata, chain bindings safely, and clear bound context when needed.

Baseline Pattern

from azure_functions_logging import get_logger, setup_logging

setup_logging(format="json")
logger = get_logger(__name__)

request_logger = logger.bind(request_id="req-1", tenant_id="tenant-a")
request_logger.info("request started")
request_logger.info("request completed")

Both log lines include request_id and tenant_id.

Immutability Guarantee

bind() does not mutate the original logger.

base = get_logger("api")
bound = base.bind(user_id="u-1")

base.info("base")
bound.info("bound")

Result:

  • base events do not include user_id.
  • bound events include user_id.

This behavior helps avoid accidental context leaks.

Chaining Bind Calls

base = get_logger("checkout")
l1 = base.bind(request_id="r-100")
l2 = l1.bind(user_id="u-900")
l3 = l2.bind(cart_id="c-55")

l3.info("checkout started")

l3 contains all merged keys: request_id, user_id, cart_id.

Clearing Context

Use clear_context() on a bound logger to remove accumulated binding on that instance.

bound = logger.bind(user_id="u-1", operation="refund")
bound.info("before clear")

bound.clear_context()
bound.info("after clear")

After clear_context(), bound fields from that instance are removed.

Azure Functions Request Pattern

import azure.functions as func
from azure_functions_logging import get_logger, inject_context, setup_logging

setup_logging(format="json")
logger = get_logger("orders")

app = func.FunctionApp()


@app.route(route="orders/{order_id}")
def get_order(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
    inject_context(context)

    order_id = req.route_params.get("order_id")
    request_logger = logger.bind(order_id=order_id, method=req.method)

    request_logger.info("lookup started")
    request_logger.info("lookup completed")
    return func.HttpResponse("ok")

This combines:

  • Global invocation fields from inject_context(context).
  • Per-request keys from bind().

Good binding keys are stable per request scope:

  • request_id
  • tenant_id
  • user_id
  • operation
  • route

Avoid high-cardinality noisy keys unless required for diagnostics.

Binding vs Per-Call Extra

Use bind() when a key should appear on many logs in the same scope. Use per-call kwargs when data is specific to a single event.

request_logger = logger.bind(request_id="r-1", tenant_id="t-1")
request_logger.info("payment authorized", amount=19.9, currency="USD")

Safety Notes

  • Do not keep request-specific bound loggers as global singletons.
  • Create bound loggers inside invocation scope.
  • Prefer immutable bind chains over mutable shared state.

Warning

Reusing one globally bound logger for unrelated requests can create misleading correlation data.

Mini End-to-End Flow

def process_checkout(base_logger, request_id, user_id):
    log = base_logger.bind(request_id=request_id, user_id=user_id, operation="checkout")
    log.info("step 1: validate cart")
    log.info("step 2: authorize payment")
    log.info("step 3: create order")

All steps share the same contextual dimensions.