Examples

Here are comprehensive examples showing how to use starlette-context in real applications.

Basic Example

A complete working example is available in the example directory of the repository.

from contextlib import asynccontextmanager

import structlog
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.base import (
    BaseHTTPMiddleware,
    RequestResponseEndpoint,
)
from starlette.requests import Request
from starlette.responses import JSONResponse, Response
from starlette.routing import Route

from starlette_context import context, plugins
from starlette_context.middleware import ContextMiddleware

logger = structlog.get_logger("starlette_context_example")


class LoggingMiddleware(BaseHTTPMiddleware):
    """
    Example logging middleware that includes context in logs.
    """

    async def dispatch(
        self, request: Request, call_next: RequestResponseEndpoint
    ) -> Response:
        await logger.info(
            "request started",
            path=request.url.path,
            method=request.method,
        )
        response = await call_next(request)
        await logger.info(
            "request finished",
            path=request.url.path,
            method=request.method,
            status_code=response.status_code,
        )
        return response


async def index(request: Request):
    context["custom_value"] = "This will be visible in logs"
    await logger.info("log from view")
    return JSONResponse(context.data)


@asynccontextmanager
async def lifespan(app):
    # Configure structlog to include context data
    setup_logging()
    yield


app = Starlette(
    debug=True,
    routes=[
        Route("/", index),
    ],
    middleware=[
        Middleware(
            ContextMiddleware,
            plugins=(
                plugins.CorrelationIdPlugin(),
                plugins.RequestIdPlugin(),
            ),
        ),
        Middleware(LoggingMiddleware),
    ],
    lifespan=lifespan,
)

Setting Up Logging with Context

Here’s how to configure structlog to automatically include context data in your logs:

from collections.abc import MutableMapping
from typing import Any

import structlog
import logging.config

from starlette_context import context


def setup_logging():
    def add_app_context(
        logger: logging.Logger,
        method_name: str,
        event_dict: MutableMapping[str, Any],
    ) -> MutableMapping[str, Any]:
        if context.exists():
            event_dict.update(context.data)
        return event_dict

    structlog.configure(
        processors=[
            structlog.processors.TimeStamper(fmt="iso"),
            add_app_context,
            structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=structlog.stdlib.AsyncBoundLogger,
        cache_logger_on_first_use=True,
    )

    logging_config = {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "json": {
                "()": structlog.stdlib.ProcessorFormatter,
                "processor": structlog.processors.JSONRenderer(),
            },
        },
        "handlers": {
            "json": {
                "level": "DEBUG",
                "class": "logging.StreamHandler",
                "formatter": "json",
            },
        },
        "loggers": {
            "starlette_context_example": {
                "handlers": ["json"],
                "level": "INFO",
            },
            "uvicorn": {"handlers": ["json"], "level": "INFO"},
        },
    }
    logging.config.dictConfig(logging_config)

With this setup, every log entry will automatically include all context data, such as request IDs and correlation IDs.

Sample Log Output

When you run the example application and make a request, you’ll see logs like:

{
  "event": "request started",
  "path": "/",
  "method": "GET",
  "X-Correlation-ID": "5ca2f0b43115461bad07ccae5976a990",
  "X-Request-ID": "21f8d52208ec44948d152dc49a713fdd",
  "timestamp": "2023-03-01T12:00:00.123456Z"
}

{
  "event": "log from view",
  "X-Correlation-ID": "5ca2f0b43115461bad07ccae5976a990",
  "X-Request-ID": "21f8d52208ec44948d152dc49a713fdd",
  "custom_value": "This will be visible in logs",
  "timestamp": "2023-03-01T12:00:00.234567Z"
}

{
  "event": "request finished",
  "path": "/",
  "method": "GET",
  "status_code": 200,
  "X-Correlation-ID": "5ca2f0b43115461bad07ccae5976a990",
  "X-Request-ID": "21f8d52208ec44948d152dc49a713fdd",
  "custom_value": "This will be visible in logs",
  "timestamp": "2023-03-01T12:00:00.345678Z"
}

Custom Middleware Example

Here’s an example of a custom middleware that uses the context:

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
from starlette.requests import Request
from starlette_context import context

class AuthMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next):
        # Extract token from header
        auth_header = request.headers.get("Authorization", "")
        if auth_header.startswith("Bearer "):
            token = auth_header[7:]
            # Validate token and get user info
            user_id = validate_token(token)  # Your validation logic
            if user_id:
                # Store user info in context
                context["user_id"] = user_id
                context["is_authenticated"] = True
            else:
                context["is_authenticated"] = False
        else:
            context["is_authenticated"] = False

        # Continue with request
        response = await call_next(request)
        return response

# Add to your middleware stack
middleware = [
    Middleware(ContextMiddleware),
    Middleware(AuthMiddleware),
]

Running the Example

To run the example from the repository:

cd example
uv run uvicorn app:app --reload --port 5000

Then visit http://localhost:5000/ in your browser or use curl:

curl http://localhost:5000/

You should see a JSON response with the context data and logs in your console showing the same data.