Extra attributes and correlation IDs

Added in version 2.3.

Log calls can carry values as attributes of the record, passed with extra={...}. A logging.Filter can add attributes as well, for example the ID of the request being handled. Set show_extra to append them to the message as key=value pairs. On a terminal they are dimmed.

Show every extra attribute

With show_extra=True, every attribute which is not a standard LogRecord attribute and does not start with _ is shown, sorted by name:

>>> import logging
>>> from gouge.colourcli import Simple
>>> handlers = Simple.basicConfig(
...     level=logging.INFO,
...     show_extra=True,
... )
>>> log = logging.getLogger("app")
>>> log.warning(
...     "Order created",
...     extra={"order.id": 42, "user": "ana"},
... )
2026-10-27 13:34:48,899 WARNING    [app] Order created order.id=42 user=ana

Values are rendered with str. A value which is empty or contains whitespace is quoted with repr(), so the pairs stay easy to parse:

>>> log.warning(
...     "Renamed", extra={"title": "Q3 report"}
... )
2026-10-27 13:34:48,899 WARNING    [app] Renamed title='Q3 report'

Show selected attributes

Give a sequence of names to show only those, in that order. Names a record does not have are skipped:

>>> handlers = Simple.basicConfig(
...     show_extra=["user", "order.id"]
... )
>>> log.warning(
...     "Order created",
...     extra={"order.id": 42, "user": "ana"},
... )
2026-10-27 13:34:48,899 WARNING    [app] Order created user=ana order.id=42
>>> log.warning(
...     "Logged out", extra={"user": "ana"}
... )
2026-10-27 13:34:48,899 WARNING    [app] Logged out user=ana

Add a correlation ID

A correlation ID ties together the records written while handling one request. Keep the current ID in a ContextVar and copy it onto every record with a filter on the handler:

>>> import contextvars
>>> request_id = contextvars.ContextVar(
...     "request_id", default="-"
... )
>>> class RequestIdFilter(logging.Filter):
...     def filter(self, record):
...         record.request_id = request_id.get()
...         return True
>>> handlers = Simple.basicConfig(
...     show_extra=["request_id"]
... )
>>> for handler in handlers:
...     handler.addFilter(RequestIdFilter())
>>> token = request_id.set("4bf92f35")
>>> log.info("Payment accepted")
2026-10-27 13:34:48,899 INFO       [app] Payment accepted request_id=4bf92f35
>>> request_id.reset(token)
>>> log.info("Idle")
2026-10-27 13:34:48,899 INFO       [app] Idle request_id=-

Set the ID where a request starts, for example in a web framework’s middleware, and reset it when the request ends.

Before 2.3, showing such a field meant subclassing Simple and overriding format() to append it. Replace such a subclass with the filter and show_extra. The filter also puts the ID into JSONLog output, which writes every extra attribute (see Structured output).

OpenTelemetry trace IDs

The OpenTelemetry logging instrumentation sets otelTraceID and otelSpanID on every record. gouge shows them as trace_id and span_id while a span is active, and leaves them out otherwise. show_extra=True includes them; name them to show only the trace IDs:

>>> handlers = Simple.basicConfig(
...     show_extra=["trace_id", "span_id"]
... )
>>> log.info(
...     "Charged",
...     extra={
...         "otelTraceID": "0af7651916cd43dd",
...         "otelSpanID": "b7ad6b71",
...     },
... )
2026-10-27 13:34:48,899 INFO       [app] Charged trace_id=0af7651916cd43dd span_id=b7ad6b71