first commit
This commit is contained in:
255
app/core/logging_config.py
Normal file
255
app/core/logging_config.py
Normal file
@ -0,0 +1,255 @@
|
||||
"""
|
||||
Logging Configuration
|
||||
|
||||
Provides structured logging setup for the application.
|
||||
Based on Python Quick Start Guide best practices.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def setup_logging(
|
||||
log_level: str = "INFO",
|
||||
environment: str = "development"
|
||||
) -> None:
|
||||
"""
|
||||
Configure application logging.
|
||||
|
||||
Args:
|
||||
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
environment: Environment name (development, production)
|
||||
|
||||
Usage:
|
||||
setup_logging(log_level="INFO", environment="production")
|
||||
"""
|
||||
level = getattr(logging, log_level.upper(), logging.INFO)
|
||||
|
||||
# Clear existing handlers
|
||||
root_logger = logging.getLogger()
|
||||
for handler in root_logger.handlers[:]:
|
||||
root_logger.removeHandler(handler)
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(level)
|
||||
|
||||
# Format based on environment
|
||||
if environment == "development":
|
||||
# Human-readable format for development
|
||||
formatter = logging.Formatter(
|
||||
fmt='%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d | %(message)s',
|
||||
datefmt='%H:%M:%S'
|
||||
)
|
||||
else:
|
||||
# Structured format for production (easier to parse)
|
||||
formatter = logging.Formatter(
|
||||
fmt='%(asctime)s | %(levelname)s | %(name)s | %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
console_handler.setFormatter(formatter)
|
||||
root_logger.addHandler(console_handler)
|
||||
root_logger.setLevel(level)
|
||||
|
||||
# Suppress noisy loggers
|
||||
logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
|
||||
|
||||
def get_logger(name: str) -> logging.Logger:
|
||||
"""
|
||||
Get a logger instance.
|
||||
|
||||
Args:
|
||||
name: Logger name (typically __name__)
|
||||
|
||||
Returns:
|
||||
Logger instance
|
||||
|
||||
Example:
|
||||
logger = get_logger(__name__)
|
||||
logger.info("User logged in", extra={"user_id": 123})
|
||||
"""
|
||||
return logging.getLogger(name)
|
||||
|
||||
|
||||
# ==========================================
|
||||
# Structured Logging Helpers
|
||||
# ==========================================
|
||||
|
||||
def log_audit_event(
|
||||
logger: logging.Logger,
|
||||
action: str,
|
||||
user_id: Optional[int] = None,
|
||||
ip_address: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Log an audit event with structured data.
|
||||
|
||||
Args:
|
||||
logger: Logger instance
|
||||
action: Action performed (e.g., "login_success", "user_created")
|
||||
user_id: User ID performing action
|
||||
ip_address: IP address of request
|
||||
**kwargs: Additional context data
|
||||
|
||||
Example:
|
||||
log_audit_event(
|
||||
logger,
|
||||
action="login_success",
|
||||
user_id=123,
|
||||
ip_address="192.168.1.1",
|
||||
username="admin"
|
||||
)
|
||||
"""
|
||||
extra_data = {
|
||||
'action': action,
|
||||
'user_id': user_id,
|
||||
'ip_address': ip_address,
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
**kwargs
|
||||
}
|
||||
|
||||
# Filter out None values
|
||||
extra_data = {k: v for k, v in extra_data.items() if v is not None}
|
||||
|
||||
logger.info(f"AUDIT: {action}", extra=extra_data)
|
||||
|
||||
|
||||
def log_security_event(
|
||||
logger: logging.Logger,
|
||||
event_type: str,
|
||||
severity: str = "warning",
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Log a security-related event.
|
||||
|
||||
Args:
|
||||
logger: Logger instance
|
||||
event_type: Type of security event (e.g., "failed_login", "rate_limit_exceeded")
|
||||
severity: Severity level (debug, info, warning, error, critical)
|
||||
**kwargs: Additional context data
|
||||
|
||||
Example:
|
||||
log_security_event(
|
||||
logger,
|
||||
event_type="failed_login",
|
||||
severity="warning",
|
||||
username="admin",
|
||||
ip_address="192.168.1.1",
|
||||
attempts=3
|
||||
)
|
||||
"""
|
||||
extra_data = {
|
||||
'event_type': event_type,
|
||||
'timestamp': datetime.utcnow().isoformat(),
|
||||
**kwargs
|
||||
}
|
||||
|
||||
log_method = getattr(logger, severity.lower(), logger.warning)
|
||||
log_method(f"SECURITY: {event_type}", extra=extra_data)
|
||||
|
||||
|
||||
# ==========================================
|
||||
# Request Logging Helpers
|
||||
# ==========================================
|
||||
|
||||
def log_request(
|
||||
logger: logging.Logger,
|
||||
method: str,
|
||||
path: str,
|
||||
status_code: int,
|
||||
duration_ms: float,
|
||||
user_id: Optional[int] = None
|
||||
) -> None:
|
||||
"""
|
||||
Log an HTTP request.
|
||||
|
||||
Args:
|
||||
logger: Logger instance
|
||||
method: HTTP method (GET, POST, etc.)
|
||||
path: Request path
|
||||
status_code: HTTP status code
|
||||
duration_ms: Request duration in milliseconds
|
||||
user_id: Authenticated user ID (if any)
|
||||
|
||||
Example:
|
||||
log_request(
|
||||
logger,
|
||||
method="POST",
|
||||
path="/api/users",
|
||||
status_code=201,
|
||||
duration_ms=45.2,
|
||||
user_id=123
|
||||
)
|
||||
"""
|
||||
extra_data = {
|
||||
'method': method,
|
||||
'path': path,
|
||||
'status_code': status_code,
|
||||
'duration_ms': round(duration_ms, 2),
|
||||
'user_id': user_id
|
||||
}
|
||||
|
||||
# Filter out None values
|
||||
extra_data = {k: v for k, v in extra_data.items() if v is not None}
|
||||
|
||||
# Use different log levels based on status code
|
||||
if status_code >= 500:
|
||||
logger.error(f"{method} {path} {status_code}", extra=extra_data)
|
||||
elif status_code >= 400:
|
||||
logger.warning(f"{method} {path} {status_code}", extra=extra_data)
|
||||
else:
|
||||
logger.info(f"{method} {path} {status_code}", extra=extra_data)
|
||||
|
||||
|
||||
# ==========================================
|
||||
# Error Logging Helpers
|
||||
# ==========================================
|
||||
|
||||
def log_exception(
|
||||
logger: logging.Logger,
|
||||
error: Exception,
|
||||
context: Optional[str] = None,
|
||||
**kwargs
|
||||
) -> None:
|
||||
"""
|
||||
Log an exception with context.
|
||||
|
||||
Args:
|
||||
logger: Logger instance
|
||||
error: Exception instance
|
||||
context: Additional context about where error occurred
|
||||
**kwargs: Additional context data
|
||||
|
||||
Example:
|
||||
try:
|
||||
# Some operation
|
||||
except Exception as e:
|
||||
log_exception(
|
||||
logger,
|
||||
error=e,
|
||||
context="Failed to create user",
|
||||
user_id=123
|
||||
)
|
||||
"""
|
||||
extra_data = {
|
||||
'error_type': type(error).__name__,
|
||||
'error_message': str(error),
|
||||
'context': context,
|
||||
**kwargs
|
||||
}
|
||||
|
||||
# Filter out None values
|
||||
extra_data = {k: v for k, v in extra_data.items() if v is not None}
|
||||
|
||||
logger.error(
|
||||
f"Exception: {type(error).__name__}: {str(error)}",
|
||||
extra=extra_data,
|
||||
exc_info=True
|
||||
)
|
||||
Reference in New Issue
Block a user