Context Management¶
Methods for adding contextual information to log messages.
Overview¶
Logly supports two context management approaches:
Method | Scope | Use Case |
---|---|---|
bind() | Global | Add fields to all subsequent logs |
contextualize() | Scoped | Add fields within context manager |
All context fields: - ✅ Automatically included in log output - ✅ Support JSON mode (nested under fields
) - ✅ Can be overridden by method kwargs - ✅ Persist across async boundaries
logger.bind()¶
Add fields globally that persist across all log calls.
Signature¶
Parameters¶
**kwargs
: Key-value pairs to bind
Returns¶
PyLogger
: New logger instance with bound context
Examples¶
from logly import logger
# Bind request context
request_logger = logger.bind(
request_id="abc-123",
user_id="user-456",
ip="192.168.1.1"
)
# All logs include context
request_logger.info("Processing request")
# Output: ... | Processing request request_id=abc-123 user_id=user-456 ip=192.168.1.1
request_logger.error("Request failed", status_code=500)
# Output: ... | Request failed request_id=abc-123 user_id=user-456 ip=192.168.1.1 status_code=500
Notes¶
When to Use bind()
- Web Requests: Bind request_id, user_id at start of request
- Background Jobs: Bind job_id, worker_id
- Microservices: Bind service name, version, environment
- Multi-tenancy: Bind tenant_id, organization_id
Immutability
bind()
returns a new logger instance. The original logger is unchanged:
Memory Efficiency
Bound loggers share the same underlying backend. Creating many bound loggers is efficient.
logger.contextualize()¶
Add fields within a scoped context manager. Fields are automatically removed when exiting the context.
Signature¶
Parameters¶
**kwargs
: Key-value pairs to add within context
Returns¶
ContextManager
: Context manager that adds/removes fields
Examples¶
from logly import logger
def process_order(order_id: int):
with logger.contextualize(order_id=order_id, stage="processing"):
logger.info("Starting order processing")
# ... processing logic ...
logger.info("Order validated")
logger.info("Payment processed")
# Context removed after exiting
logger.info("Order complete")
def handle_request(request_id: str, user_id: str):
with logger.contextualize(request_id=request_id, user_id=user_id):
logger.info("Request received")
try:
result = process_request()
logger.success("Request succeeded")
return result
except Exception as e:
logger.error("Request failed", error=str(e))
raise
# request_id and user_id no longer in context
Notes¶
When to Use contextualize()
- Function Scope: Add context for a specific function
- Request Handlers: Add request-specific context
- Nested Operations: Add context for sub-operations
- Try/Except Blocks: Add context for error handling
Automatic Cleanup
Context is automatically removed when exiting the with
block, even if an exception occurs:
Thread Safety
Context is thread-local. Each thread has its own context stack:
Context Priority¶
When the same field is defined in multiple places, the priority is:
Highest to Lowest:
- Method kwargs (direct argument to log call)
- contextualize() (innermost context)
- bind() (bound context)
- Global configuration
Example¶
logger.bind(x=1) # Priority 3
with logger.contextualize(x=2): # Priority 2
logger.info("Test", x=3) # Priority 1 (wins)
# Output: ... | Test x=3
Complete Example¶
from logly import logger
# Configure
logger.configure(level="INFO", json=True)
logger.add("console")
logger.add("logs/app.log")
# Global context with bind()
service_logger = logger.bind(
service="api",
version="1.0.0",
environment="production"
)
def handle_request(request_id: str, user_id: str):
# Scoped context with contextualize()
with service_logger.contextualize(request_id=request_id, user_id=user_id):
service_logger.info("Request received")
try:
result = process_request()
service_logger.success("Request succeeded", duration_ms=42)
return result
except Exception as e:
# Include exception details
service_logger.error("Request failed", error=str(e), error_type=type(e).__name__)
raise
# Usage
handle_request("req-123", "alice")
# Cleanup
logger.complete()
Best Practices¶
✅ DO¶
# 1. Use bind() for long-lived context
request_logger = logger.bind(request_id=request_id)
# 2. Use contextualize() for scoped context
with logger.contextualize(stage="validation"):
logger.info("Validating input")
# 3. Chain bind() for readability
logger.bind(service="api").bind(version="1.0").info("Started")
# 4. Include exception details in errors
try:
risky_op()
except Exception as e:
logger.error("Failed", error=str(e), error_type=type(e).__name__)
❌ DON'T¶
# 1. Don't forget bind() returns new logger
logger.bind(x=1) # ❌ Doesn't modify logger
logger.info("Test") # ❌ x=1 NOT included
bound = logger.bind(x=1) # ✅ Correct
bound.info("Test") # ✅ x=1 included
# 2. Don't use contextualize() for global context
with logger.contextualize(service="api"): # ❌ Too broad
# ... entire application ...
logger_service = logger.bind(service="api") # ✅ Use bind()
# 3. Don't nest too many contexts
with logger.contextualize(a=1):
with logger.contextualize(b=2):
with logger.contextualize(c=3): # ❌ Hard to follow
logger.info("Deep nesting")