Skip to content

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 message short 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_id
  • function_name
  • trace_id
  • cold_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:

{
  "logging": {
    "logLevel": {
      "default": "Warning"
    }
  }
}

If app setup is INFO, lower-severity events can be hidden by host policy.

Recommended baseline:

{
  "logging": {
    "logLevel": {
      "default": "Information"
    }
  }
}

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.json defaults 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.

12) Where to Go Next