Return a bounded tail window for an active-profile Hermes log file.
(handler, parsed)
| 9247 | |
| 9248 | |
| 9249 | def _handle_logs(handler, parsed) -> bool: |
| 9250 | """Return a bounded tail window for an active-profile Hermes log file.""" |
| 9251 | query = parse_qs(parsed.query) |
| 9252 | file_key = (query.get("file", ["agent"])[0] or "agent").strip().lower() |
| 9253 | filename = _LOG_FILE_WHITELIST.get(file_key) |
| 9254 | if not filename: |
| 9255 | return bad(handler, "Unknown log file", status=400) |
| 9256 | |
| 9257 | tail = _normalize_logs_tail(query.get("tail", [None])[0]) |
| 9258 | try: |
| 9259 | from api.profiles import get_active_hermes_home |
| 9260 | |
| 9261 | hermes_home = Path(get_active_hermes_home()).expanduser() |
| 9262 | except Exception: |
| 9263 | hermes_home = Path(os.environ.get("HERMES_HOME") or (Path.home() / ".hermes")).expanduser() |
| 9264 | |
| 9265 | log_dir = hermes_home / "logs" |
| 9266 | log_path = log_dir / filename |
| 9267 | try: |
| 9268 | # Defense in depth: the filename is hardcoded above, but keep the final |
| 9269 | # path anchored under the active profile's logs directory. |
| 9270 | if log_path.resolve(strict=False).parent != log_dir.resolve(strict=False): |
| 9271 | return bad(handler, "Invalid log file", status=400) |
| 9272 | if not log_path.exists() or not log_path.is_file(): |
| 9273 | return j(handler, { |
| 9274 | "file": file_key, |
| 9275 | "tail": tail, |
| 9276 | "lines": [], |
| 9277 | "truncated": False, |
| 9278 | "total_bytes": 0, |
| 9279 | "mtime": None, |
| 9280 | "hint": f"Log file for {file_key} not found yet.", |
| 9281 | }) |
| 9282 | st = log_path.stat() |
| 9283 | total_bytes = int(st.st_size) |
| 9284 | read_bytes = min(total_bytes, _LOG_MAX_BYTES) |
| 9285 | with log_path.open("rb") as fh: |
| 9286 | if total_bytes > read_bytes: |
| 9287 | fh.seek(total_bytes - read_bytes) |
| 9288 | raw = fh.read(read_bytes) |
| 9289 | text = raw.decode("utf-8", errors="replace") |
| 9290 | lines = text.splitlines()[-tail:] |
| 9291 | return j(handler, { |
| 9292 | "file": file_key, |
| 9293 | "tail": tail, |
| 9294 | "lines": lines, |
| 9295 | "truncated": total_bytes > read_bytes, |
| 9296 | "total_bytes": total_bytes, |
| 9297 | "mtime": st.st_mtime, |
| 9298 | "hint": "", |
| 9299 | }) |
| 9300 | except Exception as exc: |
| 9301 | logger.exception("Failed to read whitelisted log file %s", file_key) |
| 9302 | return bad(handler, _sanitize_error(exc), status=500) |
| 9303 | |
| 9304 | # ── Insights endpoint ────────────────────────────────────────────────────────── |
| 9305 |
no test coverage detected