rex/logging_config.py
Rama master · réplica de lectura del repositorio original.
"""Structured JSON logging configuration for Rex.
Provides ``setup_file_logging()`` which adds a ``RotatingFileHandler`` writing
valid JSON lines to the current runtime log under ``data/logs/rex.log``.
Each log line format::
{"timestamp": "<ISO8601>", "level": "INFO", "logger": "rex.foo",
"message": "...", "extra": {}}
Rotation: 5 MB per file, keep last 5 files.
Usage::
from rex.logging_config import setup_file_logging
setup_file_logging()
"""
from __future__ import annotations
import logging
from datetime import UTC, datetime
from logging.handlers import RotatingFileHandler
from pathlib import Path
from .log_paths import DEFAULT_RUNTIME_LOG_FILE
from .logging_utils import JsonFormatter
LOG_FILE = DEFAULT_RUNTIME_LOG_FILE
MAX_BYTES = 5_000_000 # 5 MB
BACKUP_COUNT = 3
def setup_file_logging(
log_file: str | Path = LOG_FILE,
*,
level: int = logging.DEBUG,
max_bytes: int = MAX_BYTES,
backup_count: int = BACKUP_COUNT,
) -> RotatingFileHandler:
"""Attach a JSON rotating file handler to the root logger.
Creates the log directory if it does not exist.
Args:
log_file: Path to the primary log file (default ``data/logs/rex.log``).
level: Minimum log level to write to the file (default ``DEBUG`` so all
levels captured by the root logger reach the file).
max_bytes: Maximum file size before rotation (default 5 MB).
backup_count: Number of rotated files to keep (default 5).
Returns:
The configured ``RotatingFileHandler`` (also attached to root logger).
"""
path = Path(log_file)
path.parent.mkdir(parents=True, exist_ok=True)
handler = RotatingFileHandler(
path,
maxBytes=max_bytes,
backupCount=backup_count,
encoding="utf-8",
)
handler.setLevel(level)
handler.setFormatter(JsonFormatter())
root = logging.getLogger()
# Avoid duplicate handlers on repeated calls.
for existing in root.handlers:
if isinstance(existing, RotatingFileHandler) and existing.baseFilename == str(
path.resolve()
):
return existing
root.addHandler(handler)
# Write session-start marker so sessions are clearly separated in the log file.
ts = datetime.now(UTC).isoformat(timespec="seconds")
logging.getLogger(__name__).info("=== Rex session started at %s ===", ts)
return handler
__all__ = ["setup_file_logging", "LOG_FILE", "MAX_BYTES", "BACKUP_COUNT"]