---
title: logs
---

# logs

View stored output from managed services.

```sh
$ sysg logs
```

> **Info:** From the interactive `sysg status` table, select a UNIT with
> Tab/arrow navigation and press **L** to jump straight into that unit's logs.
> The status shortcut runs `sysg 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 name of the service whose logs should be displayed (optional) |
| `-p` | `--project` | Filter logs by stable project id |
| `-l` | `--lines` | Number of lines to show (default: 50) |
| `-k` | `--kind` | Kind of logs to show: stdout, stderr, or supervisor. Omit this flag 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`) |
| `-` | `--sys` | Opt into privileged system mode. Requires running as root |
| `-` | `--log-level` | Set verbosity (`debug`, `info`, `warn`, `error`) |

## Examples

### View recent logs from all services

```sh
$ sysg logs
```

### View logs from specific service

```sh
$ sysg logs --service api
```

To disambiguate a service in a multi-project supervisor, provide the stable
project id:

```sh
$ sysg logs --project arbitration --service api
```

> **Info:** By default, `sysg logs` shows 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 stdout` or `--kind stderr` when 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` / `-f` always follows, until you interrupt it.
- `--no-follow` always prints a one-shot snapshot and exits.
- With neither flag, systemg follows **only** when stdout is an interactive
  terminal and `SYSTEMG_AGENT` is unset. When stdout is a pipe, a file, an SSH
  command, or `SYSTEMG_AGENT` is 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.

```sh
$ sysg logs --service api --follow      # stay attached (interactive)
$ sysg logs --service api --no-follow    # one snapshot, then exit
$ sysg logs --service api | grep ERROR   # auto one-shot: stdout is a pipe
```

### Purge logs for a specific service

```sh
$ 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

```sh
$ sysg logs --purge
```

This truncates all service log files, plus `supervisor.log`, in place and does not print logs.

### View stderr logs

```sh
$ sysg logs --service api --kind stderr
```

### View supervisor logs

```sh
$ sysg logs --kind supervisor
```

### Show more history

```sh
$ sysg logs --service api --lines 200
```

### Filter by pattern

```sh
$ 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

```sh
$ 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

```sh
$ sysg logs --service api --all --grep ERROR
```

By default `sysg logs` reads only the tail of the active `{service}.log`. `--all`
concatenates the rotated backups (`{service}.log.2`, `{service}.log.1`) followed
by the active file, oldest to newest, so you can grep or time-window across the
entire retained history in one command instead of `cat`-ing each rotation by
hand. `--all` ignores `--lines`.

### Machine-readable output

```sh
$ 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

```sh
$ 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.

```sh
$ sysg logs --service api --format json --grep ERROR | jq -r .line
```

### Raw application output

```sh
$ 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 logs` strips those escapes so downstream tools (`grep`, `jq`, `awk`) see
> clean text. Pass `--no-strip-ansi` to keep the color, or `--strip-ansi` to
> force stripping even on an interactive terminal.

### Stream snapshots

```sh
$ sysg logs --service api --lines 200 --stream 2
```

In stream mode, each refresh prints a new snapshot of the latest `--lines` entries.

### Prune rotated backups

```sh
$ 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 exists
- `supervisor.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:

```sh
$ 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&nbsp;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&nbsp;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:

```text
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 `--raw` to drop systemg's prefix
> and keep only the application's line, or `--format json` to 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 both streams through one per-service writer into `{service}.log`. On Linux and macOS, the follow view (`--follow`, or the interactive default) tails that file directly with `tail -F`; the one-shot view (`--no-follow`, or the default when stdout is not an interactive terminal) reads the tail once and exits. `--kind stdout` and `--kind stderr` filter recent 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.

## See also

- [`status`](/how-it-works/commands/status) - Check service health
- [`inspect`](/how-it-works/commands/inspect) - View detailed metrics
