Source code for ralph.display.raw_overflow

"""Per-unit raw NDJSON overflow log writer."""

from __future__ import annotations

import re
import threading
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pathlib import Path

_SAFE_CHARS = re.compile(r"[^A-Za-z0-9._-]")
DEFAULT_MAX_OVERFLOW_FILE_BYTES = 50 * 1024 * 1024


def _sanitize_unit_id(unit_id: str) -> str:
    return _SAFE_CHARS.sub("_", unit_id)


[docs] class RawOverflowLog: """Append-mode raw log for a single work unit. Thread-safe. Silently no-ops on filesystem errors so the display path never crashes due to a read-only workspace. """ def __init__( self, workspace_root: Path, unit_id: str, *, max_bytes: int = DEFAULT_MAX_OVERFLOW_FILE_BYTES, ) -> None: safe_id = _sanitize_unit_id(unit_id) self.path = workspace_root / ".agent" / "raw" / f"{safe_id}.log" self._lock = threading.Lock() self._first_write = True self._disabled = False self._max_bytes = max(max_bytes, 0) self._bytes_written = 0
[docs] def disable(self) -> None: """Permanently disable this log so future appends are no-ops.""" with self._lock: self._disabled = True
[docs] def append(self, line: str) -> bool: """Write *line* to the overflow log. Returns True when the line was written. Returns False when the log is disabled, the byte cap has been reached, or an I/O error occurs. """ with self._lock: if self._disabled: return False try: text = line.rstrip("\n") + "\n" encoded = text.encode("utf-8") if self._bytes_written + len(encoded) > self._max_bytes: self._disabled = True return False self.path.parent.mkdir(parents=True, exist_ok=True) if self._first_write: self.path.write_bytes(encoded) self._first_write = False else: with self.path.open("ab") as fh: fh.write(encoded) self._bytes_written += len(encoded) return True except (OSError, PermissionError): self._disabled = True return False
[docs] def relative_reference(self, workspace_root: Path) -> str: """Return POSIX path relative to *workspace_root*, or absolute on error.""" try: return self.path.relative_to(workspace_root).as_posix() except ValueError: return self.path.as_posix()
__all__ = ["DEFAULT_MAX_OVERFLOW_FILE_BYTES", "RawOverflowLog"]