F4bC0d3 commited on
Commit
aadc662
Β·
1 Parent(s): 5f92103

fix(logs): silence noisy WebUI access log polls + bound on-disk log size

Browse files

Three changes to keep the HF Logs tab readable and the HF Dataset backup
from growing unbounded:

1. Patch hermes-webui's log_request() at image build time to drop 2xx
responses for high-frequency poll paths (/api/dashboard/status,
/api/health/agent, /api/sessions, /api/profiles, /api/models,
/sw.js, /static/*, etc). 4xx and 5xx still log so debugging works.
2. Rotate \/logs/*.log at boot when any file exceeds 5MB
(rename to .log.1, start fresh). Bounds the disk footprint.
3. Extend hermes-sync.py exclusions to skip *.log, *.log.1, *.log.2,
*.pid, *.tmp suffixes - and treat 'logs/' as a fully-excluded dir
not just a top-level skip. Backup dataset stays clean.

Files changed (3) hide show
  1. Dockerfile +79 -0
  2. hermes-sync.py +7 -1
  3. start.sh +17 -0
Dockerfile CHANGED
@@ -90,6 +90,85 @@ if old in src:
90
  print("kanban patch: applied")
91
  PY
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  # Keep hermes CLI on PATH for all shell types (login/interactive/non-interactive)
94
  RUN echo 'export PATH="/opt/hermes/.venv/bin:/opt/data/.local/bin:$PATH"' \
95
  > /etc/profile.d/hermes-venv.sh
 
90
  print("kanban patch: applied")
91
  PY
92
 
93
+ # Quiet hermes-webui's per-request access log noise.
94
+ # By default it prints `[webui] {"ts":...,"method":...}` for EVERY request,
95
+ # which drowns the HF Logs tab once any browser tab is open polling
96
+ # /api/dashboard/status, /api/health/agent, /api/sessions, /sw.js, etc.
97
+ # Patch log_request() to drop 2xx responses for high-frequency poll paths.
98
+ # Errors and chat/streaming paths still log normally.
99
+ RUN python3 - <<'PY'
100
+ from pathlib import Path
101
+ import re
102
+ import sys
103
+
104
+ p = Path("/opt/hermes-webui/server.py")
105
+ if not p.exists():
106
+ sys.exit(0)
107
+ src = p.read_text(encoding="utf-8")
108
+ sentinel = "# huggingmes-webui: quiet-poll-paths"
109
+ if sentinel in src:
110
+ sys.exit(0)
111
+
112
+ old = (
113
+ " def log_request(self, code: str='-', size: str='-') -> None:\n"
114
+ " \"\"\"Structured JSON logs for each request.\"\"\"\n"
115
+ " import json as _json\n"
116
+ " duration_ms = round((time.time() - getattr(self, '_req_t0', time.time())) * 1000, 1)\n"
117
+ " record = _json.dumps({\n"
118
+ " 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),\n"
119
+ " 'method': self.command or '-',\n"
120
+ " 'path': self.path or '-',\n"
121
+ " 'status': int(code) if str(code).isdigit() else code,\n"
122
+ " 'ms': duration_ms,\n"
123
+ " })\n"
124
+ " print(f'[webui] {record}', flush=True)"
125
+ )
126
+
127
+ new = (
128
+ " _QUIET_POLL_PATHS = ( " + sentinel + "\n"
129
+ " '/api/health/agent', '/api/dashboard/status',\n"
130
+ " '/api/dashboard/config', '/api/sessions', '/api/profiles',\n"
131
+ " '/api/profile/active', '/api/onboarding/status',\n"
132
+ " '/api/insights', '/api/system/health',\n"
133
+ " '/api/settings', '/api/projects', '/api/reasoning',\n"
134
+ " '/api/models', '/api/chat/stream/status',\n"
135
+ " '/api/git-info', '/sw.js', '/health',\n"
136
+ " )\n"
137
+ " _QUIET_PREFIXES = ('/static/', '/session/static/', '/assets/')\n"
138
+ "\n"
139
+ " def log_request(self, code: str='-', size: str='-') -> None:\n"
140
+ " \"\"\"Structured JSON logs for each request, skipping noisy polls.\"\"\"\n"
141
+ " # Always log non-2xx so 401/404/5xx remain visible.\n"
142
+ " try:\n"
143
+ " status_int = int(code) if str(code).isdigit() else 0\n"
144
+ " except Exception:\n"
145
+ " status_int = 0\n"
146
+ " path = (self.path or '').split('?', 1)[0]\n"
147
+ " if 200 <= status_int < 400:\n"
148
+ " if path in self._QUIET_POLL_PATHS:\n"
149
+ " return\n"
150
+ " for pref in self._QUIET_PREFIXES:\n"
151
+ " if path.startswith(pref):\n"
152
+ " return\n"
153
+ " import json as _json\n"
154
+ " duration_ms = round((time.time() - getattr(self, '_req_t0', time.time())) * 1000, 1)\n"
155
+ " record = _json.dumps({\n"
156
+ " 'ts': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),\n"
157
+ " 'method': self.command or '-',\n"
158
+ " 'path': self.path or '-',\n"
159
+ " 'status': int(code) if str(code).isdigit() else code,\n"
160
+ " 'ms': duration_ms,\n"
161
+ " })\n"
162
+ " print(f'[webui] {record}', flush=True)"
163
+ )
164
+
165
+ if old in src:
166
+ p.write_text(src.replace(old, new), encoding="utf-8")
167
+ print("webui log-quiet patch: applied")
168
+ else:
169
+ print("webui log-quiet patch: pattern not found, skipping")
170
+ PY
171
+
172
  # Keep hermes CLI on PATH for all shell types (login/interactive/non-interactive)
173
  RUN echo 'export PATH="/opt/hermes/.venv/bin:/opt/data/.local/bin:$PATH"' \
174
  > /etc/profile.d/hermes-venv.sh
hermes-sync.py CHANGED
@@ -50,8 +50,14 @@ EXCLUDED_DIRS = {
50
  "__pycache__",
51
  "node_modules",
52
  "venv",
 
53
  }
54
  EXCLUDED_TOP_LEVEL = {"logs", STATE_FILE.name}
 
 
 
 
 
55
  if not INCLUDE_ENV:
56
  EXCLUDED_TOP_LEVEL.add(".env")
57
 
@@ -126,7 +132,7 @@ def should_exclude(rel_posix: str, path: Path) -> bool:
126
  return True
127
  if path.is_file():
128
  name_lower = path.name.lower()
129
- if name_lower.endswith((".db-shm", ".db-wal", ".db-journal")):
130
  return True
131
  try:
132
  return path.stat().st_size > MAX_FILE_SIZE_BYTES
 
50
  "__pycache__",
51
  "node_modules",
52
  "venv",
53
+ "logs", # log files are useless after a restart
54
  }
55
  EXCLUDED_TOP_LEVEL = {"logs", STATE_FILE.name}
56
+ EXCLUDED_SUFFIXES = (
57
+ ".log", ".log.1", ".log.2",
58
+ ".db-shm", ".db-wal", ".db-journal",
59
+ ".pid", ".tmp",
60
+ )
61
  if not INCLUDE_ENV:
62
  EXCLUDED_TOP_LEVEL.add(".env")
63
 
 
132
  return True
133
  if path.is_file():
134
  name_lower = path.name.lower()
135
+ if name_lower.endswith(EXCLUDED_SUFFIXES):
136
  return True
137
  try:
138
  return path.stat().st_size > MAX_FILE_SIZE_BYTES
start.sh CHANGED
@@ -60,6 +60,23 @@ fi
60
  # ── Setup state dirs ──────────────────────────────────────────────────
61
  mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home,plugins,webui}
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  # Expose hermes CLI to login shells
64
  mkdir -p "$HERMES_HOME/.local/bin"
65
  ln -sfn /opt/hermes/.venv/bin/hermes "$HERMES_HOME/.local/bin/hermes"
 
60
  # ── Setup state dirs ──────────────────────────────────────────────────
61
  mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills,skins,plans,workspace,home,plugins,webui}
62
 
63
+ # Rotate on-disk logs at boot. The router + WebUI + dashboard tee their
64
+ # stdout into $HERMES_HOME/logs/*.log via `tee -a`, which means without
65
+ # rotation those files grow forever and end up in the HF Dataset backup.
66
+ # Strategy: if a log is >5MB, rename to .1 (overwriting any previous .1)
67
+ # and start fresh. Cheap, deterministic, no cron needed.
68
+ if [ -d "$HERMES_HOME/logs" ]; then
69
+ for f in "$HERMES_HOME/logs"/*.log; do
70
+ [ -f "$f" ] || continue
71
+ sz=$(stat -c%s "$f" 2>/dev/null || echo 0)
72
+ if [ "$sz" -gt 5242880 ]; then
73
+ mv -f "$f" "${f}.1"
74
+ : > "$f"
75
+ echo "rotated $(basename "$f") ($sz bytes -> .1)"
76
+ fi
77
+ done
78
+ fi
79
+
80
  # Expose hermes CLI to login shells
81
  mkdir -p "$HERMES_HOME/.local/bin"
82
  ln -sfn /opt/hermes/.venv/bin/hermes "$HERMES_HOME/.local/bin/hermes"