fix(debug): say where a client-side log lives instead of "(file not found)" (#78687)
hermes debug share runs on the backend. A desktop app connected to a remote, docker, or SSH backend writes desktop.log on the client machine, so the bundle can never contain it — and the report rendered that as a bare "(file not found)", which reads as "the app logged nothing" and sends triage after a client-side bug it cannot see. Name the writer and the path to collect by hand. Backend-written logs are unchanged, a present desktop.log is still captured, and an empty one still reports "(file empty)" — the app ran and logged nothing is a different fact from the file being on another host.
This commit is contained in:
@@ -372,6 +372,35 @@ def _primary_log_path(log_name: str) -> Optional[Path]:
|
||||
return (get_hermes_home() / "logs" / filename) if filename else None
|
||||
|
||||
|
||||
# Logs written by a client process rather than by this backend. When the
|
||||
# desktop app talks to a remote/docker/SSH backend, `hermes debug share` runs
|
||||
# on the *backend* and can never see them — a bare "(file not found)" then
|
||||
# reads as "the app logged nothing" and sends triage down a dead end, which is
|
||||
# exactly the wrong answer when the client is the thing being debugged.
|
||||
_CLIENT_SIDE_LOGS = {
|
||||
"desktop": (
|
||||
"written by Hermes Desktop on the machine running the app, not by this "
|
||||
"backend. If the desktop connects to a remote/docker/SSH backend, collect "
|
||||
"it on that client machine"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _missing_log_note(log_name: str) -> str:
|
||||
"""Explain a missing log instead of stating a bare absence.
|
||||
|
||||
For a client-side log the absence is expected on a remote backend, so the
|
||||
note names the writer and the path to collect by hand.
|
||||
"""
|
||||
reason = _CLIENT_SIDE_LOGS.get(log_name)
|
||||
if reason is None:
|
||||
return "(file not found)"
|
||||
|
||||
primary = _primary_log_path(log_name)
|
||||
where = f" — expected at {primary}" if primary else ""
|
||||
return f"(not on this host: {reason}{where})"
|
||||
|
||||
|
||||
def _resolve_log_path(log_name: str) -> Optional[Path]:
|
||||
"""Find the log file for *log_name*, falling back to the .1 rotation.
|
||||
|
||||
@@ -432,7 +461,11 @@ def _capture_log_snapshot(
|
||||
log_path = _resolve_log_path(log_name)
|
||||
if log_path is None:
|
||||
primary = _primary_log_path(log_name)
|
||||
tail = "(file empty)" if primary and primary.exists() else "(file not found)"
|
||||
tail = (
|
||||
"(file empty)"
|
||||
if primary and primary.exists()
|
||||
else _missing_log_note(log_name)
|
||||
)
|
||||
return LogSnapshot(path=None, tail_text=tail, full_text=None)
|
||||
|
||||
try:
|
||||
|
||||
@@ -146,6 +146,63 @@ class TestCaptureLogSnapshot:
|
||||
assert len(kept) == 10
|
||||
|
||||
|
||||
class TestMissingLogNote:
|
||||
"""A missing log explains itself when the writer isn't this backend.
|
||||
|
||||
`hermes debug share` runs on the backend, so a desktop connected to a
|
||||
remote/docker/SSH backend can never contribute desktop.log. Reporting a
|
||||
bare absence sends triage after a client-side bug it cannot see.
|
||||
"""
|
||||
|
||||
def test_backend_written_log_reports_plain_absence(self, hermes_home):
|
||||
from hermes_cli.debug import _capture_log_snapshot
|
||||
|
||||
(hermes_home / "logs" / "agent.log").unlink()
|
||||
|
||||
snap = _capture_log_snapshot("agent", tail_lines=10)
|
||||
assert snap.full_text is None
|
||||
assert snap.tail_text == "(file not found)"
|
||||
|
||||
def test_client_written_log_names_its_writer_and_path(self, hermes_home):
|
||||
from hermes_cli.debug import _capture_log_snapshot
|
||||
|
||||
(hermes_home / "logs" / "desktop.log").unlink()
|
||||
|
||||
snap = _capture_log_snapshot("desktop", tail_lines=10)
|
||||
assert snap.full_text is None
|
||||
assert "not on this host" in snap.tail_text
|
||||
assert "Hermes Desktop" in snap.tail_text
|
||||
# The reader needs the path to collect by hand on the client machine.
|
||||
assert str(hermes_home / "logs" / "desktop.log") in snap.tail_text
|
||||
|
||||
def test_present_client_log_is_captured_normally(self, hermes_home):
|
||||
"""A local backend still reads desktop.log — the note is only for a miss."""
|
||||
from hermes_cli.debug import _capture_log_snapshot
|
||||
|
||||
snap = _capture_log_snapshot("desktop", tail_lines=10)
|
||||
assert "backend spawned" in snap.tail_text
|
||||
assert "not on this host" not in snap.tail_text
|
||||
|
||||
def test_empty_client_log_is_empty_not_absent(self, hermes_home):
|
||||
"""An empty file means the app ran and logged nothing — a different fact."""
|
||||
from hermes_cli.debug import _capture_log_snapshot
|
||||
|
||||
(hermes_home / "logs" / "desktop.log").write_text("")
|
||||
|
||||
snap = _capture_log_snapshot("desktop", tail_lines=10)
|
||||
assert snap.tail_text == "(file empty)"
|
||||
|
||||
def test_report_carries_the_note_for_a_remote_backend(self, hermes_home):
|
||||
"""The uploaded report — what people paste into support — must explain it."""
|
||||
from hermes_cli.debug import collect_debug_report
|
||||
|
||||
(hermes_home / "logs" / "desktop.log").unlink()
|
||||
|
||||
report = collect_debug_report(log_lines=10, dump_text="dump\n")
|
||||
assert "--- desktop.log" in report
|
||||
assert "not on this host" in report
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user