Class-bound structured logging with auto-injected correlation IDs for Python services.
Reason this release was yanked:
Superseded by 0.2.0 (root-layout rebuild); 0.1.0 is the deprecated src-layout build
Project description
logging-mixin
Class-bound structured logging with auto-injected correlation IDs for Python services.
Replaces module-level loggers in business logic with per-class loggers that automatically inject correlation IDs and class context into every log record. Enables clean distributed tracing without boilerplate.
Why?
Production services need correlation IDs for tracing requests through distributed systems. Traditional approaches require passing context through every function or managing global state:
# Traditional approach: manual context passing (boilerplate)
logger = logging.getLogger(__name__)
def create_user(name: str, correlation_id: str):
logger.info("Creating user", extra={"correlation_id": correlation_id, "name": name})
# Must thread correlation_id through every call
save_user(name, correlation_id)
def save_user(name: str, correlation_id: str):
logger.info("Saving user", extra={"correlation_id": correlation_id, "name": name})
logging-mixin solves this with automatic injection:
from logging_mixin import LoggingMixin
class UserService(LoggingMixin):
def create_user(self, name: str):
self.log_info("Creating user", name=name)
# correlation_id automatically injected by LoggingMixin
self.save_user(name)
def save_user(self, name: str):
self.log_info("Saving user", name=name)
# correlation_id still available, no parameter needed
Installation
pip install logging-mixin
Requires Python 3.10+.
Optional framework extras
# Django support
pip install logging-mixin[django]
# FastAPI support
pip install logging-mixin[fastapi]
# AWS Lambda support
pip install logging-mixin[aws-lambda]
# All together
pip install logging-mixin[django,fastapi,aws-lambda]
# Development (includes all extras + testing tools)
pip install logging-mixin[dev]
Quick Start
1. Create a service using LoggingMixin
from logging_mixin import LoggingMixin
class OrderService(LoggingMixin):
def create_order(self, user_id: int, items: list[str]):
self.log_info("order.create", user_id=user_id, item_count=len(items))
# Logs with:
# logger name: "myapp.services.OrderService"
# extra: {"correlation_id": "abc123", "user_id": 123, "item_count": 3}
...
2. Set correlation ID in your framework
Django
Add the middleware to settings.py:
MIDDLEWARE = [
"logging_mixin.adapters.django.CorrelationIdMiddleware",
# ... other middleware
]
FastAPI
Add middleware:
from fastapi import FastAPI
from logging_mixin.adapters.fastapi import correlation_id_middleware
app = FastAPI()
app.add_middleware(correlation_id_middleware)
AWS Lambda
Call in handler:
from logging_mixin.adapters.aws_lambda import setup_correlation_id
def lambda_handler(event, context):
setup_correlation_id(event, context)
# Now LoggingMixin can access correlation_id
...
3. Use in background tasks
Correlation IDs automatically propagate to Celery tasks, background jobs, and async functions via Python's contextvars:
from celery import shared_task
from logging_mixin import LoggingMixin
@shared_task
def process_order(order_id: int):
service = OrderService()
service.log_info("order.processing", order_id=order_id)
# Inherits correlation_id from the original request context
Design
Instance-only (no @classmethod/@staticmethod)
LoggingMixin's log_* methods are instance methods. They cannot be called from @classmethod or @staticmethod because they read self._logger:
class MyService(LoggingMixin):
def instance_method(self):
self.log_info("works") # ✓ OK
@classmethod
def class_method(cls):
self.log_info("FAILS") # ✗ TypeError: missing 1 required positional argument 'self'
@staticmethod
def static_method():
self.log_info("FAILS") # ✗ TypeError
Workaround: Use module-level logger for class methods and manually inject correlation ID:
import logging
from logging_mixin import get_correlation_id
logger = logging.getLogger(__name__)
class MyService(LoggingMixin):
def instance_method(self):
self.log_info("instance.event") # ✓ Automatic injection
@classmethod
def class_method(cls):
cid = get_correlation_id()
logger.info("class.event", extra={"correlation_id": cid or "-"}) # ✓ Manual injection
Correlation ID lifecycle
Correlation IDs are stored in a contextvars.ContextVar, which means they:
- Survive async/await boundaries (async-safe)
- Cross thread boundaries when using thread pool executors
- Are automatically reset at the start of each HTTP request (Django/FastAPI middleware)
- Propagate to background tasks (Celery, threading, async)
Composition with masking
LoggingMixin automatically composes with masking libraries. If your instance has a mask_for_logging() method (e.g., from a masking mixin), its output is added to the log record:
from logging_mixin import LoggingMixin
from some_library import MaskingMixin
class Response(LoggingMixin, MaskingMixin):
def trace(self):
self.log_debug("response")
# Logs with extra: {"correlation_id": "...", "instance": <masked dict>}
API Reference
LoggingMixin
Class-bound logger providing five methods:
class Service(LoggingMixin):
def do_thing(self):
self.log_debug("event", detail="verbose") # DEBUG level
self.log_info("event", status="ok") # INFO level
self.log_warning("event", issue="slow") # WARNING level
self.log_error("event", error="failure") # ERROR level
self.log_exception("event") # ERROR level + traceback
Each method:
- Takes an event name (string) and optional keyword arguments
- Automatically injects correlation_id into the log record's
extradict - Reads from the per-class logger (
module.ClassName) - Composes with
mask_for_logging()if available
Context functions
from logging_mixin import get_correlation_id, set_correlation_id, clear_correlation_id
# Get the current correlation ID (returns None if not set)
cid = get_correlation_id()
# Manually set (for background tasks, tests, non-request contexts)
set_correlation_id("abc123def456")
# Clear (useful for test isolation)
clear_correlation_id()
Framework adapters
Django middleware
Automatically sets correlation ID from X-Correlation-ID header or generates UUID.
from logging_mixin.adapters.django import CorrelationIdMiddleware
MIDDLEWARE = ["logging_mixin.adapters.django.CorrelationIdMiddleware", ...]
FastAPI dependency
Two approaches:
# Middleware (auto for all routes):
from logging_mixin.adapters.fastapi import correlation_id_middleware
app.add_middleware(correlation_id_middleware)
# Or dependency (per-route opt-in):
from fastapi import Depends
from logging_mixin.adapters.fastapi import correlation_id_dependency
@app.get("/items/")
def get_items(cid: str = Depends(correlation_id_dependency)):
...
AWS Lambda
from logging_mixin.adapters.aws_lambda import setup_correlation_id
def lambda_handler(event, context):
setup_correlation_id(event, context)
# Now LoggingMixin can access correlation_id via get_correlation_id()
...
Testing
LoggingMixin is test-friendly:
import logging
from logging_mixin import LoggingMixin, set_correlation_id
class TestMyService:
def test_logs_with_correlation_id(self, caplog):
set_correlation_id("test-123")
service = MyService()
with caplog.at_level(logging.INFO):
service.do_something()
assert caplog.records[0].correlation_id == "test-123"
Design Principles
- Class-bound: Each class gets its own logger (
module.ClassName) for clean grouping - Instance-only: Methods read
self._logger(cannot be used from @classmethod/@staticmethod) - Async-safe: Uses
contextvars.ContextVar(survives async/await, thread pools) - Framework-agnostic: Core mixin has zero framework dependencies
- Composable: Works naturally with masking mixins and other mixins
- Zero boilerplate: No function signature changes needed
Trade-offs
- Cannot use in @classmethod/@staticmethod: Use module-level logger + manual correlation ID injection instead
- Requires ContextVar setup: Framework adapters or manual
set_correlation_id()call needed - Implicit behavior: Correlation ID is silently injected (can be surprising if not expected)
License
Apache 2.0 — see LICENSE file.
See Also
- contextvars — Python standard library
- logging — Python standard library
- Django middleware — Django framework
- FastAPI middleware — FastAPI framework
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file logging_mixin-0.1.0.tar.gz.
File metadata
- Download URL: logging_mixin-0.1.0.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e31326780a782bba9987e4f90d5ef9fbffbcbccc0ef1aa5fcc97c8daf3efc3d0
|
|
| MD5 |
4d1d17a65ecb3525a3ec80a6976a58c0
|
|
| BLAKE2b-256 |
ebfbeca1e9f39647664eb397bdb8e86216510016d5147f23dd382671043948a6
|
File details
Details for the file logging_mixin-0.1.0-py3-none-any.whl.
File metadata
- Download URL: logging_mixin-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.25
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3adb75c86d7a5e19646af28a404f5997faac70c24f7919033d23d0b18d9c2500
|
|
| MD5 |
3b2e7a5a3b5bcbb45ea5839f9cc14627
|
|
| BLAKE2b-256 |
c597e71fbde1f28a3002a6f94a28ed08027a39d2738cb4e5cb4850b43dc9feb9
|