Simplify docs and add concrete examples for input fields
Rewrites doc-panel descriptions to be direct and absolute (no edge-case hedging), and adds concrete example values for params, metrics, filter queries, alert conditions, and CLI arguments. Also updates the Alerts/ Settings/Presets sections to describe the ntfy-sink-in-Settings and profile-dropdown changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e69ce4e0ba
commit
0a21582282
+127
-77
@@ -18,43 +18,49 @@ const SECTIONS: DocSection[] = [
|
||||
title: 'General idea',
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
ottyl turns a CLI program's stdout into a Grafana-like dashboard. Pipe a program's output into it, and it
|
||||
indexes each line in memory as it arrives while serving a dashboard from the same process.
|
||||
</p>
|
||||
<p>ottyl turns a CLI program's stdout into a dashboard. Pipe a program's output into it:</p>
|
||||
<p>
|
||||
<Code>{'cli_command --verbose | ottyl'}</Code>
|
||||
</p>
|
||||
<p>
|
||||
Everything — log lines, params, metrics, and derived buckets — lives in one in-memory index for the
|
||||
process's lifetime. Nothing touches disk unless you save a <em>profile</em> or change a <em>setting</em>.
|
||||
It indexes each line in memory as it arrives and serves the dashboard from the same process. Everything —
|
||||
log lines, params, metrics, and derived buckets — lives in that in-memory index for the process's
|
||||
lifetime. Nothing touches disk unless you save a <em>profile</em> or change a <em>setting</em>.
|
||||
</p>
|
||||
<p className="font-semibold text-text">CLI arguments</p>
|
||||
<ul className="list-disc pl-5">
|
||||
<li>
|
||||
<Code>--port</Code> — port to serve the API and webapp on (default <Code>8420</Code>).
|
||||
<Code>--port</Code> — port to serve on. Example: <Code>ottyl --port 9000</Code>. Default: an OS-assigned
|
||||
free port.
|
||||
</li>
|
||||
<li>
|
||||
<Code>--static-dir</Code> — directory containing the built webapp (default <Code>webapp/dist</Code>).
|
||||
<Code>--settings-path</Code> — path to the settings file. Example:{' '}
|
||||
<Code>ottyl --settings-path ./ottyl-settings.json</Code>. Default: a per-OS config location, shown by{' '}
|
||||
<Code>ottyl --help</Code>.
|
||||
</li>
|
||||
<li>
|
||||
<Code>--settings-path</Code> — path to the settings file; defaults to a per-OS config location (e.g.{' '}
|
||||
<Code>~/.config/ottyl/settings.json</Code> on Linux).
|
||||
<Code>--file</Code> — tail a file instead of reading stdin, like <Code>tail -f</Code>. Example:{' '}
|
||||
<Code>ottyl --file /var/log/app.log</Code>.
|
||||
</li>
|
||||
<li>
|
||||
<Code>--metrics-url</Code> — poll a Prometheus/OpenMetrics endpoint for metrics, alongside log ingestion.
|
||||
Example: <Code>ottyl --metrics-url http://localhost:9100/metrics</Code>. Scraped samples are queryable by
|
||||
metric name in PromQL and dashboards immediately, with no declaration step.
|
||||
</li>
|
||||
</ul>
|
||||
<p className="font-semibold text-text">Settings</p>
|
||||
<p>
|
||||
There is one <em>settings</em> file per machine, holding the colour scheme, any imported/linked CSS, and
|
||||
One <em>settings</em> file per machine holds the colour scheme, imported/linked CSS, the ntfy sink, and
|
||||
which profile is active. Settings only tracks profile pointers — the profiles themselves (params, metrics,
|
||||
dashboards) are stored separately, next to the settings file by default.
|
||||
</p>
|
||||
<p className="font-semibold text-text">Profiles</p>
|
||||
<p>
|
||||
A profile bundles the parsing/indexing rules for one CLI app — its params and metrics, plus any saved
|
||||
dashboards — so restarting ottyl against the same program doesn't lose your setup. A <Code>Default</Code>{' '}
|
||||
profile always exists and can't be deleted. Creating a new profile copies the active one (or starts empty
|
||||
if none is active), so variations build on what you already have. Switching profiles hot-swaps the running
|
||||
engine's declared params/metrics immediately, with no restart needed.
|
||||
dashboards — so restarting ottyl against the same program keeps your setup. The <Code>Default</Code>{' '}
|
||||
profile always exists and can't be deleted. The profile dropdown's "+" button creates a new profile as a
|
||||
copy of the active one, and its "×" button deletes the active profile. Switching profiles hot-swaps the
|
||||
running engine's declared params/metrics immediately, with no restart.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -65,30 +71,43 @@ const SECTIONS: DocSection[] = [
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The filter bar takes a query combining regex search over raw log text with comparisons against declared
|
||||
metrics, joined by <Code>and</Code>/<Code>or</Code>:
|
||||
The filter bar combines regex search over raw log text with comparisons against declared metrics, joined
|
||||
by <Code>and</Code>/<Code>or</Code>. Examples:
|
||||
</p>
|
||||
<p>
|
||||
<Code>{'regex("error") and sequence<5 or timestamp>12:00'}</Code>
|
||||
</p>
|
||||
<p>
|
||||
<Code>{'metric_name[param]<5'}</Code> compares a metric's captured param against a number, string, or
|
||||
timestamp. <Code>sequence</Code> and <Code>timestamp</Code> are reserved metric names present on every log
|
||||
line, so they're always filterable without declaring anything.
|
||||
</p>
|
||||
<p className="font-semibold text-text">Date/time formats</p>
|
||||
<p>Timestamp comparisons accept several forms:</p>
|
||||
<ul className="list-disc pl-5">
|
||||
<li>
|
||||
relative, humantime-style offsets from now — <Code>5h</Code>, <Code>-3m</Code>
|
||||
<Code>{'regex("error")'}</Code> — lines containing "error".
|
||||
</li>
|
||||
<li>
|
||||
<Code>{'regex("error") and sequence<5'}</Code> — lines containing "error" with sequence under 5.
|
||||
</li>
|
||||
<li>
|
||||
<Code>{'requests[duration]>100'}</Code> — the <Code>requests</Code> metric's captured{' '}
|
||||
<Code>duration</Code> param over 100.
|
||||
</li>
|
||||
<li>
|
||||
<Code>{'timestamp>-5m'}</Code> — lines from the last 5 minutes.
|
||||
</li>
|
||||
<li>absolute clock times — e.g. 12:00</li>
|
||||
<li>full dates</li>
|
||||
</ul>
|
||||
<p>
|
||||
The same filter string goes to both <Code>GET /api/logs</Code> and <Code>GET /api/timeline</Code> as the{' '}
|
||||
<Code>filter</Code> query param and is evaluated on the server; a parse error surfaces as inline text under
|
||||
the filter bar rather than crashing the page.
|
||||
<Code>sequence</Code> and <Code>timestamp</Code> are reserved metric names present on every log line, so
|
||||
they're always filterable without declaring anything.
|
||||
</p>
|
||||
<p className="font-semibold text-text">Date/time formats</p>
|
||||
<p>Timestamp comparisons accept three forms:</p>
|
||||
<ul className="list-disc pl-5">
|
||||
<li>
|
||||
relative offsets from now — <Code>5h</Code> (5 hours from now), <Code>-3m</Code> (3 minutes ago)
|
||||
</li>
|
||||
<li>
|
||||
absolute clock times — <Code>12:00</Code>, <Code>09:30:00</Code>
|
||||
</li>
|
||||
<li>
|
||||
full dates — <Code>2026-01-15</Code>, <Code>2026-01-15T09:30:00Z</Code>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
The filter string is evaluated on the server. A parse error shows as inline text under the filter bar.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -99,20 +118,28 @@ const SECTIONS: DocSection[] = [
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
A parameter declaration tells ottyl how to derive a named value from a log line: a regex the message must
|
||||
match, a capture group, and a type (<Code>string</Code>, <Code>number</Code>, or <Code>timestamp</Code>).
|
||||
Declaring one retroactively indexes every already-ingested line and checks every new line as it arrives.
|
||||
A parameter declaration derives a named value from a log line: a regex the message must match, a capture
|
||||
group, and a type (<Code>string</Code>, <Code>number</Code>, or <Code>timestamp</Code>). Example:
|
||||
</p>
|
||||
<ul className="list-disc pl-5">
|
||||
<li>
|
||||
Name: <Code>duration</Code>
|
||||
</li>
|
||||
<li>
|
||||
Regex: <Code>{'duration=(?P<duration>\\d+)ms'}</Code>
|
||||
</li>
|
||||
<li>
|
||||
Capture group: <Code>duration</Code>
|
||||
</li>
|
||||
<li>
|
||||
Type: <Code>number</Code>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Params are the building blocks metrics are made of (a metric is really a message-match regex plus a set of
|
||||
param matchers), and they drive filtering and the log preview's column picker — declaring a{' '}
|
||||
<Code>duration</Code> param, for instance, makes it available as both a log-table column and a comparable
|
||||
value in the filter bar.
|
||||
</p>
|
||||
<p>
|
||||
The Params panel declares a bare param directly (<Code>POST /api/params</Code>) — the same request the
|
||||
Metrics panel's declaration form makes per param matcher. Declaring one there updates the log preview's
|
||||
column picker immediately, with no page reload needed.
|
||||
Declaring a param indexes every already-ingested line immediately, then checks every new line as it
|
||||
arrives. It shows up right away as a column in the log table. To filter on it (e.g.{' '}
|
||||
<Code>requests[duration]>100</Code>), declare it as a param on a metric instead — see the Metrics
|
||||
section below.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -127,11 +154,10 @@ const SECTIONS: DocSection[] = [
|
||||
select a fixed range, narrowing the log table, metrics, and dashboards below to that window.
|
||||
</p>
|
||||
<p>
|
||||
The preset buttons (<Code>All</Code>, <Code>5m</Code>, <Code>15m</Code>, <Code>1h</Code>,{' '}
|
||||
<Code>24h</Code>) are Grafana-style shortcuts: <Code>All</Code> clears the range entirely, and a relative
|
||||
preset is <em>rolling</em> — its window keeps advancing to stay "last N minutes from now" instead of
|
||||
freezing at selection time. Dragging while a preset is active switches to a fixed (non-rolling) range, same
|
||||
as a normal drag.
|
||||
The preset buttons — <Code>All</Code>, <Code>1m</Code>, <Code>2m</Code>, <Code>5m</Code>, <Code>15m</Code>,{' '}
|
||||
<Code>1h</Code>, <Code>24h</Code> — are quick-range shortcuts. <Code>All</Code> clears the range. Every
|
||||
other preset is <em>rolling</em>: its window keeps advancing to stay "last N minutes from now" instead of
|
||||
freezing at selection time. Dragging while a preset is active switches to a fixed range.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -144,7 +170,11 @@ const SECTIONS: DocSection[] = [
|
||||
<p>
|
||||
The log table lists ingested lines newest-last, each with its sequence number, timestamp, and raw text. The
|
||||
column picker (top-right) adds a column per declared param, showing that line's derived value (blank if it
|
||||
didn't match) — a quick way to eyeball params without switching to the Metrics panel.
|
||||
didn't match).
|
||||
</p>
|
||||
<p>
|
||||
Scrolling stays anchored to the row you're looking at as new lines arrive or old ones age out of a rolling
|
||||
window — the view doesn't jump when content is pruned from the top.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -155,16 +185,29 @@ const SECTIONS: DocSection[] = [
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
Every metric is fundamentally a <em>counter</em>: a message-match regex paired with one or more named param
|
||||
matchers (each with its own capture group and type). Every log line matching the regex is one counter
|
||||
increment — a timestamped occurrence, tagged with the labels captured from that line.
|
||||
Every metric is a <em>counter</em>: a message-match regex paired with one or more named param matchers
|
||||
(each with its own capture group and type). Every log line matching the regex is one counter increment — a
|
||||
timestamped occurrence, tagged with the labels captured from that line. Example:
|
||||
</p>
|
||||
<ul className="list-disc pl-5">
|
||||
<li>
|
||||
Name: <Code>requests</Code>
|
||||
</li>
|
||||
<li>
|
||||
Regex: <Code>{'request path=(?P<path>\\S+) duration=(?P<duration>\\d+)ms'}</Code>
|
||||
</li>
|
||||
<li>
|
||||
Params: <Code>path</Code> (capture group <Code>path</Code>, type <Code>string</Code>, role{' '}
|
||||
<Code>label</Code>), <Code>duration</Code> (capture group <Code>duration</Code>, type <Code>number</Code>
|
||||
, role <Code>value</Code>)
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Each param is either a <strong>label</strong> (the default) or the metric's single <strong>value</strong>{' '}
|
||||
param. Labels identify which bucket an occurrence belongs to; a value param's captured number becomes the
|
||||
occurrence's stored value instead of a label — e.g. a <Code>duration</Code> param marked <em>value</em>{' '}
|
||||
lets you query the distribution or latest reading, not just how often it occurred. A metric with only
|
||||
label params defaults every occurrence's value to a fixed <Code>1</Code> (a pure occurrence count).
|
||||
param. Labels identify which bucket an occurrence belongs to. A value param's captured number becomes the
|
||||
occurrence's stored value instead of a label — in the example above, <Code>duration</Code> lets you query
|
||||
the distribution or latest reading, not just how often the line occurred. A metric with only label params
|
||||
defaults every occurrence's value to a fixed <Code>1</Code> (a pure occurrence count).
|
||||
</p>
|
||||
<p>
|
||||
Declaring a metric backfills it against everything already ingested, then keeps indexing new lines as they
|
||||
@@ -232,39 +275,44 @@ const SECTIONS: DocSection[] = [
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
An alert rule fires a notification when its condition matches a newly-ingested log line, evaluated per line
|
||||
as it arrives rather than backfilled against history. There are four condition types:
|
||||
as it arrives. There are four condition types:
|
||||
</p>
|
||||
<ul className="list-disc pl-5">
|
||||
<li>
|
||||
<span className="font-semibold text-text">Immediate</span> — fires once per matching line.
|
||||
<span className="font-semibold text-text">Immediate</span> — fires once per matching line. Example query:{' '}
|
||||
<Code>{'regex("panic")'}</Code>.
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-semibold text-text">Threshold</span> — fires when a query's match count within a
|
||||
rolling window crosses a numeric threshold.
|
||||
rolling window crosses a numeric threshold. Example: query <Code>{'regex("error")'}</Code>, op{' '}
|
||||
<Code>>=</Code>, threshold <Code>10</Code>, window <Code>1m</Code>.
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-semibold text-text">NewValue</span> — fires the first time a field regex captures a
|
||||
value not seen before by this rule (seen values live only in memory, so a restart forgets them).
|
||||
value not seen before by this rule. Example: field regex{' '}
|
||||
<Code>{'user_id=(?P<id>\\d+)'}</Code>, capture group <Code>id</Code>. Seen values live only in memory, so
|
||||
a restart forgets them.
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-semibold text-text">Ratio</span> — fires when the ratio of two queries' match
|
||||
counts within a rolling window crosses a numeric ratio.
|
||||
counts within a rolling window crosses a numeric ratio. Example: query A{' '}
|
||||
<Code>{'regex("error")'}</Code>, query B <Code>{'regex("request")'}</Code>, op <Code>>=</Code>, ratio{' '}
|
||||
<Code>0.1</Code>, window <Code>5m</Code>.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Query-based conditions (Immediate/Threshold/Ratio) reuse the filter bar's syntax, but only the subset that
|
||||
evaluates a single line's own fields — <Code>regex(...)</Code>, <Code>sequence</Code>,{' '}
|
||||
<Code>timestamp</Code>, and <Code>and</Code>/<Code>or</Code>. A <Code>metric_name[param]</Code> comparison
|
||||
isn't supported in an alert query.
|
||||
Query-based conditions (Immediate/Threshold/Ratio) reuse the filter bar's syntax, restricted to{' '}
|
||||
<Code>regex(...)</Code>, <Code>sequence</Code>, <Code>timestamp</Code>, and <Code>and</Code>/<Code>or</Code>
|
||||
. A <Code>metric_name[param]</Code> comparison isn't supported in an alert query.
|
||||
</p>
|
||||
<p>
|
||||
A window accepts a plain number of milliseconds or a humantime-style duration like <Code>30s</Code>,{' '}
|
||||
A window accepts a plain number of milliseconds or a humantime-style duration: <Code>30s</Code>,{' '}
|
||||
<Code>5m</Code>, <Code>1h</Code>.
|
||||
</p>
|
||||
<p>
|
||||
Every rule delivers to an <em>ntfy</em> sink: a topic (required) and an optional server override (default{' '}
|
||||
<Code>https://ntfy.sh</Code>). "Send test notification" delivers to the form's current topic/server without
|
||||
needing to save the rule first.
|
||||
Every rule delivers to the single ntfy sink configured in Settings: a topic and an optional server override
|
||||
(default <Code>https://ntfy.sh</Code>). Configure the sink and send a test notification from the Settings
|
||||
panel — the Alerts panel only defines rule conditions.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -275,9 +323,9 @@ const SECTIONS: DocSection[] = [
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The Settings panel controls process-wide state: colour scheme, the max number of logs kept in memory
|
||||
(blank means unlimited), and imported/linked CSS. Changes apply immediately and persist to the settings
|
||||
file on disk — no save button needed.
|
||||
The Settings panel controls process-wide state: colour scheme, the ntfy sink alert rules deliver to, the
|
||||
max number of logs kept in memory (blank means unlimited), and imported/linked CSS. Changes apply
|
||||
immediately and persist to the settings file on disk. There is no save button.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
@@ -288,9 +336,11 @@ const SECTIONS: DocSection[] = [
|
||||
body: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>
|
||||
The Presets panel manages profiles: create, switch, and delete them. Switching activates a profile's
|
||||
params/metrics in the running engine immediately, no restart needed — see <em>General idea</em> above for
|
||||
what a profile stores.
|
||||
The profile dropdown in the sidebar manages profiles. Selecting one switches to it and activates its
|
||||
params/metrics in the running engine immediately, with no restart. The <Code>+</Code> button prompts for a
|
||||
name and creates a new profile as a copy of the active one, then switches to it. The <Code>×</Code> button
|
||||
deletes the active profile; it's disabled for <Code>Default</Code>, which can't be deleted. See{' '}
|
||||
<em>General idea</em> above for what a profile stores.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user