Skip to content

feat(stack): persist service logs, ship them to Analytics, and add stack logs history - #6893

Open
avallete wants to merge 9 commits into
developfrom
avallete/stack-logs-files
Open

avallete wants to merge 9 commits into
developfrom
avallete/stack-logs-files

Conversation

@avallete

@avallete avallete commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Summary

In the experimental stack, service output lived only in an in-memory buffer: supabase stack logs had no history, nothing survived an owner exit, and Studio's Logs pages were always empty because nothing reached Analytics. This PR persists every service's output, ships it to Analytics, and gives stack logs a history-first interface.

Persisted logs

  • Every output chunk carries its launch id, process part, launch-wide sequence number and publish time, set where the process is spawned (startup commands, native processes, containers, database). Lines are split once in the owner; late output of a failed launch never joins the next one, and upstream buffer overflow becomes a lost record.
  • Files live at <stateRoot>/<stackId>/logs/<service>/<instanceId>/<generation>.log, one record per line (<time> <kind> <launchId> | <text>). Segments are immutable once closed, rotate at 5 MiB, and are retained by bytes (10 MiB) and count (64). Lines over 32 KiB are cut and flagged truncated.
  • One position-based reader serves history, follow and offline reads. It subscribes before reading and rechecks the writer's end under its lock, so a follower never misses a write.
  • Queued output is drained on stop; failed appends back off without blocking services; instance and stack destroy remove logs (with a bounded Windows sharing-violation retry shared with State); orphaned log directories are removed on the next owner start.

Studio Logs: shipping to Analytics

  • The owner ships Auth, REST, Realtime, Storage, Functions and database lines to their Logflare sources, using the legacy Vector remaps ported to TypeScript (source names unchanged).
  • It posts to Analytics' own backend only while the composed Analytics instance is running and healthy, so shipping never wakes Analytics or keeps it awake.
  • A per-instance cursor.json next to the log files lets lines written while Analytics slept ship after it wakes, with their original timestamps. Retained history is shipped the first time an instance is seen.
  • Events carry deterministic ids and Logflare de-duplicates on them, so failed posts are retried without duplicates. 401/403/404 pause shipping and keep the cursor; malformed bodies are skipped. Bodies are capped at 256 events and 1 MiB including the JSON envelope.
  • PostgREST now logs every request (PGRST_LOG_LEVEL=info), and request lines reach Analytics with method, path, protocol and status.

supabase stack logs

  • Prints retained history and exits by default; -f/--follow hands over to live output per instance from the last printed record, without gaps or duplicates.
  • --tail N (default 200), --since <duration|ISO|start>, repeatable --service. History is streamed with bounded memory and read from the files when the stack is down; -f fails before any output when the stack is not running.
  • stream-json keeps the log-entry envelope with source: "history" | "live" and record timestamps, and adds log-marker events. --output-format json prints an array.
  • The live-only logs RPC is replaced by readLogs({ id, since?, tail?, follow?, from? }); the Promise client exposes it as an async iterable. streamStackLogs reads a stopped stack. Launch ids keep increasing per instance across owner restarts, so --since start always means the latest launch.

Vector removed from the experimental stack

  • The CLI no longer composes Vector and the stack package no longer has a Vector service kind.
  • Saved stacks that contain a Vector instance are migrated on owner start (state rewritten atomically under the state lock, after the container sweep); leftover Vector files and containers are removed.
  • The legacy supabase start still runs Vector, so its artifact pin stays in the catalog (which legacy slim mode reads) under a separate artifact kind; stack service kinds, supabase services and stack prepare exclude it. analytics.vector_port stays in the config schema for the same reason; the experimental stack ignores it.

Terminal captures

Recorded from this branch's source on macOS: terminal with vhs, Studio in headless Chrome, both running at the same time against the same stack. A copy of the usebasejump/basejump sample with Analytics enabled and a small hello function generates the traffic. Before this change, Studio's Logs pages were empty and stack logs printed live output only.

supabase stack start (Docker, lazy): no Vector member.

stack-start-docker

Docker: stack logs -f (top) follows REST, Auth, Storage and Functions while requests run (bottom); lazy services wake on the first request, and Studio's PostgREST, Auth and Storage pages show the same lines.

stack-concurrent-docker

Docker, Analytics asleep: requests are written to the log files without waking Analytics; opening Studio wakes it, and the earlier lines arrive with their original timestamps.

