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.
Python code
36 linesimport 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
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
- Use `threading.local` to store the correlation ID and a custom filter to append it to each record, which is thread-safe.
- 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
More from Observability & SRE
- Calculate Error Rate from Log Stream in Python easy
- Check if a Timestamp Falls in a Daily Maintenance Window in Python easy
- Export Metrics with OTLP Mock in Python medium
- Generate Mock CPU and Memory Metrics in Python easy
- Generate Prometheus Text Exposition Format in Python easy
- Generate Synthetic CPU Utilization Metrics in Python easy
Keep learning
Related tutorials and quizzes for this topic.