Files
akadmin 900f51af3a Complete architecture simplification: 54+ files → 37 files
- Removed 11 duplicate router files (auth.py, users.py, decks.py, card_router.py, interactions.py, user_data.py, rooms.py, games/router.py, refresh.py, card_import.py, ws.py)
- Removed duplicate core files (logging_config.py, settings.py)
- Removed unused services (card_mirror_service.py, mtgjson_loader.py, mtgjson_uploader.py)
- Removed unused schemas (proto_messages.py, protocol_constants.py)
- Created run.py startup script for simple execution
- Created app/core/config.py for unified configuration management
- Created app/core/logging.py for logging configuration
- Consolidated routers into 6 focused files (auth_router.py, users_router.py, decks_router.py, cards_router.py, user_data_router.py, games_router.py)
- Updated all imports to use consolidated model paths
- Added backward compatibility aliases in database.py
- Created SIMPLIFICATION_SUMMARY.md documentation

Architecture improvements:
- Single unified database engine with auto-detection (SQLite/PostgreSQL)
- Consolidated models into 2 files (models.py, mtg_models.py)
- Clean router structure with one file per domain
- Simplified configuration with sensible defaults
- SQLite by default for development, PostgreSQL for production

Testing:
- All imports verified working
- App loads successfully
- Ready for development and testing
2026-08-26 15:15:36 +00:00

188 lines
5.8 KiB
Python

"""
Logging configuration for MTG Online backend.
Provides structured logging with support for both text and JSON output formats.
Configured via environment variables or the application config.
## Configuration
| Variable | Default | Description |
|---|---|---|
| LOG_LEVEL | INFO | Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| LOG_FORMAT | text | Output format ("text" or "json") |
| MTG_LOG_LEVEL | INFO | Alternative env var for log level |
| MTG_LOG_FORMAT | text | Alternative env var for log format |
## Usage
from app.core.logging import configure_logging
# Auto-configures on import, or call explicitly:
configure_logging()
## Features
- Service name "mtgonline-backend" added to all log records
- Container name from HOSTNAME env var (for Docker)
- Suppresses noisy SQLAlchemy loggers unless DEBUG
- JSON format includes timestamp, level, service, container, logger, message
- Text format: `[timestamp] [service] LEVEL - message`
"""
import json
import logging
import os
import sys
from datetime import datetime, timezone
from typing import Optional
def _get_container_name() -> str:
"""Get container name from HOSTNAME environment variable.
Returns:
Container name or "unknown" if not available.
"""
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. Adds service name
"mtgonline-backend" and container name to all log records.
Attributes:
SERVICE_NAME: Service identifier added to all log records.
fmt_type: Output format ("text" or "json").
container_name: Container identifier from HOSTNAME env var.
"""
SERVICE_NAME = "mtgonline-backend"
def __init__(self, fmt_type: str = "text") -> None:
"""Initialize the formatter.
Args:
fmt_type: Output format, either "text" or "json".
"""
super().__init__()
self.fmt_type = fmt_type
self.container_name = _get_container_name()
def format(self, record: logging.LogRecord) -> str:
"""Format a log record with service and container metadata.
Args:
record: The log record to format.
Returns:
Formatted log string in the configured format.
"""
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:
"""Format log record as human-readable text.
Args:
record: The log record to format.
Returns:
Text-formatted log string.
"""
return (
f"{self.formatTime(record)} [{record.service}] "
f"{record.levelname} - {record.getMessage()}"
)
def _format_json(self, record: logging.LogRecord) -> str:
"""Format log record as JSON.
Includes timestamp, level, service, container, logger, and message.
Adds exception information if present.
Args:
record: The log record to format.
Returns:
JSON-formatted log string.
"""
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(
log_level: Optional[str] = None,
log_format: Optional[str] = None,
) -> None:
"""Configure application logging.
Reads configuration from environment variables or uses defaults.
Sets up a stream handler with the configured formatter.
Suppresses noisy SQLAlchemy loggers unless DEBUG level is enabled.
Args:
log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
Overrides MTG_LOG_LEVEL and LOG_FORMAT env vars if provided.
log_format: Output format ("text" or "json").
Overrides MTG_LOG_FORMAT and LOG_FORMAT env vars if provided.
Example:
>>> configure_logging() # Use env vars or defaults
>>> configure_logging(log_level="DEBUG", log_format="json")
"""
# Determine log level
if log_level is None:
log_level = os.environ.get("MTG_LOG_LEVEL", os.environ.get("LOG_LEVEL", "INFO")).upper()
# Determine log format
if log_format is None:
log_format = os.environ.get("MTG_LOG_FORMAT", os.environ.get("LOG_FORMAT", "text")).lower()
# Validate inputs
level = getattr(logging, log_level, logging.INFO)
if log_format not in ("text", "json"):
log_format = "text"
# Create formatter and handler
formatter = ContainerFormatter(fmt_type=log_format)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
# Configure root logger
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()