"""
Structured logging setup using structlog.
This provides JSON-formatted logs with automatic context injection,
making it easy to search and analyze logs in production.
Benefits:
- Structured JSON logs (easy to parse)
- Automatic trace ID injection
- Consistent log format across all modules
- Performance-optimized
"""
from __future__ import annotations
import logging
import sys
from typing import Any, Dict
import structlog
from structlog.types import EventDict, WrappedLogger
from src.config import settings
def add_app_context(
logger: WrappedLogger, method_name: str, event_dict: EventDict
) -> EventDict:
"""
Add application-wide context to every log entry.
This automatically adds:
- Application name
- Environment (dev/staging/prod)
- Service version
"""
event_dict["app"] = "updation_mcp"
event_dict["environment"] = settings.environment
event_dict["version"] = "1.0.0"
return event_dict
def setup_logging() -> None:
"""
Configure structured logging for the application.
Call this once at application startup.
"""
# Configure standard library logging
logging.basicConfig(
format="%(message)s",
stream=sys.stdout,
level=getattr(logging, settings.log_level),
)
# Determine processors based on format
if settings.log_format == "json":
# Production: JSON logs
processors = [
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.processors.TimeStamper(fmt="iso"),
add_app_context,
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.processors.UnicodeDecoder(),
structlog.processors.JSONRenderer(),
]
else:
# Development: Human-readable console logs
processors = [
structlog.contextvars.merge_contextvars,
structlog.stdlib.add_log_level,
structlog.stdlib.add_logger_name,
structlog.processors.TimeStamper(fmt="iso"),
add_app_context,
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.dev.ConsoleRenderer(),
]
# Configure structlog
structlog.configure(
processors=processors,
wrapper_class=structlog.stdlib.BoundLogger,
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
def get_logger(name: str = None) -> structlog.stdlib.BoundLogger:
"""
Get a logger instance.
Args:
name: Logger name (usually __name__)
Returns:
Configured structlog logger
Example:
```python
logger = get_logger(__name__)
logger.info("user_logged_in", user_id=123, ip="1.2.3.4")
```
"""
return structlog.get_logger(name)
# Setup logging on module import
setup_logging()