Spaces:
Configuration error
Configuration error
| """ | |
| Production-grade logging configuration for Analytics Microservice. | |
| """ | |
| import json | |
| import logging | |
| import logging.handlers | |
| import os | |
| import sys | |
| import traceback | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| from typing import Any, Dict, Optional | |
| SERVICE_NAME = "analytics-ms" | |
| LOG_DIR = Path(os.getenv("LOG_DIR", "logs")) | |
| LOG_MAX_BYTES = int(os.getenv("LOG_MAX_BYTES", 50 * 1024 * 1024)) | |
| LOG_BACKUP_COUNT = int(os.getenv("LOG_BACKUP_COUNT", "10")) | |
| class JSONFormatter(logging.Formatter): | |
| """Emit log records as single-line JSON objects.""" | |
| RESERVED = frozenset({ | |
| "args", "created", "exc_info", "exc_text", "filename", | |
| "funcName", "levelname", "levelno", "lineno", "message", | |
| "module", "msecs", "msg", "name", "pathname", "process", | |
| "processName", "relativeCreated", "stack_info", "thread", "threadName", | |
| }) | |
| def format(self, record: logging.LogRecord) -> str: | |
| payload: Dict[str, Any] = { | |
| "timestamp": datetime.fromtimestamp(record.created, tz=timezone.utc).isoformat(), | |
| "level": record.levelname, | |
| "logger": record.name, | |
| "message": record.getMessage(), | |
| "service": SERVICE_NAME, | |
| "pid": record.process, | |
| } | |
| if record.levelno >= logging.WARNING: | |
| payload["caller"] = f"{record.pathname}:{record.lineno}" | |
| for key, val in record.__dict__.items(): | |
| if key not in self.RESERVED and not key.startswith("_"): | |
| payload[key] = val | |
| if record.exc_info and record.exc_info[0] is not None: | |
| exc_type, exc_value, exc_tb = record.exc_info | |
| payload["exception"] = { | |
| "type": exc_type.__name__, | |
| "message": str(exc_value), | |
| "stacktrace": traceback.format_exception(exc_type, exc_value, exc_tb), | |
| } | |
| try: | |
| return json.dumps(payload, default=str) | |
| except Exception: | |
| payload["message"] = str(record.getMessage()) | |
| return json.dumps(payload, default=str) | |
| class ConsoleFormatter(logging.Formatter): | |
| GREY = "\x1b[38;5;240m" | |
| CYAN = "\x1b[36m" | |
| YELLOW = "\x1b[33m" | |
| RED = "\x1b[31m" | |
| BOLD_RED = "\x1b[1;31m" | |
| RESET = "\x1b[0m" | |
| LEVEL_COLORS = { | |
| logging.DEBUG: "\x1b[38;5;240m", | |
| logging.INFO: "\x1b[36m", | |
| logging.WARNING: "\x1b[33m", | |
| logging.ERROR: "\x1b[31m", | |
| logging.CRITICAL: "\x1b[1;31m", | |
| } | |
| def format(self, record: logging.LogRecord) -> str: | |
| color = self.LEVEL_COLORS.get(record.levelno, self.RESET) | |
| ts = datetime.fromtimestamp(record.created, tz=timezone.utc).strftime("%H:%M:%S") | |
| msg = record.getMessage() | |
| base = f"{self.GREY}{ts}{self.RESET} {color}{record.levelname:<8}{self.RESET} {record.name} - {msg}" | |
| if record.exc_info: | |
| base += "\n" + self.formatException(record.exc_info) | |
| return base | |
| def setup_logging(level: str = "INFO") -> None: | |
| numeric_level = getattr(logging, level.upper(), logging.INFO) | |
| root = logging.getLogger() | |
| root.setLevel(numeric_level) | |
| root.handlers.clear() | |
| # Console handler | |
| ch = logging.StreamHandler(sys.stdout) | |
| ch.setLevel(numeric_level) | |
| is_json = os.getenv("LOG_FORMAT", "json").lower() == "json" | |
| ch.setFormatter(JSONFormatter() if is_json else ConsoleFormatter()) | |
| root.addHandler(ch) | |
| # File handlers | |
| try: | |
| LOG_DIR.mkdir(parents=True, exist_ok=True) | |
| fh = logging.handlers.RotatingFileHandler( | |
| LOG_DIR / "app.log", maxBytes=LOG_MAX_BYTES, backupCount=LOG_BACKUP_COUNT | |
| ) | |
| fh.setLevel(numeric_level) | |
| fh.setFormatter(JSONFormatter()) | |
| root.addHandler(fh) | |
| eh = logging.handlers.RotatingFileHandler( | |
| LOG_DIR / "app_errors.log", maxBytes=LOG_MAX_BYTES, backupCount=LOG_BACKUP_COUNT | |
| ) | |
| eh.setLevel(logging.ERROR) | |
| eh.setFormatter(JSONFormatter()) | |
| root.addHandler(eh) | |
| except Exception: | |
| pass # Non-critical if file logging fails | |
| def get_logger(name: str) -> logging.Logger: | |
| return logging.getLogger(name) | |