Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use extra to attach a field to one logging call, LoggerAdapter to reuse context across calls, a filter to enrich records at a logger or handler, or a LogRecord factory to add fields as records are created. In every case, the formatter must know about the field, and every record reaching it must provide that field.

How custom logging attributes work

Python logging creates a LogRecord for each event. Custom attributes let you attach useful context—such as a request, tenant, or job ID—to that record and include it in formatted output. The logging cookbook explains that values passed through extra are merged into the record, so a formatter can reference them by name: Python Logging Cookbook.

Choose the narrowest mechanism that covers the records that need the value: extra for an individual event, an adapter for a group of calls, a filter for a logger or handler boundary, or a factory for attributes broadly added at record creation.

Add an attribute to one logging call with extra

Pass a dictionary to the extra parameter, then reference its key in the formatter:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import logging

logging.basicConfig(
    format="%(levelname)s %(message)s [request_id=%(request_id)s]",
    level=logging.INFO,
)
logger = logging.getLogger(__name__)

logger.info("Request received", extra={"request_id": "req-123"})

The mapping’s values become attributes on that call’s LogRecord; %(request_id)s inserts the value when the formatter renders the message. Use stable, application-specific names such as request_id, tenant_id, or job_id. Do not use names that collide with built-in record attributes such as name, levelname, or message. The standard record fields are listed in the LogRecord attributes reference.

Make sure every formatted record has the field

If a formatter refers to %(request_id)s but a record reaching it has no request_id, formatting can fail. This commonly happens when only some calls provide extra but the same handler formats all records. Ensure the field is supplied consistently, or choose an enrichment method that covers all records reaching that formatter.

Reuse context across calls with LoggerAdapter

When several log calls share the same context, wrap the logger with a LoggerAdapter instead of repeating the same mapping:

import logging

logger = logging.getLogger(__name__)
request_logger = logging.LoggerAdapter(logger, {"request_id": "req-123"})

request_logger.info("Request received")
request_logger.warning("Request is taking longer than expected")

The adapter routes calls through the underlying logger and supplies its context as extra. This is useful for a request or job that generates multiple log events. Avoid creating a separate logger for every connection or request: logger instances are not garbage-collected, so an unbounded set is difficult to manage. The cookbook describes adapter use and its default context behavior in the Logging Cookbook.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Adapter context and per-call extra

With the documented default behavior, an adapter’s context replaces a caller-provided extra mapping. If a call needs both adapter-level context and its own fields, check the behavior for your Python version and implement an intentional merge strategy rather than assuming the mappings combine automatically.

Enrich records at a logger or handler with a filter

A filter can add, change, or remove attributes for records processed where it is installed. A handler-level filter is useful when only that handler’s output should receive the field:

import logging

class RequestContextFilter(logging.Filter):
    def filter(self, record):
        record.request_id = current_request_id()
        return True

handler = logging.StreamHandler()
handler.addFilter(RequestContextFilter())
handler.setFormatter(logging.Formatter(
    "%(levelname)s %(message)s [request_id=%(request_id)s]"
))

Install a filter on a logger or handler according to which records need enrichment. A handler filter applies at that handler’s processing point, so another handler will not automatically receive its changes. See the filter reference for the API.

Python 3.12 replacement-record behavior

Starting in Python 3.12, a filter may return a replacement LogRecord. A handler filter can use this to change the record emitted by that handler without mutating the original record that other handlers may process. Do not rely on replacement-record behavior when running an earlier Python version.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Add attributes when records are created with a factory

A custom LogRecord factory can attach a field broadly at record creation time. Chain the existing factory so its behavior is preserved:

import logging

old_factory = logging.getLogRecordFactory()

def record_factory(*args, **kwargs):
    record = old_factory(*args, **kwargs)
    record.application = "billing"
    return record

logging.setLogRecordFactory(record_factory)

Use this for values that belong on records broadly, rather than for context needed only by a particular handler. Avoid replacing standard attributes or fields another factory already sets. Chaining factories adds runtime work to logging calls; the cookbook recommends a filter when it can achieve the same result. See the LogRecord factory reference.

Choose the right method

Need Mechanism Key consideration
One custom value on one event extra Include the key in the formatter and supply it for every record that uses that format.
Shared context across a group of calls LoggerAdapter By default, adapter context replaces call-level extra.
Context at one logger or handler boundary Filter Placement determines which records are enriched; replacement-record returns require Python 3.12 or later.
An attribute on records at creation time LogRecord factory Chain the existing factory and account for added runtime work.

For request IDs on only selected calls, start with extra. When many calls in one request share the same value, use an adapter. Use a filter when enrichment belongs at a specific processing boundary, and a factory only when the attribute should be added broadly as records are created.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.