""" Persistent logging utility for HuggingFace Spaces. Logs are written to files that persist across space restarts. """ import os import sys from datetime import datetime from pathlib import Path from typing import Optional # Try to use persistent storage if available # HF Spaces may have /tmp or other persistent locations PERSISTENT_LOG_DIR = None # Try common persistent locations for log_dir in [ "/tmp/logs", # Common temp location (may persist) "/persistent/logs", # Some HF Spaces have this os.path.join(os.path.expanduser("~"), ".cache", "caribbean-voices", "logs"), # User cache ]: try: Path(log_dir).mkdir(parents=True, exist_ok=True) # Test write test_file = os.path.join(log_dir, ".test_write") with open(test_file, "w") as f: f.write("test") os.remove(test_file) PERSISTENT_LOG_DIR = log_dir break except (PermissionError, OSError): continue # Fallback to current directory if no persistent location found if PERSISTENT_LOG_DIR is None: PERSISTENT_LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "logs") Path(PERSISTENT_LOG_DIR).mkdir(parents=True, exist_ok=True) print(f"📝 Log directory: {PERSISTENT_LOG_DIR}") class TeeOutput: """Tee output to both stdout and a file.""" def __init__(self, file_handle, original_stdout): self.file = file_handle self.stdout = original_stdout def write(self, message): self.stdout.write(message) self.file.write(message) self.file.flush() def flush(self): self.stdout.flush() self.file.flush() class PersistentLogger: """Logger that redirects stdout/stderr to both console and persistent log files.""" def __init__(self, log_name: str = "training"): self.log_name = log_name self.log_file = os.path.join(PERSISTENT_LOG_DIR, f"{log_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log") self.log_handle = open(self.log_file, "a", buffering=1) # Line buffered # Save original stdout/stderr self.original_stdout = sys.stdout self.original_stderr = sys.stderr # Create tee outputs self.tee_stdout = TeeOutput(self.log_handle, self.original_stdout) self.tee_stderr = TeeOutput(self.log_handle, self.original_stderr) # Redirect stdout/stderr sys.stdout = self.tee_stdout sys.stderr = self.tee_stderr print(f"📝 Logging to: {self.log_file}") def close(self): """Close the log file and restore original stdout/stderr.""" # Restore original stdout/stderr sys.stdout = self.original_stdout sys.stderr = self.original_stderr # Close log file if self.log_handle: self.log_handle.close() self.log_handle = None def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() def get_latest_log_file(log_name: str = "training") -> Optional[str]: """Get the path to the latest log file for a given log name.""" log_files = list(Path(PERSISTENT_LOG_DIR).glob(f"{log_name}_*.log")) if not log_files: return None # Sort by modification time, return most recent latest = max(log_files, key=lambda p: p.stat().st_mtime) return str(latest) def get_all_log_files(log_name: str = "training") -> list: """Get all log files for a given log name, sorted by modification time (newest first).""" log_files = list(Path(PERSISTENT_LOG_DIR).glob(f"{log_name}_*.log")) return sorted(log_files, key=lambda p: p.stat().st_mtime, reverse=True) def get_log_directory() -> str: """Get the log directory path.""" return PERSISTENT_LOG_DIR