application / utils /logging_setup.py
Krixter's picture
Upload 10 files
18b6a95 verified
Raw History Blame Contribute Delete
2.08 kB
"""
Logging system: in-memory ring buffer + file output.
"""
from __future__ import annotations
import logging
import time
from collections import deque
from pathlib import Path
from typing import Optional
class RingBufferHandler(logging.Handler):
"""Keeps last N log lines in memory for UI display."""
def __init__(self, maxlen: int = 500):
super().__init__()
self._buffer: deque[str] = deque(maxlen=maxlen)
def emit(self, record: logging.LogRecord) -> None:
msg = self.format(record)
self._buffer.append(msg)
def get_lines(self, last: int = 100) -> list[str]:
lines = list(self._buffer)
return lines[-last:] if last else lines
def get_text(self, last: int = 100) -> str:
return "\n".join(self.get_lines(last))
def clear(self) -> None:
self._buffer.clear()
class AppLogger:
_instance: Optional["AppLogger"] = None
def __init__(self, log_dir: Path = Path("output")):
self.log_dir = log_dir
self.log_dir.mkdir(parents=True, exist_ok=True)
self.log_file = log_dir / "logs.txt"
self.ring = RingBufferHandler(maxlen=1000)
self.ring.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s", "%H:%M:%S"))
file_handler = logging.FileHandler(self.log_file, encoding="utf-8")
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
root = logging.getLogger()
root.setLevel(logging.INFO)
root.addHandler(self.ring)
root.addHandler(file_handler)
@classmethod
def get(cls, log_dir: Path = Path("output")) -> "AppLogger":
if cls._instance is None:
cls._instance = cls(log_dir)
return cls._instance
def ui_log(self, msg: str) -> None:
"""Add a message directly to the ring buffer (bypassing Python log level)."""
ts = time.strftime("%H:%M:%S")
self.ring._buffer.append(f"{ts} {msg}")
def get_ui_text(self, last: int = 200) -> str:
return self.ring.get_text(last)