Adding a Correlation ID to Log Context in Python

Injects a correlation ID into the logging context using a context manager and a custom log record factory so every log line includes the ID.

Medium Python 3.9+ Aug 9, 2026 Observability & SRE 15 views 0 copies

Python code

36 lines
Python 3.9+
import logging
import uuid
from contextlib import contextmanager

logging.basicConfig(level=logging.INFO, format='%(levelname)s | %(correlation_id)s | %(message)s')


@contextmanager
def correlation_id_context(correlation_id):
    """Temporarily inject a correlation_id into the logging context."""
    extra = {'correlation_id': correlation_id}
    logging.LoggerAdapter(logging.getLogger(), extra)
    # Use extra keyword in the root logger to pass the field
    original_factory = logging.getLogRecordFactory()

    def factory(*args, **kwargs):
        record = original_factory(*args, **kwargs)
        record.correlation_id = correlation_id
        return record

    logging.setLogRecordFactory(factory)
    try:
        yield
    finally:
        logging.setLogRecordFactory(original_factory)


def process_task():
    correlation_id = str(uuid.uuid4())[:8]
    with correlation_id_context(correlation_id):
        logging.info("Task started")
        logging.info("Processing data...")


if __name__ == "__main__":
    process_task()

Output

stdout
INFO | 1a2b3c4d | Task started
INFO | 1a2b3c4d | Processing data...

How it works

The @contextmanager decorator turns correlation_id_context into a reusable context manager. Inside, a custom log record factory is set using logging.setLogRecordFactory. This factory calls the original factory to create the log record, then attaches the correlation_id attribute to it. Because the logging formatter references %(correlation_id)s, every log message created inside the with block automatically includes the ID. The finally block restores the original factory so other parts of the program are unaffected. The LoggerAdapter line is effectively a no-op and can be removed, but it's kept to illustrate the intent.

Common mistakes

  • Forgetting to restore the original log record factory after the context block, leaking the correlation ID to unrelated logs.
  • Using `logging.LoggerAdapter` expecting it to inject the field, but not actually assigning the adapter to the logger.
  • Assuming the correlation ID appears in every thread; this pattern only affects the current thread unless you also handle thread-local storage.

Variations

  1. Use `threading.local` to store the correlation ID and a custom filter to append it to each record, which is thread-safe.
  2. Pass the correlation ID explicitly to each log call via the `extra` parameter instead of using a custom factory.

Real-world use cases

  • Tracing a single user request across multiple service logs in a microservices architecture.
  • Correlating logs from background jobs or message consumers with a unique job ID for debugging.
  • Matching log entries to a specific API call in a REST service to simplify error investigation.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Observability & SRE

Related tutorials and quizzes for this topic.