stack-analytics-wake-docker

Native runtime: the same flow.

stack-concurrent-native

stack logs history: --tail, --since 10m, --since start and stream-json.

stack-logs-history

Stopped stack (native): history is read from the files, and -f fails before printing.

stack-logs-offline-native

Supersedes #6864.

🤖 Generated with Claude Code

@avallete avallete added the run-preview-packages Publish pkg.pr.new preview CLI packages for this PR label Sep 30, 2026
@avallete

Copy link
Copy Markdown
Member Author

/ai-review

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by a newer AI review

🤖 AI Review

Reconciled all 7 Claude and 8 Codex findings into 13 deduplicated entries. Confirmed the code-level issues, including one major shutdown-bound failure. The concurrency and legacy-output findings are narrowed to account for owner lifecycle serialization and the CLI's existing line framing. Verification used surrounding source code, trusted conventions, and isolated UTF-8 and history-selection reproductions.

Findings

Severity Location Category Sources Claim
🟠 MAJOR packages/stack/src/host/LogStore.ts:758 shutdown codex The five-second drain timeout does not bound shutdown because persistence batches, including filesystem writes and retention, are uninterruptible.
🟡 MINOR packages/stack/src/host/LogStore.ts:752 correctness claude+codex Late partial lines from ended launches do not flush after two seconds of quiet unless another output batch arrives or the store closes.
🟡 MINOR packages/stack/src/host/LogStore.ts:860 concurrency claude Concurrent calls to the store's remove and close operations can detach the same handle twice, leaving the second caller blocked forever.
🟡 MINOR packages/stack/src/Owner.ts:653 behavior-change claude The legacy logs RPC now buffers unterminated output and normalizes carriage-return progress updates into newline-terminated records, changing raw RPC and package output.
🟡 MINOR packages/stack/src/host/LogStore.ts:725 error-handling claude+codex Failed partial-line flushes can disappear without a lost marker, and previously committed lost markers can be duplicated when a later write in the same append fails.
🟡 MINOR packages/stack/src/host/LogStore.ts:893 performance claude Sequential instance detachment allows shutdown drain time to accumulate across instances instead of sharing one shutdown budget.
🟡 MINOR packages/stack/src/host/LogStore.ts:425 correctness codex The backward history scan can stop before records needed for the timestamp-ordered tail or since result.
🟡 MINOR packages/stack/src/host/LogStore.ts:351 correctness codex Readers silently skip deleted interior generations when an older undeletable segment remains.
🟡 MINOR packages/stack/src/host/LogRecord.ts:284 correctness codex A UTF-8 character split across three chunks loses its first-byte timestamp.
🟡 MINOR packages/stack/src/host/LogStore.ts:556 error-handling codex Windows sharing-violation retries do not recognize segment-listing failures after they have been wrapped in LogStoreError.
🟡 MINOR packages/stack/src/host/LogStore.integration.test.ts:72 test-cleanup codex Integration tests that ignore openStore's returned close operation leave the independently created store scope and its resource finalizers unclosed.
⚪ NIT packages/stack/src/host/LogStore.ts:522 documentation claude The live-tail documentation incorrectly describes its PubSub as unbounded and the recipe buffer as the only place output can be dropped.
⚪ NIT packages/stack/src/effect.ts:1039 input-validation claude The public readStackLogs API accepts invalid tail values without validating that they are non-negative integers.

Stats

Claude findings: 7 · Codex findings: 8 · Confirmed: 13 · Refuted: 0 · Uncertain: 0


Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: manual · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment thread packages/stack/src/host/LogStore.ts Outdated
Comment thread packages/stack/src/host/LogStore.ts Outdated
Comment thread packages/stack/src/host/LogStore.ts Outdated
Comment thread packages/stack/src/Owner.ts Outdated
Comment thread packages/stack/src/host/LogStore.ts Outdated
Comment thread packages/stack/src/host/LogStore.ts Outdated
Comment thread packages/stack/src/host/LogStore.ts Outdated
Comment thread packages/stack/src/host/LogRecord.ts Outdated
Comment thread packages/stack/src/host/LogStore.ts
Comment thread packages/stack/src/host/LogStore.integration.test.ts Outdated
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Supabase CLI preview

npx --yes https://pkg.pr.new/supabase/cli/supabase@3990b4f9d3ce97d4fec88ea3f8962c11c107b333

Preview package for commit 3990b4f.

@avallete
avallete force-pushed the avallete/stack-logs-files branch from a47d306 to 19bd0eb Compare September 30, 2026 14:18
@avallete avallete changed the title feat(stack): persist service logs to files feat(stack): persist service logs, ship them to Analytics, and add stack logs history Sep 30, 2026
avallete and others added 4 commits October 1, 2026 10:35
Service output lived only in a per-recipe in-memory buffer, so the stack
kept no log history, lost it when the owner exited, and native mode had
nothing to replay.

The owner now writes every instance's output to
`logs/<service>/<instanceId>/<generation>.log` under the stack's state
directory. Output chunks are tagged with their launch, process part,
sequence and publish time at the source, so lines are split once, never
glued across launches, and upstream loss is recorded as `lost`.
Segments rotate at 5 MiB with byte and count retention, and are removed
on instance and stack destroy.

A single position-based reader serves history, follow and offline reads.
It is exposed as a `readLogs` RPC and `readStackLogs`; the existing
`logs` RPC keeps its live-only behaviour for `supabase stack logs`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Studio's Logs pages stayed empty because nothing forwarded the stack's
service output to Analytics.

The owner now ships persisted Auth, REST, Realtime, Storage, Functions
and database lines to their Logflare sources, using the legacy Vector
remaps ported to TypeScript. It posts to Analytics' own backend only
while the composed Analytics instance is running and healthy, so
shipping never wakes it or keeps it awake. Each instance keeps a cursor
beside its log files: lines written while Analytics sleeps are shipped
after it wakes with their original timestamps.

Events carry deterministic ids and Logflare de-duplicates on them, so
failed posts are retried without duplicates. Auth and target errors
pause shipping and keep the cursor; malformed bodies are skipped.

The CLI no longer composes Vector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`supabase stack logs` now prints retained history and exits by default.
`-f/--follow` hands over from history to live output per instance
without gaps, `--tail` (default 200), `--since <duration|ISO|start>` and
a repeatable `--service` select what is shown, and history is read from
the log files when the stack is down. Output keeps the `log-entry`
contract with `source: "history" | "live"` and adds `log-marker` events.
The legacy live-only `logs` RPC is replaced by `readLogs`, which the
Promise client exposes as an async iterable.

The experimental stack no longer has a Vector service: saved stacks
that contain one are migrated on owner start, and leftover Vector files
and containers are removed. `analytics.vector_port` stays in the config
schema because the legacy `supabase start` still reads it.

PostgREST now logs every request, and request lines reach Analytics
with their method, path, protocol and status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The artifacts catalog is also the version table the legacy `supabase
start` reads in slim mode, so dropping Vector's entry would silently
fall back to the upstream Vector image there.

Vector stays in the catalog as an artifact kind that only the legacy
start runs; the experimental stack's service kinds, `supabase services`
and stack prepare still exclude it. Docker-backed log tests get the
same timeout guard as the package's other Docker tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@avallete
avallete force-pushed the avallete/stack-logs-files branch from 19bd0eb to 2ba2d89 Compare October 1, 2026 08:49
avallete and others added 2 commits October 1, 2026 12:58
- Abort log persistence 5 seconds into close, detaching instances under one
  deadline; aborted segments are retired and still release their handles.
- Flush quiet late partial lines when their grace expires.
- Count unwritten records as lost once, and clear lost markers only after
  they are written.
- Read every segment for tails and report interior segment gaps with resumeAt.
- Keep the first byte's time for UTF-8 characters split across chunks.
- Retry wrapped sharing violations, claim removed instances atomically, and
  validate the readStackLogs tail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Flush a late partial line only after every chunk published before its
  grace deadline is processed, comparing publish times.
- Report a lost marker when a reader finishes a segment and the next
  retained generation is not contiguous.
- Treat stack logs --since as a value-consuming flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hold the newline chunk behind a blocked write while the grace flusher
wakes past its deadline, so the test fails if the flush skips queued
output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@avallete
avallete marked this pull request as ready for review October 1, 2026 13:59
@avallete
avallete requested a review from a team as a code owner October 1, 2026 13:59
@avallete

avallete commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

/ai-review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

Both independent reviews were available. Verified all eight distinct findings: seven confirmed and one refuted. The major finding is a reproduced latest-launch filtering bug. Other confirmed findings concern migration contention, cleanup, follow diagnostics, redundant sweeps, and documentation. The reported global-flag scanning regression does not occur on the stack logs execution path.

