Skip to main content

Live Tail & Histogram API

Real-time log streaming and time-bucketed event counts. These endpoints power the live tail view and timeline bar chart in the Web UI.

GET /tail

Server-Sent Events (SSE) stream for real-time log tailing with full LynxFlow pipeline support. Uses the same query engine as POST /query, but in streaming mode.

Query Parameters

ParameterRequiredDefaultDescription
qYes--LynxFlow query (streaming stages only)
countNo100Number of historical events to replay during catchup (max 10,000)
fromNo"-1h"Catchup lookback window (relative or ISO 8601)

Two Phases

  1. Catchup -- replays the last count matching events from storage (time range: from to now). Each event is sent as event: result. Phase ends with event: catchup_done.
  2. Live -- streams new events from the EventBus through the LynxFlow pipeline in real time. Events are sent as event: result. Heartbeat sent every 15 seconds as event: heartbeat.

Basic Example

curl -N "localhost:3100/api/v1/tail?q=from+main+level%3Derror&from=-1h&count=100"

SSE event stream:

event: result
data: {"_time":"2026-02-14T14:51:58Z","_raw":"level=ERROR status=502 uri=/api/users","status":502}

event: result
data: {"_time":"2026-02-14T14:52:01Z","_raw":"level=ERROR status=503 uri=/api/orders","status":503}

event: catchup_done
data: {"count":2}

event: result
data: {"_time":"2026-02-14T14:52:15Z","_raw":"level=ERROR status=500 uri=/api/pay","status":500}

event: heartbeat
data: {"ts":"2026-02-14T14:52:30Z"}

With a LynxFlow Pipeline

Apply streaming transformations to the live tail:

# Filter and project fields
curl -N "localhost:3100/api/v1/tail?q=from+main+%22ERROR%22+%7C+where+status+%3E+500+%7C+keep+_time,uri,status&count=50"

# Extract fields with parse regex
curl -N "localhost:3100/api/v1/tail?q=from+main+%22connection+refused%22+%7C+parse+regex+r%22host%3D(%3FP%3Chost%3E%5CS%2B)%22&from=-30m"

Supported LynxFlow Stages

Only streaming (event-by-event) stages are supported in live tail:

SupportedNot Supported
where, extend, parse, keep, drop, rename, headstats, sort, top

Stages that must see all input before producing output (aggregation, sorting, top-k) are rejected with 422:

{
"error": "command \"stats\" is not supported in live tail (it requires all data before producing output)"
}

Event Types

EventWhenData
resultEach matching event (catchup + live)Event JSON object
catchup_doneHistorical replay complete{"count": N}
heartbeatEvery 15s during live phase{"ts": "..."}
errorQuery or pipeline errorError object

JavaScript Example

const params = new URLSearchParams({
q: 'from main level=error | where status > 500',
count: 100,
from: '-1h'
});

const es = new EventSource(`/api/v1/tail?${params}`);

es.addEventListener("result", (e) => {
appendRow(JSON.parse(e.data));
});

es.addEventListener("catchup_done", (e) => {
showLiveIndicator();
});

es.addEventListener("heartbeat", (e) => {
updatePing();
});

es.addEventListener("error", (e) => {
showError(JSON.parse(e.data));
});

Error Responses

StatusCodeDescription
400--Invalid LynxFlow query (parse error, includes suggestion)
422--Query contains stages unsupported in live tail
Why SSE and not WebSocket?

Live tail is a unidirectional server-to-client stream. SSE auto-reconnects, passes through HTTP proxies without special configuration, and works with the native EventSource browser API. WebSocket would add unnecessary complexity for a one-way data flow.


GET /histogram

Time-bucketed event counts for the timeline bar chart. Designed to be fast -- fires on every filter change in the Web UI and runs independently and concurrently with the main query.

Query Parameters

ParameterRequiredDefaultDescription
qNo--LynxFlow filter expression (e.g. level=error)
fromYes--Start time (relative or ISO 8601)
toNo"now"End time
bucketsNo60Target number of buckets. Server picks the best interval (1s, 5s, 1m, 5m, 1h).

Example

curl -s "localhost:3100/api/v1/histogram?q=level%3Derror&from=-1h&buckets=60" | jq .

Response (200):

{
"data": {
"interval": "1m",
"buckets": [
{"time": "2026-02-14T14:00:00Z", "count": 42},
{"time": "2026-02-14T14:01:00Z", "count": 87},
{"time": "2026-02-14T14:02:00Z", "count": 156}
],
"total": 8432
},
"meta": {
"took_ms": 12
}
}

Response Fields

FieldTypeDescription
data.intervalstringBucket interval chosen by server (e.g., "1m", "5m", "1h")
data.bucketsarrayArray of {time, count} objects
data.buckets[].timestringISO 8601 bucket start time
data.buckets[].countintegerNumber of matching events in this bucket
data.totalintegerTotal matching events across all buckets
meta.took_msnumberQuery execution time in milliseconds

Interval Selection

The server automatically selects the best interval based on the time range and target buckets count:

Time RangeTypical Interval
< 5 minutes1s or 5s
5 minutes -- 1 hour1m
1 -- 6 hours5m
6 -- 24 hours15m
1 -- 7 days1h
> 7 days6h or 1d

Without a Filter

Omit q to get a histogram of all events:

curl -s "localhost:3100/api/v1/histogram?from=-6h&buckets=72" | jq .