feat(docker): container log viewer + Settings controls [M79 step 5]
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 41s
CI / integration (push) Failing after 2m24s
CI / publish (push) Has been skipped

The user-facing half (rule 27). Per-container log viewer + admin controls, so
container logs are something the operator can actually touch.

Viewer (plugins/docker):
- /container/<host>/<name>/logs full page + /logs/lines HTMX fragment polled
  every 5s (near-live follow); stream filter (all/stdout/stderr) + case-insensitive
  text search; newest-first so the latest lines survive the poll's scroll reset;
  empty/loading states; a "View logs" link from the container detail page.
  Jinja auto-escapes log content (no markup injection).

Settings (Thresholds & Retention tab):
- global on/off toggle, comma-separated exclude list, retention days + max MB
  per container — DB-backed, no restart. The toggle + exclude are enforced
  authoritatively at ingest (_persist_logs drops disabled/excluded lines), since
  the push model has no channel to tell an agent to stop.

Tests: route-defined smoke; template-parse covers the new templates; integration
test for the ingest-time toggle + exclude enforcement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CAGR73DUowdVFVvYzLXC5C
This commit is contained in:
2026-07-19 18:58:20 -04:00
parent 07a841d91e
commit 8f1c8c5cf7
9 changed files with 252 additions and 3 deletions
+10 -1
View File
@@ -124,11 +124,20 @@ async def _persist_logs(session, host, log_batches) -> None:
"""Append pushed container log lines (one row per line) for this host.
Time-series / append-only — the per-container size+age ring (retention)
bounds growth, so a chatty container just keeps a shorter window.
bounds growth, so a chatty container just keeps a shorter window. The global
toggle + exclude list (Settings, no restart) are enforced here: this is the
authoritative drop point, since the push model has no channel to tell an
agent to stop collecting.
"""
from steward.core.settings import get_setting
from .models import DockerLog
if not await get_setting(session, "docker.logs.enabled"):
return
exclude = set(await get_setting(session, "docker.logs.exclude") or ())
for row in _log_rows(log_batches, host.id):
if row["container_name"] in exclude:
continue
session.add(DockerLog(**row))