Findings

Severity Location Category Sources Claim
🟠 MAJOR apps/cli/src/commands/experimental/stack/logs/logs.format.ts:116 correctness codex --since start can discard the current launch's logs and retain an older launch when that older launch's first output arrives late.
🟡 MINOR packages/stack/src/State.ts:686 robustness claude Migration acquires the global registry lock even when no Vector instances exist, introducing an unnecessary contention failure into owner startup and orphan reclamation.
🟡 MINOR packages/stack/src/State.ts:672 resource-cleanup codex Vector migration leaves the stack-owned vector.rendered.yaml behind for instances that used a custom pipeline.
🟡 MINOR apps/cli/src/commands/experimental/stack/logs/logs.handler.ts:179 error-handling claude --follow silently skips selected instances missing from the subsequently listed handles; if all are missing, the command completes successfully without explaining why following ended.
⚪ NIT packages/stack/src/host/LogForwarder.ts:375 error-handling claude Destroying a shipped instance while Analytics is serving can produce a misleading 'Log shipping ... paused' warning during normal cleanup.
⚪ NIT packages/stack/src/Sweep.ts:50 code-quality claude Session-stack orphan reclamation performs two container sweeps: one explicitly before migration and another through the namespace destroy path.
⚪ NIT apps/cli/src/command-internal/colors.ts:81 documentation codex The new magenta and blue helpers describe bright colors but select normal ANSI colors.
Refuted findings (kept for transparency, not posted as review comments)
  • apps/cli/src/command-internal/db-target-flags.ts:152 (correctness): Adding boolean -f to stack logs causes the global explicit-false scanners to skip a following --experimental=false or --yes=false token.
    Refuted: The cited helper has the described arity assumption, but it is not invoked on the stack logs execution path. The explicit-false scans run through resolveYes, resolveExperimental, and resolveDebugWithProjectEnv, which this command does not use. Stack registration instead uses resolveExperimentalFeature (stack-backend.ts:92-96), and telemetry correctly overrides f to follow (logs.command.ts:72; command-telemetry.ts:192-200). The claimed command-level regression therefore does not follow from this PR.

Stats

Claude findings: 5 · Codex findings: 3 · Confirmed: 7 · Refuted: 1 · Uncertain: 0


Models: claude-opus-5-5 + gpt-6.1-sol · Trigger: manual · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment thread apps/cli/src/commands/experimental/stack/logs/logs.format.ts Outdated
Comment thread packages/stack/src/State.ts
Comment thread packages/stack/src/State.ts
Comment thread apps/cli/src/commands/experimental/stack/logs/logs.handler.ts
Comment thread packages/stack/src/host/LogForwarder.ts Outdated
Comment on lines +50 to 53
yield* Owner.sweepContainers(saved, dataRoot);
yield* state.migrate(id);
if (saved.lifetime === "session")
yield* destroyStack(state, saved, dataRoot, options.cacheRoot);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ NIT · code-quality · source: claude

Session-stack orphan reclamation performs two container sweeps: one explicitly before migration and another through the namespace destroy path.

Evidence: Sweep.ts:50-53 invokes sweepContainers and then destroyStack for session stacks. Sweep.ts:15-20 invokes Owner.namespace.destroy, whose Owner.ts:741-743 pipeline invokes sweepContainers again.

Suggested fix: Allow reclamation to reuse proof of its completed preliminary sweep when invoking destruction. Preserve the preliminary sweep before deleting files that leftover Vector containers may still mount.

Comment thread apps/cli/src/command-internal/colors.ts Outdated
avallete and others added 2 commits October 1, 2026 19:14
- Keep launch ids increasing per instance across owner restarts so
  stack logs --since start always selects the latest launch.
- Migrate saved Vector stacks without taking the registry lock when there
  is nothing to migrate, and remove the rendered Vector config.
- Fail stack logs --follow when the running stack serves none of the
  selected instances, and warn about skipped ones.
- Detach an instance from log shipping, after its cursor write lands,
  before its logs are removed.
- Remove readStackLogs, the end read option and unused platform options;
  reuse existing helpers and trim redundant tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… one

An instance saved before launch ids were persisted resumes after the
highest launch id at the end of its newest log segment, so its next
launch never reuses an id its logs already hold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-preview-packages Publish pkg.pr.new preview CLI packages for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant