open-navigator / scripts /utils /log_sync.py
jcbowyer's picture
Clean HuggingFace deployment without binary files
e59d91d
Raw
History Blame Contribute Delete
4.7 kB
"""
Log sync β€” copies run logs to Google Drive.
Supports two backends (tried in order):
1. Direct filesystem copy to a mounted Google Drive path (preferred on WSL2/Windows)
2. rclone upload (for Linux servers without Google Drive Desktop)
Logs land at:
<gdrive_root>/CommunityOne/open-navigator-logs/<machine_id>/<run_type>/<run_id>/
For governance / Gemma pipeline folder layout (``01_raw_inputs``, ``02_reference_data/orbis_files``,
``03_processed_outputs/...``), see ``GovernancePipelinePaths`` in ``core_lib.gdrive_paths`` and
``scripts/utils/ensure_governance_pipeline_drive_layout.py``.
Configuration (env vars β€” all optional):
LOG_GDRIVE_MOUNT Path to mounted Google Drive root
(default: /mnt/g/My Drive)
LOG_GDRIVE_BASE Sub-path inside the mount
(default: CommunityOne/open-navigator-logs)
LOG_MACHINE_ID Machine label (default: socket.gethostname())
RCLONE_GDRIVE_REMOTE rclone remote name, used only if mount not found
(default: gdrive)
"""
import os
import shutil
import socket
import subprocess
import sys
from pathlib import Path
from loguru import logger
# ``gdrive_paths`` was ported to ``core_lib``; ensure it resolves when this module is
# imported as a bare script (repo root on path but workspace not editable-installed).
_repo_root = Path(__file__).resolve().parents[2]
_core_lib_src = _repo_root / "packages" / "core-lib" / "src"
if str(_core_lib_src) not in sys.path:
sys.path.insert(0, str(_core_lib_src))
from core_lib.gdrive_paths import gdrive_mount_path
GDRIVE_MOUNT = gdrive_mount_path()
GDRIVE_BASE = os.getenv("LOG_GDRIVE_BASE", "CommunityOne/open-navigator-logs")
MACHINE_ID = os.getenv("LOG_MACHINE_ID", socket.gethostname())
RCLONE_REMOTE = os.getenv("RCLONE_GDRIVE_REMOTE", "gdrive")
def _sync_via_mount(log_dir: Path, run_type: str) -> bool:
"""Copy log_dir into the mounted Google Drive folder."""
dest = GDRIVE_MOUNT / GDRIVE_BASE / MACHINE_ID / run_type / log_dir.name
try:
dest.mkdir(parents=True, exist_ok=True)
for src_file in log_dir.iterdir():
# shutil.copy/copy2 fail on NTFS mounts (chmod not permitted); plain write avoids that
(dest / src_file.name).write_bytes(src_file.read_bytes())
logger.success(f"Logs copied β†’ {dest}")
return True
except Exception as e:
logger.warning(f"Drive mount copy failed: {e}")
return False
def _rclone_configured() -> bool:
try:
result = subprocess.run(
["rclone", "listremotes"], capture_output=True, text=True, timeout=10
)
return f"{RCLONE_REMOTE}:" in result.stdout
except Exception:
return False
def _sync_via_rclone(log_dir: Path, run_type: str) -> bool:
"""Upload log_dir to Google Drive via rclone."""
if not _rclone_configured():
logger.warning(
f"rclone remote '{RCLONE_REMOTE}' not configured. "
"Run: rclone config (add a remote named 'gdrive')"
)
return False
remote_path = f"{RCLONE_REMOTE}:{GDRIVE_BASE}/{MACHINE_ID}/{run_type}/{log_dir.name}"
try:
result = subprocess.run(
["rclone", "copy", str(log_dir), remote_path, "--stats-one-line"],
capture_output=True, text=True, timeout=120,
)
if result.returncode == 0:
logger.success(f"Logs uploaded β†’ {remote_path}")
return True
logger.warning(f"rclone exited {result.returncode}: {result.stderr.strip()}")
return False
except subprocess.TimeoutExpired:
logger.warning("rclone sync timed out after 120 s")
return False
except Exception as e:
logger.warning(f"rclone sync failed: {e}")
return False
def sync_logs(log_dir: Path, run_type: str, project_root: Path | None = None) -> bool:
"""
Upload a run's log directory to Google Drive.
Tries the mounted Drive path first; falls back to rclone.
Always non-fatal β€” failures are logged as warnings.
"""
if not log_dir.exists():
logger.warning(f"Log dir {log_dir} does not exist β€” nothing to sync")
return False
try:
display = log_dir.relative_to(project_root) if project_root else log_dir
except ValueError:
display = log_dir.resolve().relative_to(project_root.resolve()) if project_root else log_dir
logger.info(f"Syncing {display} [machine: {MACHINE_ID}]")
if GDRIVE_MOUNT.exists():
return _sync_via_mount(log_dir, run_type)
logger.debug(f"Drive mount {GDRIVE_MOUNT} not found β€” trying rclone")
return _sync_via_rclone(log_dir, run_type)