GodSpeed / src /utils /logger.py
Samyuktha24's picture
feat: add Zustand stores for filters and UI state management
9dfccd9
"""Structured JSON logger for Godspeed backend.
Usage:
from src.utils.logger import get_logger
logger = get_logger(__name__)
logger.info("graph_ingest_done", ingested=42, duration_ms=1230)
Every log line is a JSON object with at minimum:
time, level, logger, request_id (from ContextVar), message, **extra_fields
"""
from __future__ import annotations
import json
import logging
import time
from contextvars import ContextVar
from typing import Any
# Set per-request by RequestLoggingMiddleware
request_id_var: ContextVar[str] = ContextVar("request_id", default="-")
class _JsonFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
log: dict[str, Any] = {
"time": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
"level": record.levelname,
"logger": record.name,
"request_id": request_id_var.get(),
"message": record.getMessage(),
}
# Extra structured fields attached via logger.info("msg", extra={...})
for key, val in record.__dict__.items():
if key not in {
"name", "msg", "args", "levelname", "levelno", "pathname",
"filename", "module", "exc_info", "exc_text", "stack_info",
"lineno", "funcName", "created", "msecs", "relativeCreated",
"thread", "threadName", "processName", "process", "message",
"taskName",
}:
log[key] = val
if record.exc_info:
log["exc"] = self.formatException(record.exc_info)
return json.dumps(log, default=str)
def _configure_root() -> None:
root = logging.getLogger()
if root.handlers:
return
handler = logging.StreamHandler()
handler.setFormatter(_JsonFormatter())
root.addHandler(handler)
root.setLevel(logging.INFO)
_configure_root()
def get_logger(name: str) -> logging.Logger:
return logging.getLogger(name)
class Timer:
"""Context manager that measures elapsed ms."""
def __init__(self) -> None:
self._start = 0.0
def __enter__(self) -> "Timer":
self._start = time.perf_counter()
return self
def __exit__(self, *_: Any) -> None:
pass
@property
def ms(self) -> int:
return int((time.perf_counter() - self._start) * 1000)