Documentation
¶
Overview ¶
Package logs owns the daemon-side log storage and streaming hub.
Storage shape:
$XDG_STATE_HOME/a-novel/logs/<stack>/<service>/<target>/ ├── current.log ← currently-streaming run ├── run-<timestamp>.log ← archived previous runs (max 5) └── ...
Format: one JSON object per line — {ts, stream, line} — preserving ANSI escapes in `line` so the CLI/TUI can render colored output and `jq` can filter by stream.
Streaming hub: the Store also owns subscriber channels per (stack, service, target). When the runner appends a line, every subscriber gets it; the hub closes the channel when the proc terminates. The StreamLogs RPC handler attaches a subscriber and forwards lines over connect-rpc until the channel closes or the client disconnects.
Index ¶
- type Line
- type Store
- func (s *Store) CloseTarget(targetID string)
- func (s *Store) CurrentPath(stack, service, target string) string
- func (s *Store) ListRuns(stack, service, target string) []string
- func (s *Store) OpenForWrite(targetID, stack, service, target string) (*Writer, error)
- func (s *Store) RunPath(stack, service, target, runID string) string
- func (s *Store) Subscribe(targetID string) (<-chan Line, func(), bool)
- type Stream
- type Writer
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Line ¶
type Line struct {
Ts time.Time `json:"ts"`
Stream Stream `json:"stream"`
Line string `json:"line"`
}
Line is one log entry as we serialize it to disk and forward to subscribers.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the daemon's per-target log registry. Concurrency-safe; one Store per daemon.
func New ¶
func New() *Store
New returns an empty Store. Its stream registry populates on OpenForWrite; nothing is touched on disk until a target actually emits.
func (*Store) CloseTarget ¶
CloseTarget marks the target's stream as done — closes the file handle, closes every subscriber channel, removes the stream record. Called by the runner when an instance reaches PHASE_TERMINATED.
func (*Store) CurrentPath ¶
CurrentPath returns the path of the active current.log for a target.
func (*Store) ListRuns ¶
ListRuns returns the timestamps of every archived run for `targetID`, newest first.
func (*Store) OpenForWrite ¶
OpenForWrite prepares the log file for a fresh run of `targetID`. The existing current.log (if any) rotates to a timestamped archive, the oldest archives are pruned, and a new current.log is created. Returns a Writer the runner can pipe stdout / stderr through.
func (*Store) Subscribe ¶
Subscribe adds a delivery channel for `targetID` and returns it alongside an unsubscribe function the caller must defer-call on exit. Without that call, the subscriber channel stays in the stream's fanout list until the target itself terminates — a leak that grows linearly with disconnect rate for long-running targets.
The channel is buffered modestly (32 slots); slow subscribers that don't drain get dropped lines silently — the daemon never blocks the runner on a stuck client.
Returns (nil, nil, false) if no stream exists yet for the target — the caller can poll back later or read the archived run instead.
type Stream ¶
type Stream string
Stream is one of stdout / stderr — matches the proto enum but kept as a local type so this package doesn't depend on the generated code.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer adapts the targetStream into an io.Writer that the runner can hand to exec.Cmd.Stdout / .Stderr. Each Write splits on newlines, JSON-encodes each line with {ts, stream, line}, writes to the per-target file, and fans out to every subscriber.
A single Writer is shared by both the Stdout and Stderr views; each view carries the actual stream tag. Per-stream partials live on streamWriter.