Spaces:
Running
Running
| """Logging โ ๋กํ ์ดํ ํ์ผ + in-memory ring buffer + ๋ ๋ฒจ ํํฐ. | |
| UI ์ "๋ก๊ทธ" ํ์ด์ง๊ฐ `recent()` ๊ฒฐ๊ณผ๋ฅผ ํด๋งํด์ ํ์. | |
| ํ์ผ์ `logs/app.log` (1MB ๋จ์ ๋กค๋ง, ์ต๊ทผ 3๊ฐ ๋ณด์กด). | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import logging.handlers | |
| import time | |
| from collections import deque | |
| from pathlib import Path | |
| from threading import Lock | |
| LOG_DIR = Path(__file__).resolve().parent / "logs" | |
| LOG_DIR.mkdir(exist_ok=True) | |
| LOG_FILE = LOG_DIR / "app.log" | |
| _BUFFER: deque = deque(maxlen=600) | |
| _buf_lock = Lock() | |
| LEVEL_RANK = {"DEBUG": 0, "INFO": 1, "WARNING": 2, "ERROR": 3, "CRITICAL": 4} | |
| class MemoryHandler(logging.Handler): | |
| """In-memory ring โ UI ๊ฐ ์ฆ์ ๊ฐ์ ธ๊ฐ ์ ์๊ฒ dict ํํ๋ก ์ ์ฅ.""" | |
| def emit(self, record: logging.LogRecord) -> None: | |
| try: | |
| msg = record.getMessage() | |
| if record.exc_info: | |
| # ๊ฐ๋จํ ํ ์ค ์์ฝ๋ง | |
| msg += f" | {record.exc_info[0].__name__}: {record.exc_info[1]}" | |
| except Exception: | |
| msg = "<format error>" | |
| with _buf_lock: | |
| _BUFFER.append({ | |
| "ts": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(record.created)) + | |
| f".{int(record.msecs):03d}", | |
| "ts_unix": record.created, | |
| "level": record.levelname, | |
| "logger": record.name, | |
| "module": record.module, | |
| "line": record.lineno, | |
| "message": msg, | |
| }) | |
| def setup(level: str = "INFO") -> None: | |
| root = logging.getLogger() | |
| root.setLevel(level.upper()) | |
| # ์ค๋ณต ๋ฑ๋ก ๋ฐฉ์ง | |
| for h in list(root.handlers): | |
| if isinstance(h, MemoryHandler) or ( | |
| isinstance(h, logging.handlers.RotatingFileHandler) | |
| and getattr(h, "baseFilename", "") == str(LOG_FILE) | |
| ): | |
| root.removeHandler(h) | |
| fmt = logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s") | |
| fh = logging.handlers.RotatingFileHandler( | |
| LOG_FILE, maxBytes=1024 * 1024, backupCount=3, encoding="utf-8", | |
| ) | |
| fh.setFormatter(fmt) | |
| root.addHandler(fh) | |
| mh = MemoryHandler() | |
| root.addHandler(mh) | |
| logging.getLogger("werkzeug").setLevel("WARNING") # ์์ฒญ ๋ก๊ทธ ์ค์ | |
| def set_level(level: str) -> str: | |
| level = level.upper() | |
| if level not in LEVEL_RANK: | |
| raise ValueError(f"unknown level: {level}") | |
| logging.getLogger().setLevel(level) | |
| return level | |
| def get_level() -> str: | |
| return logging.getLevelName(logging.getLogger().level) | |
| def recent(level: str | None = None, limit: int = 200, contains: str | None = None) -> list[dict]: | |
| """์ต๊ทผ ๋ก๊ทธ ์กฐํ. level= 'INFO' ๋ฉด INFO ์ด์, contains= ๋ถ๋ถ๋ฌธ์์ด ํํฐ.""" | |
| with _buf_lock: | |
| items = list(_BUFFER) | |
| if level: | |
| threshold = LEVEL_RANK.get(level.upper(), 0) | |
| items = [r for r in items if LEVEL_RANK.get(r["level"], 0) >= threshold] | |
| if contains: | |
| c = contains.lower() | |
| items = [r for r in items if c in r["message"].lower() or c in r["logger"].lower()] | |
| return items[-limit:] | |
| def buffer_size() -> int: | |
| return len(_BUFFER) | |