from __future__ import annotations import logging import os from datetime import datetime, timezone from logging.handlers import TimedRotatingFileHandler from pathlib import Path def utc_now() -> datetime: return datetime.now(timezone.utc) def configure_logging(level: str, log_file: Path | None = None) -> None: level_value = getattr(logging, level.upper(), logging.INFO) logging_handlers: list[logging.Handler] = [logging.StreamHandler()] if log_file is not None: log_file.parent.mkdir(parents=True, exist_ok=True) rotating_handler = TimedRotatingFileHandler( log_file, when="midnight", interval=1, backupCount=90, encoding="utf-8", delay=True, ) rotating_handler.suffix = "%Y-%m-%d" logging_handlers.append(rotating_handler) logging.basicConfig( level=level_value, format="%(asctime)s %(levelname)s %(name)s %(message)s", handlers=logging_handlers, force=True, ) for logger_name in ("uvicorn", "uvicorn.error", "uvicorn.access", "uvicorn.asgi"): logger = logging.getLogger(logger_name) logger.handlers = [] logger.propagate = True def project_root() -> Path: return Path(__file__).resolve().parents[1] def safe_join(root: Path, untrusted_path: str) -> Path: if untrusted_path.startswith(("\\\\", "//")): raise ValueError("UNC path is not allowed") if os.path.isabs(untrusted_path): raise ValueError("Absolute path is not allowed") candidate = (root / untrusted_path).resolve() root_resolved = root.resolve() if root_resolved not in candidate.parents and candidate != root_resolved: raise ValueError("Path traversal detected") return candidate