Commands
logs
View stored output from managed services.
$ sysg logs -p myapp
Info: From the interactive
sysg statustable, select a UNIT with Tab/arrow navigation and press L to jump straight into that unit's logs. The status shortcut runssysg logs -s <UNIT> -l 100 --stream 2, which shows the latest 100 lines and refreshes the snapshot every 2 seconds.
Options
| Short | Long | Description |
|---|---|---|
-c | --config | Path to configuration file |
- | --purge | Reset log files instead of displaying them |
- | --prune | Delete rotated log backups instead of displaying logs (requires --max-size and/or --max-age) |
- | --max-size | When pruning, cap total rotated-backup size (e.g. 500MB, 2g) |
- | --max-age | When pruning, remove rotated backups older than this (e.g. 7d, 12h) |
-s | --service | The service whose logs to show. With no -p, resolves to the loose (__loose__) bundle only |
-p | --project | The project whose logs to show (all its services, or one with -s) |
- | --supervisor | Show the supervisor's own log instead of a service's. Cannot be combined with -s/-p |
-l | --lines | Trailing lines to show. Defaults to the latest 100; pass -l N to choose another limit |
-k | --kind | Kind of logs to show: stdout or stderr. Omit to show stdout+stderr together |
-f | --follow | Follow the log stream until interrupted (like tail -F) |
- | --no-follow | Force a one-shot snapshot even on an interactive terminal |
- | --since | Only show lines captured at or after this time (RFC3339, YYYY-MM-DD, or a relative age like 30m/2h/7d) |
- | --until | Only show lines captured at or before this time (same formats as --since) |
-g | --grep | Only show lines matching this regular expression |
-a | --all | Read the full active-plus-rotated history instead of the last --lines |
- | --path | Print the on-disk log path(s) instead of the logs, then exit |
- | --format | Machine-readable output. json prints one {ts, stream, service, line} object per line |
- | --raw | Print only the application's original line, dropping systemg's timestamp/stream prefix |
- | --strip-ansi | Strip ANSI color/escape sequences (default on for --format json, --raw, and non-interactive output) |
- | --no-strip-ansi | Keep ANSI escape sequences even when they would be stripped by default |
- | --stream | Continuously refresh the latest log snapshot at the provided interval (e.g., 5, 1s, 2m) |
-v | --verbose | Print operation progress |
- | --sys | Opt into privileged system mode. Requires running as root |
- | --drop-privileges | Accepted globally but ignored; logs does not spawn services |
- | --plain | Disable color, banners, paging, and implicit log following |
- | --log-level | Set logging verbosity for this invocation (trace through off, or 5-0) |
Scoping
Logs are always scoped — a bare sysg logs is refused
(SG0019) so you never get the wrong
project's output. Each project's logs live in their own place
({project}/{service}), so two projects can share a service name without their
logs colliding.
| You run | You get |
|---|---|
logs -p <project> | all of that project's services |
logs -p <project> -s <service> | that one service |
logs -s <service> (no -p) | the loose (__loose__) service of that name only |
logs --supervisor | the supervisor's own log |
logs (nothing) | refused — SG0019 |
Note
Modes
logs is really five modes: show (the default), follow (--follow), print-path
(--path), purge (--purge), and prune (--prune). The mode flags are mutually
exclusive, and none combines with --follow — an illegal combination is refused
with SG0204 rather than guessed at.
Note
--purge clears logs through the running supervisor when one is up, so both
the on-disk files and the supervisor's in-memory buffer that sysg logs replays
from are dropped together. Clearing files alone would leave sysg logs still
showing the "purged" lines.
Warning
--prune needs a bound to prune against. Running it with neither --max-size
nor --max-age is refused with SG0017.
Examples
View one project's service logs
$ sysg logs --project myapp
View logs from specific service
$ sysg logs --service api
To disambiguate a service in a multi-project supervisor, provide the stable project id:
$ sysg logs --project arbitration --service api
Info: By default,
sysg logsshows a stacked stdout+stderr stream in capture order. systemg adds its own UTC capture timestamp and stream label to service output as it is written, then reads the combined service log for the default view. Use--kind stdoutor--kind stderrwhen you need a single stream only.
Follow mode and non-interactive callers
Whether sysg logs follows the stream (stays attached and prints new lines as
they arrive) or prints a one-shot snapshot and exits depends on how it is
invoked:
--follow/-falways follows, until you interrupt it. In a terminal, press Esc or Ctrl-C to stop following and return to the shell; the stream reconnects on its own if the supervisor connection drops, so it never freezes silently while a service is still logging.--no-followalways prints a one-shot snapshot and exits.- With neither flag, systemg follows only when stdout is an interactive
terminal and
SYSTEMG_AGENTis unset. When stdout is a pipe, a file, an SSH command, orSYSTEMG_AGENTis set, it prints a one-shot snapshot and exits.
This keeps automation from wedging: a plain sysg logs -s api over
ssh host 'sysg logs -s api', sysg logs -s api | grep error, or any agent
returns immediately instead of blocking on a follow that can never be
interrupted.
$ sysg logs -p arbitration-dev -s arb_rs__dev --lines 0 --follow
$ sysg logs --service api --no-follow # one snapshot, then exit
$ sysg logs --service api | grep ERROR # auto one-shot: stdout is a pipe
--lines 0 --follow is the standard live-streaming form: it skips existing
log history, stays attached, and prints only newly emitted lines.
Purge logs for a specific service
$ sysg logs --service api --purge
This truncates api's current stdout and stderr log files in place and does not print logs.
Purge logs for all services
$ sysg logs --purge
This truncates all service log files, plus supervisor.log, in place and does not print logs.
View stderr logs
$ sysg logs --service api --kind stderr
View supervisor logs
$ sysg logs --supervisor
Show more history
$ sysg logs --service api --lines 200
Filter by pattern
$ sysg logs --service api --grep ERROR
--grep takes a regular expression and keeps only matching lines. It composes
with --kind, the time bounds, and --all.
Filter by time window
$ sysg logs --service api --since 2h
$ sysg logs --service api --since 2026-07-07 --until 2026-07-07T12:00:00Z
Time bounds are matched against systemg's capture timestamp (the UTC prefix on
each stored line). --since / --until accept an RFC3339 timestamp, a bare UTC
date (taken as midnight), or a relative age in the past (30m, 2h, 7d).
Because following cannot honor an upper bound, any --since / --until /
--all read is a one-shot snapshot.
Read the full rotated history
$ sysg logs --service api --all --grep ERROR
By default sysg logs reads the latest 100 lines from the active
{service}.log; pass --lines N to choose another tail size. --all
concatenates the rotated backups
({service}.log.2, {service}.log.1) followed by the active file, oldest to
newest, so you can query the entire retained history without joining files by
hand. --all ignores --lines.
Machine-readable output
$ sysg logs --service api --format json
--format json emits one JSON object per captured line —
{"ts","stream","service","line"} — dropping banners and section headers so the
stream is a clean JSON-lines document. ANSI escapes are stripped automatically.
Project-wide filtering and aggregation
$ sysg logs --project arbitration --all --since 2026-07-08 --grep openai_ --format json --no-follow
Every content flag — --grep, --since / --until, and --all — applies to
project-wide reads (--project, or the no-service default) exactly as it does to
a single --service. Each JSON record carries the resolved service name of the
unit that produced the line, so you can aggregate by service without querying each
one separately.
$ sysg logs --service api --format json --grep ERROR | jq -r .line
Raw application output
$ sysg logs --service api --raw
--raw prints only the application's original log line, without systemg's
capture timestamp and stream label — the closest equivalent to reading the
service's own stdout. Banners are dropped and ANSI is stripped by default.
ANSI handling
systemg records service output verbatim, so lines a service colored for its own terminal are stored with their ANSI escapes.
Info: When output is a pipe, a file, or
--format json/--raw,sysg logsstrips those escapes so downstream tools (grep,jq,awk) see clean text. Pass--no-strip-ansito keep the color, or--strip-ansito force stripping even on an interactive terminal.
Stream snapshots
$ sysg logs --service api --lines 200 --stream 2
In stream mode, each refresh replaces the current frame with a new snapshot of
the latest --lines entries. Without --lines, each frame contains the latest
100 lines.
Prune rotated backups
$ sysg logs --prune --max-size 500MB --max-age 7d
Deletes rotated backup files ({name}.log.1, {name}.log.2, …) older than
--max-age and then trims the remaining backups oldest-first until their total
size is under --max-size. Active .log files are never touched. At least one
of --max-size or --max-age is required.
Log files
Logs are stored in ~/.local/share/systemg/logs/:
{service}.log- Canonical stdout+stderr stream in capture order{service}_stdout.log/{service}_stderr.log- Legacy split-stream files from older systemg versions, still read as a fallback when no canonical log existssupervisor.log- systemg's own log output
Locate log files
Use --path to print where logs live instead of reading them — handy when you
need to point another tool at the files directly:
$ sysg logs --path # the log directory
$ sysg logs --service api --path # that service's active log file
$ sysg logs --service api --path --all # active file plus rotated backups
Rotation and retention
Every active log file (service logs and supervisor.log) is size-capped. When
an active file would exceed logs.max_bytes (default 10 MiB) it is rotated
to {name}.log.1, older backups shift up, and at most logs.max_files
(default 5) numbered backups are retained. A single log event larger than
16 KiB is truncated with a …[truncated N bytes] marker before it is
written, so one oversized payload cannot blow past the size cap. Tune the caps
with the logs.max_bytes / logs.max_files config keys, globally or per
service.
systemg's own verbosity is info by default and honors per-crate RUST_LOG
directives (for example RUST_LOG=mycrate=debug,noisycrate=warn), so enabling
debug for one target does not pull in dependency-wide debug noise.
Each captured service line is stored with a systemg capture timestamp and stream label:
2026-05-14T02:05:13.470911Z stderr Compiling tungstenite v0.28.0
2026-05-14T02:05:13.471020Z stdout request completed status=200
Info: The leading timestamp is systemg's own capture time; a service that prints its own timestamp will show both. Use
--rawto drop systemg's prefix and keep only the application's line, or--format jsonto get the timestamp, stream, and message as separate fields.
Performance model
For the default file logging sink, systemg captures each service's stdout and stderr through pipes and writes complete, immediately flushed lines through one per-service writer into {project}/{service}.log. A single-service follow (--follow, or the interactive default) tails that canonical file. A project-wide follow atomically takes a recent complete-line backlog and subscribes to future lines from the supervisor so several services can share one stream. The one-shot view (--no-follow, or the default when stdout is not an interactive terminal) reads the persisted active file once and exits. --kind stdout and --kind stderr filter captured lines by the stored stream label. The command itself is lightweight; the continuous cost comes from the capture threads, one writer thread, and one append-only file write path per service.
For high-output production workloads, configure logs.sink: none globally or per service to discard service output and avoid systemg log-writer threads, pipe backpressure from log capture, and file growth.