File size: 2,301 Bytes
46bca93 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | # /core/logging.py
from __future__ import annotations
import json
import logging
import sys
from datetime import datetime
from typing import Optional
try:
# Optional: human-friendly console colors if installed
import colorama # type: ignore
colorama.init()
_HAS_COLOR = True
except Exception: # pragma: no cover
_HAS_COLOR = False
# Very small JSON formatter (avoids extra deps)
class JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str: # type: ignore[override]
payload = {
"ts": datetime.utcfromtimestamp(record.created).isoformat(timespec="milliseconds") + "Z",
"level": record.levelname,
"logger": record.name,
"msg": record.getMessage(),
}
if record.exc_info:
payload["exc_info"] = self.formatException(record.exc_info)
return json.dumps(payload, ensure_ascii=False)
class ConsoleFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str: # type: ignore[override]
ts = datetime.utcfromtimestamp(record.created).strftime("%H:%M:%S")
lvl = record.levelname
name = record.name
msg = record.getMessage()
if _HAS_COLOR:
COLORS = {
"DEBUG": "\033[37m",
"INFO": "\033[36m",
"WARNING": "\033[33m",
"ERROR": "\033[31m",
"CRITICAL": "\033[41m",
}
RESET = "\033[0m"
color = COLORS.get(lvl, "")
return f"{ts} {color}{lvl:<8}{RESET} {name}: {msg}"
return f"{ts} {lvl:<8} {name}: {msg}"
_initialized = False
def setup_logging(level: str = "INFO", json_logs: bool = False) -> None:
"""
Initialize root logger once.
"""
global _initialized
if _initialized:
return
_initialized = True
root = logging.getLogger()
root.setLevel(level.upper())
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter() if json_logs else ConsoleFormatter())
root.handlers[:] = [handler]
def get_logger(name: Optional[str] = None) -> logging.Logger:
"""
Get a logger (call setup_logging() first to configure formatting).
"""
return logging.getLogger(name or "app")
|