MukeshKapoor25's picture
Initial commit
b143975
"""
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)