Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@

- Added the `relay-logs` command, which prints files or standard input colored by level.
- Added `-f` to follow files across rotation and truncation, and `-n` to start with the last lines of each file.
- Added `-l` to filter by level and `-g` and `-x` to include or exclude lines by pattern, keeping stack frames with their entry.
- Added the interactive viewer with following, level toggles, and regular-expression search with highlighting, and `--plain` to print lines instead.
- Added `--format cards` for piping into Relay Cards, `--format json`, and reading of Log Howl alerts.
- Added the repository banner, social preview, demo log, and keyboard and format guides.
- Added `parseLine`, which recognizes text, JSON, and logfmt lines, and `sanitizeLine`, which removes terminal control sequences.
94 changes: 79 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# Relay Logs
<p align="center">
<img src="./docs/assets/banner.png" alt="Relay Logs" width="100%">
</p>

Relay Logs reads log files and command output in the terminal. It recognizes the level, timestamp, and message of each line, whether the line is plain text, JSON, or logfmt, and colors it by level.
Relay Logs is a live log viewer for the terminal. It follows files as they grow, recognizes the level, timestamp, and message of each line, whether the line is plain text, JSON, or logfmt, and lets you filter by level and search as new lines arrive.

## Read a log
## Try the demo

```sh
relay-logs app.log
relay-logs api.log worker.log
npm run dev 2>&1 | relay-logs
npm install
npm run demo
```

When several files are given, each line is prefixed with the name of the file it came from. Use `-` to read standard input alongside files.
The demo opens a sample service log with text, JSON, and logfmt lines, a warning, and an error with its stack trace. Press `/` and type `timeout` to search, `1`–`6` to hide or show levels, `?` for the keyboard reference, and `q` to quit.

For a demonstration that prints the log and exits:

```sh
npm run demo:plain
```

## Follow a log

Expand All @@ -19,17 +26,74 @@ relay-logs -f app.log
relay-logs -f -n 50 api.log worker.log
```

`-f` keeps printing lines as files grow, like `tail -F`. It starts with the last 10 lines of each file, or the number given with `-n`. Lines appear only once their line ending has been written, so a line still being written is never split. When a file is truncated, Relay Logs reads it again from the start, and when it is replaced by log rotation, the new file is read from its first line. A file that does not exist yet is waited for.
`-f` keeps reading files as they grow, like `tail -F`. It starts with the last 10 lines of each file, or the number given with `-n`. A line appears only once its line ending has been written, a truncated file is read again from the start, a file replaced by log rotation is read from its first line, and a file that does not exist yet is waited for. Lines from several files are interleaved in the order they arrive.

In a terminal, files open in the interactive viewer. When output is piped or the log arrives on standard input, each line is printed colored by its level instead, and `--plain` does the same in a terminal:

```sh
npm run dev 2>&1 | relay-logs
```

## Viewer controls

| Key | Action |
| --------------- | ------------------------------------------------------------- |
| `↑`, `k` | Select the previous line and pause following |
| `↓`, `j` | Select the next line; reaching the newest line resumes |
| `PgUp`, `b` | Move up a page |
| `PgDn`, `space` | Move down a page |
| `g`, `G` | Jump to the first line or follow the newest one |
| `f` | Pause or resume following |
| `1`–`6` | Show or hide trace, debug, info, warn, error, and fatal lines |
| `/` | Search with a regular expression and highlight matches |
| `n`, `N` | Jump to the next newer or older match |
| `&` | Show only matching lines, or all lines again |
| `Esc` | Clear the search |
| `c` | Clear the lines read so far |
| `q` | Quit |
| `?` | Open the keyboard reference |

The viewer keeps the newest 50,000 lines. The complete reference is in [the keyboard guide](./docs/keyboard.md).

## Filter

```sh
relay-logs -l warn app.log
relay-logs -f -g timeout -g refused -x /health api.log
```

`-l` shows only entries at a level or above. Lines without a recognizable level are hidden by `-l`, except indented lines such as stack frames, which stay with the entry above them. `-g` keeps lines matching a regular expression and `-x` hides them; both may be repeated, and patterns ignore case unless they contain an uppercase letter.

## Use with Relay Cards

`--format cards` writes each entry in the form [Relay Cards](https://github.com/relay-cli/relay-cards) turns into a card:

```sh
relay-logs -f -l warn --format cards api.log | relay-cards
```

Errors become error cards that collect the stack frames below them, warnings become warning cards, and JSON records become JSON cards. `--format json` writes one object per entry with `time`, `level`, `source`, and `message`, whatever format the log was in.

## Use with Log Howl

Relay Logs reads the alerts [Log Howl](https://github.com/dispersal-wolves/log-howl) prints, showing each one at its severity with the rule that raised it:

```sh
log-howl watch --config rules.json >> alerts.jsonl &
relay-logs -f alerts.jsonl
```

Following the alert file keeps the interactive viewer available. Alerts can also be piped straight into `relay-logs --format cards | relay-cards`.

When several files are followed, their lines are interleaved in the order they arrive.
## Documentation

## Recognized lines
- [Architecture](./docs/architecture.md)
- [Recognized lines and output formats](./docs/formats.md)
- [Keyboard reference](./docs/keyboard.md)

- Text lines with an optional leading timestamp and a level such as `ERROR`, `[warn]`, `<info>`, or `FATAL:`
- JSON records with `level`, `lvl`, `severity`, or `levelname`, including pino and bunyan numeric levels
- logfmt lines with `level=` and `msg=`
## Scope

A line whose level cannot be recognized is still shown as it was written. Terminal control sequences are removed from every line before it is printed, so a log cannot move the cursor or change terminal settings.
Relay Logs reads, filters, and displays logs on the machine it runs on. Alerting on log lines belongs in a watcher such as Log Howl, and shipping logs to another host is left to the tools built for it.

## Development

Expand All @@ -38,7 +102,7 @@ npm install
npm run check
```

Relay Logs requires Node.js 22 or newer. See [the architecture notes](./docs/architecture.md) and [CONTRIBUTING.md](./CONTRIBUTING.md).
`npm run check` verifies formatting, linting, strict types, tests, and the distributable package. Relay Logs requires Node.js 22 or newer.

## License

Expand Down
6 changes: 4 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ Relay Logs turns lines from files or standard input into log entries and display
2. `openSources` opens each file, or standard input, as a labeled stream of lines. `followFile` polls a file for appended lines and notices truncation and rotation by its size and identity. `mergeSources` interleaves followed files as their lines arrive.
3. `parseLine` sanitizes a line and recognizes its level, timestamp, and message. The original text is kept in `raw`, so nothing is lost when a line is not recognized.
4. `formatPlain` prints an entry for pipes and plain terminals.

Filtering and the interactive viewer build on these modules.
5. `createFilter` decides which entries are shown by level and pattern, carrying the decision for an entry over to its indented continuation lines.
6. `LogBuffer` holds the entries the viewer can scroll through, drops the oldest past its capacity, and notifies the viewer in batches.
7. `LogViewer` renders the buffer with Ink. Its state lives in `viewer/model.ts` as plain functions: which entries are visible, which one is selected, and where search matches are. The selection is kept by sequence number, so it stays on the same line as new lines arrive and old ones are dropped.
8. `formatCards` and `formatJson` write entries for other tools, and `readLogHowlAlert` reads alerts from Log Howl. Relay Logs depends on neither project; they meet at the line format.
15 changes: 15 additions & 0 deletions docs/assets/ARTWORK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Relay Logs artwork

The repository banner and social preview follow the Relay artwork direction used by Relay Chat and Relay Cards. They use the official Relay mark from the organization profile as it is, without redrawing it.

## Banner

`banner.png` uses a 3:1 composition. The left side contains the Relay mark, the product name, and a direct description. The right side depicts the real viewer: the header with the visible levels and the following indicator, log lines with their time, level, source, and message, an error selected with its stack frame below it, a highlighted search match, and the search prompt.

## Social preview

