logs

package
v1.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 25, 2026 License: MIT Imports: 9 Imported by: 0

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

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. Subscriber maps populate on OpenForWrite / Subscribe; nothing is touched on disk until a target actually emits.

func (*Store) CloseTarget

func (s *Store) CloseTarget(targetID string)

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

func (s *Store) CurrentPath(stack, service, target string) string

CurrentPath returns the path of the active current.log for a target.

func (*Store) ListRuns

func (s *Store) ListRuns(stack, service, target string) []string

ListRuns returns the timestamps of every archived run for `targetID`, newest first.

func (*Store) OpenForWrite

func (s *Store) OpenForWrite(targetID, stack, service, target string) (*Writer, error)

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) RunPath

func (s *Store) RunPath(stack, service, target, runID string) string

RunPath returns the path of an archived run.

func (*Store) Subscribe

func (s *Store) Subscribe(targetID string) (<-chan Line, func(), bool)

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.

const (
	StreamStdout Stream = "stdout"
	StreamStderr Stream = "stderr"
)

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 (see below).

func (*Writer) Close

func (w *Writer) Close() error

Close finalizes the underlying stream (closes file + subscribers). Idempotent.

func (*Writer) Stderr

func (w *Writer) Stderr() io.Writer

func (*Writer) Stdout

func (w *Writer) Stdout() io.Writer

Stdout / Stderr return a new Writer view bound to this stream tag. The two views share the underlying targetStream's file + subscribers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL