DreamLake

Living dev note. Iterate freely.

The dashboard also needs queue visualization — see Queues for the sidebar, queue detail, and overlap views.

The dashboard needs to render logs for several distinct surfaces:

  • An exec — bash command output (one-shot or streaming).
  • A run / job — @dls.udf invocation: stdout/stderr + Python tracebacks.
  • A session — interactive REPL output (live tail).
  • A daemon itself — nymph's own logs (runtime, errors, config).
  • A provider — cloud launch / instance provisioning logs.

These are different sources of truth. The UI should treat the log view as a generic byte renderer and let the controlplane (or a small adapter layer) resolve which source backs each view.

Pluggable log sources

SourceWhere bytes liveReachabilityLatency to render
Stream subscriber (Tier 1 — Log storage)controlplane-owned Redis Streams, fan-out to consumersonly while exec/session active~ms
Daemon ring buffernymph in-memory, last ~64KBonly while daemon up; pull-on-demand via controlplaneone poll round-trip (~25s today, ~ms after wake-up)
Controlplane archive (Tier 2 — Log storage)S3 (default with CP-mediated upload) or mongo for small payloadsalways, as long as not GC'd~50ms (mongo) / ~200ms (S3 presigned GET)
Out-of-band shipper (CloudWatch / vector / journald)external systemalwaysdepends on system
SSH to hostfiles on the VMrequires SSH access from operatorseconds

Each surface picks the right source based on:

  • Is the producer active right now (stream) vs historical (archive)?
  • Does the daemon have egress (S3-direct OK) vs no (must go through CP)?
  • Is the volume small (fits in mongo) vs large (needs S3)?
  • Does the user want live tail or just enough context to debug?

UI shape

One reusable component: <LogView>.

tsx
<LogView
  source="stream" | "archive" | "ringbuf"
  sourceArgs={...}      // exec_id / session_id / worker_id / job_id
  // The component fetches initial bytes, opens a subscription if
  // applicable, renders a virtual-scrolled buffer with search,
  // wrap toggle, auto-scroll-to-tail, copy-region.
/>

Routes that mount it:

/workers/:id/log               → ringbuf (pulled via CP)
/execs/:id                     → stream (if active) / archive (if done)
/runs/:id                      → stream (if active) / archive (if done)
/sessions/:id                  → stream (active sessions only)
/admin/providers/:name/launches/:id  → archive (cloud-init / launch console)

The component itself doesn't know what backs the source — it talks to one controlplane endpoint that abstracts:

GET  /v1/namespaces/:ns/logs?source=stream&exec_id=…   → SSE
GET  /v1/namespaces/:ns/logs?source=archive&exec_id=…  → presigned S3 URL
GET  /v1/namespaces/:ns/logs?source=ringbuf&worker=…   → JSON {bytes, cursor}

The dashboard fetches once and picks transport based on the returned shape (URL → direct fetch; cursor → poll; SSE stream → consume).

Useful UX details

  • Auto-scroll to tail by default, but pin off on user scroll (matches journalctl + every IDE log viewer).
  • Search across visible buffer — Cmd-F binding. No server-side search at v1.
  • ANSI color rendering. Many of our outputs come from pytest / cargo / pip — they have meaningful escape codes.
  • Timestamps optional — toggle in the toolbar. Default off because bash exec doesn't emit them; nymph logs do.
  • Permalink to a line range — #L100-L120 anchor. Lets users paste a link to a specific failure in a chat message.
  • Download as .log — for archived sources, this is just a redirect to the presigned URL. For streams, snapshot the current buffer.

What ships first

In order:

  1. /runs/:id archive view. This is the most common debugging need today (UDF dispatches fail; users want stack traces). Backed by the existing exec-result blob in mongo. Renders ANSI, no live stream needed for v1.
  2. /workers/:id/log ringbuf view. Pull on demand via a new controlplane endpoint that asks the daemon "give me your last 64KB" — answered on the next poll. Useful for "is this daemon OK?" debugging without SSHing.
  3. /execs/:id stream view. Depends on the streaming wire from Log storage (Tier 1). Real-time tail of an active exec.
  4. /sessions/:id stream view. Depends on Sessions + the stream wire. Full interactive view.

(1) is dashboard-only work; (2) needs a tiny daemon-side ringbuf endpoint; (3) and (4) wait for the streaming wire.

What does NOT ship

  • Server-side full-text search across all logs. Use grep on the downloaded .log for v1. Bring it back when a user actually asks.
  • Forwarded retention policies (compliance, GDPR-style log expiration). When that customer comes, build it; not before.
  • Cross-job log correlation (Datadog / Honeycomb style). External observability tool's job, not the dashboard's.

Open

  • Mobile rendering. Log views suck on phones; do we even bother? Probably "scrollable list of failed jobs" works for triage and the full log opens a separate full-screen view.
  • Live multi-tail. Combined view of N execs streaming concurrently. Useful for fleet debugging but adds protocol complexity. Defer.