`.github/social-preview.png` recomposes the same interface for a 2:1 thumbnail. The description is removed, and the mark and name are stacked so they stay legible in repository cards.

## Direction

Both images use the shared Relay palette (background `#080B0F`, text `#E8EDF2`, blue `#48A8FF`, orange `#FF8A3D`, red `#FF5C6C`, yellow `#F4C95D`, green `#5DD39E`), monospace interface typography, and restrained terminal borders. The content mirrors what the viewer renders, with no decorative code, robots, or imagery of other products.
Binary file added docs/assets/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions docs/formats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Recognized lines and output formats

## Recognized lines

Relay Logs reads the level, timestamp, and message of each line:

| Input | Recognized when |
| -------- | ----------------------------------------------------------------------------------------------------- |
| Text | An optional leading timestamp is followed by a level such as `ERROR`, `[warn]`, `<info>`, or `FATAL:` |
| JSON | The record has `level`, `lvl`, `severity`, or `levelname`, including pino and bunyan numeric levels |
| logfmt | The line has `level=`, with the message in `msg=` |
| Log Howl | The record uses the `dispersal-wolves/log-howl-event/v1` schema |

Level names are read generously: `warning` is `warn`, `err` is `error`, `notice` is `info`, and `critical`, `emerg`, and `panic` are `fatal`. A line whose level cannot be recognized is still shown as it was written, and indented lines such as stack frames stay with the entry above them when filtering.

Terminal control sequences and control characters are removed from every line before it is shown, so a log cannot move the cursor, change the window title, or create hyperlinks.

## Log Howl alerts

Alerts are shown at their severity, with the rule name before the matched line (`failed-login: Failed password for …`). When the alert arrives on standard input, its source is the file Log Howl was watching. Severities outside the usual level names map `low` to `info`, `medium` to `warn`, and `high` to `error`.

## Output formats

`--format` chooses how printed lines are written. Any format other than `text` prints lines instead of opening the viewer.

| Format | Output |
| ------- | ---------------------------------------------------------------------------------------------------- |
| `text` | Each line as it was written, colored by level, prefixed with its file when several files are read |
| `json` | One object per entry with `time`, `level`, `source`, and `message` |
| `cards` | Lines Relay Cards turns into cards: `[time source] ERROR: message`, warnings, JSON, and stack frames |
41 changes: 41 additions & 0 deletions docs/keyboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Keyboard reference

The viewer opens when standard input and output are both terminals. Press `?` inside it for the same list.

## Moving

| Key | Action |
| --------------- | ------------------------------------------------------ |
| `↑`, `k` | Select the previous line and pause following |
| `↓`, `j` | Select the next line; reaching the newest line resumes |
| `PgUp`, `b` | Move up a page |
| `PgDn`, `space` | Move down a page |
| `g`, `Home` | Jump to the first line |
| `G`, `End` | Follow the newest line |
| `f` | Pause or resume following |

While paused, new lines are still read and counted in the header, and the selected line stays where it is.

## Levels

`1` through `6` show or hide trace, debug, info, warn, error, and fatal lines. Hidden levels are struck through in the header. Lines without a level are never hidden this way.

## Search

| Key | Action |
| ----- | ----------------------------------------------- |
| `/` | Type a pattern and press `Enter`; `Esc` cancels |
| `n` | Jump to the next newer match |
| `N` | Jump to the next older match |
| `&` | Show only matching lines, or all lines again |
| `Esc` | Clear the search |

Patterns are regular expressions and ignore case unless they contain an uppercase letter. Committing a search selects the nearest match at or above the selected line.

## Other

| Key | Action |
| ------------- | ---------------------------- |
| `c` | Clear the lines read so far |
| `?` | Open or close this reference |
| `q`, `Ctrl+C` | Quit |
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
"scripts": {
"build": "tsup",
"dev": "tsx src/cli.tsx",
"demo": "tsx src/cli.tsx examples/app.log",
"demo:plain": "tsx src/cli.tsx --plain examples/app.log",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint .",
Expand Down
Loading
Loading