- Add structured logging config with JSON/text format support - Create docker-entrypoint.sh with dependency health checks - Enhance /health endpoint with DB and Redis connectivity checks - Add resource limits (CPU/memory) for all services - Add network aliases for service discovery - Add container hostnames for better identification - Reduce health check timeout from 20s to 5s - Add build metadata labels to Dockerfile - Use ENTRYPOINT for dependency checking before app startup - Log rotation: 50m per file, 5 files max
98 lines
3.0 KiB
Python
98 lines
3.0 KiB
Python
"""
|
|
Structured logging configuration for MTG Online backend.
|
|
|
|
Supports JSON and text log formats with container metadata.
|
|
Configured via MTG_LOG_LEVEL and MTG_LOG_FORMAT environment variables.
|
|
"""
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
def _get_container_name() -> str:
|
|
"""Get container name from HOSTNAME env var or fallback."""
|
|
return os.environ.get("HOSTNAME", "unknown")
|
|
|
|
|
|
class ContainerFormatter(logging.Formatter):
|
|
"""Formatter that adds container metadata to log records.
|
|
|
|
Supports both text and JSON output formats.
|
|
"""
|
|
|
|
SERVICE_NAME = "mtgonline-backend"
|
|
|
|
def __init__(self, fmt_type: str = "text"):
|
|
super().__init__()
|
|
self.fmt_type = fmt_type
|
|
self.container_name = _get_container_name()
|
|
|
|
def format(self, record: logging.LogRecord) -> str:
|
|
record.service = self.SERVICE_NAME
|
|
record.container = self.container_name
|
|
|
|
if self.fmt_type == "json":
|
|
return self._format_json(record)
|
|
return self._format_text(record)
|
|
|
|
def _format_text(self, record: logging.LogRecord) -> str:
|
|
return (
|
|
f"{self.formatTime(record)} [{record.service}] "
|
|
f"{record.levelname} - {record.getMessage()}"
|
|
)
|
|
|
|
def _format_json(self, record: logging.LogRecord) -> str:
|
|
log_entry = {
|
|
"timestamp": datetime.fromtimestamp(
|
|
record.created, tz=timezone.utc
|
|
).isoformat(),
|
|
"level": record.levelname,
|
|
"service": record.service,
|
|
"container": record.container,
|
|
"logger": record.name,
|
|
"message": record.getMessage(),
|
|
}
|
|
if record.exc_info and record.exc_info[0] is not None:
|
|
log_entry["exception"] = self.formatException(record.exc_info)
|
|
return json.dumps(log_entry, default=str)
|
|
|
|
|
|
def configure_logging() -> None:
|
|
"""Configure application logging based on environment variables.
|
|
|
|
Reads:
|
|
MTG_LOG_LEVEL - Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
MTG_LOG_FORMAT - Output format ("text" or "json")
|
|
"""
|
|
log_level = os.environ.get("MTG_LOG_LEVEL", "INFO").upper()
|
|
log_format = os.environ.get("MTG_LOG_FORMAT", "text").lower()
|
|
|
|
level = getattr(logging, log_level, logging.INFO)
|
|
|
|
formatter = ContainerFormatter(fmt_type=log_format)
|
|
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(formatter)
|
|
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(level)
|
|
root_logger.handlers.clear()
|
|
root_logger.addHandler(handler)
|
|
|
|
# Suppress noisy loggers unless DEBUG
|
|
if level != logging.DEBUG:
|
|
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)
|
|
logging.getLogger("sqlalchemy.pool").setLevel(logging.WARNING)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger.info(
|
|
f"Logging configured: level={log_level}, "
|
|
f"format={log_format}, container={_get_container_name()}"
|
|
)
|
|
|
|
|
|
# Auto-configure on import
|
|
configure_logging()
|