docs/netdata-ai/skills/query-netdata-cloud/query-logs.md
This guide is part of the query-netdata-cloud skill.
Read the SKILL.md prerequisites first.
For the generic Function transport and the canonical protocol
reference, see query-functions.md. The
authoritative protocol spec is
<repo>/src/plugins.d/FUNCTION_UI_REFERENCE.md (specifically the
"Log Explorer Format" section).
Log Functions are the Log Explorer class of Functions
(has_history: true in their info response). They return a
time-windowed skim of a larger log dataset, with facets
(per-field value counts) for drill-down and an optional
histogram (bucketed counts over time) for context.
Four log Functions exist today, each backed by a different log source. Their request and response shapes follow the same standard envelope, but the field set differs per source:
| Function | Source | Notes |
|---|---|---|
systemd-journal | systemd journal namespaces (system, user, namespace-specific, remote-forwarded) | Linux nodes |
windows-events | Windows event log channels | Windows nodes |
macos-logs | macOS unified log store through Apple's native OSLog framework | macOS nodes |
otel-logs | OpenTelemetry logs ingested by the agent | Any node with the OTEL log receiver enabled |
Confirm which are registered on a node via the
function-listing endpoint in
query-functions.md. Field names below are
illustrative for systemd-journal; the same Function payload keys
(after, before, last, query, facets, histogram,
__logs_sources, ...) apply to the other log Functions -- only the values
and column names differ per source.
POST /api/v2/nodes/{nodeId}/function?function=systemd-journal
Same shape as any other Function call. The body is the
systemd-journal Function's payload.
Always start with info=true to confirm the current schema -- the
Function's parameter set evolves across agent versions.
TOKEN="YOUR_API_TOKEN"
NODE="YOUR_NODE_UUID"
read -r -d '' PAYLOAD <<'EOF'
{ "info": true }
EOF
curl -sS -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
"https://app.netdata.cloud/api/v2/nodes/$NODE/function?function=systemd-journal" \
-d "$PAYLOAD"
The accepted_params array in the response tells you which keys the
agent currently accepts.
STATUS_FILE_VERSION = 29)| Key | Type | Purpose |
|---|---|---|
info | bool | Discovery only; do not combine with a real query |
after | int | Lower time bound, in seconds (struct field after_s). Positive = absolute Unix seconds; negative = relative seconds from before. NOT ms, NOT µs |
before | int | Upper time bound, in seconds (struct field before_s). Positive = absolute Unix seconds; negative = relative seconds from now (0 = now) |
last | int | Page size (rows). Default 200 |
direction | string | backward (default; newest first) or forward |
anchor | int | Per-row cursor for pagination, in microseconds (matches the row timestamp values, which are µs — a different unit from after/before) |
query | string | Free-text search (FTS) across indexed fields |
facets | string[] | Field names to group by (returns counts per value) |
histogram | string | Field name to bucket-by-time |
selections | object | The only working way to filter by source or by field in the JSON body. {"__logs_sources":[...]} selects sources; {"FIELD":[...]} filters a facet. See Selecting log sources — passing __logs_sources as a top-level key is silently ignored |
if_modified_since | int | Tail mode -- skip if no new data |
data_only | bool | Skip metadata for a faster query |
sampling | int | Cap on rows scanned when search would otherwise be huge |
slice | bool | Native backend filter (faster, less flexible) |
delta | bool | Incremental histogram updates |
tail | bool | Append-mode (combine with if_modified_since) |
info=true returns the current authoritative list; rely on it, not
this table, when in doubt.
info → selections)This is the single most important mechanism for log queries, and it
works identically for every log Function (systemd-journal,
windows-events, otel-logs, and any other built on the shared
libnetdata logs-query library LQS). Only the source id values
differ per Function; the request shape is the same.
The agent parses the source selector from the POST body only inside
the selections object, as an array
(<repo>/src/libnetdata/facets/logs_query_status.h:329-410,
lqs_request_parse_json_payload). There is no top-level
__logs_sources key in the JSON parser, so:
{ "__logs_sources": "Netdata/Daemon" } // ❌ silently IGNORED → queries ALL sources
{ "selections": { "__logs_sources": ["Netdata/Daemon"] } } // ✅ filters server-side
The top-level / function-string form
source:<id>only applies to the on-agent function-string transport. Over the Cloud REST API the body is JSON, so you MUST useselections.__logs_sources(an array).
infoThe info=true response contains a __logs_sources widget. Each
option carries the id you pass, plus its size, entry count, and time
coverage (retention) — invaluable for picking the right source and
knowing how far back data exists:
curl ... -d '{"info":true}' \
| jq -r '.. | objects | select(.id=="__logs_sources") | .options[]
| "\(.id)\t\(.info)"'
# windows-events example output:
# All-Of-Netdata 4 channels, 3.08MiB, covering 3d 19h, 1.94K entries, last 2026-06-26T04:30:44Z
# Netdata/Daemon 1 channel, 1028KiB, covering 3d 19h, 789 entries, last 2026-06-26T04:28:37Z
# Netdata/Health ...
# systemd-journal ids look different, e.g. all, all-local-system-logs,
# all-local-namespaces, <namespace-name> — always read them from info.
__logs_sources is a multiselect: pass one or more ids in the
array; selecting any source resets the default (which is "all
sources") so you get exactly the union you asked for
(logs_query_status.h:407-410).
read -r -d '' PAYLOAD <<'EOF'
{
"after": -3600, "before": 0, "last": 200, "data_only": true,
"selections": { "__logs_sources": ["Netdata/Daemon"] }
}
EOF
curl -sS -X POST -H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
"https://app.netdata.cloud/api/v2/nodes/$NODE/function?function=windows-events" \
-d "$PAYLOAD"
Microsoft-Windows-SystemDataArchiver/SRUM emits ~10 events/sec),
the result is dominated by noise — a 500-row page can span under a
minute, and a wide unfiltered window can time out or make the
plugin exit before responding.data_only:true to skip facet-count and histogram
aggregation. That aggregation is the usual cause of timeouts on wide
windows; data_only makes otherwise-failing historical queries
succeed.selections + data_only:true + a bounded last. Then a
single query can cover a long window cheaply.The response uses the standard Function envelope (top-level
keys status, v, type, help, accepted_params,
required_params, has_history, update_every, data, ...).
For log Functions, type is the source name (logs family
discriminator). Verified live against the agent-events node:
| Top-level key | Description |
|---|---|
status | HTTP-style status integer (200 on success) |
v | Function schema version |
type | Family discriminator (carries logs-family value) |
help / accepted_params / required_params | Discovery metadata (see query-functions.md) |
data | Array of row arrays -- this is the result rows |
columns | Object keyed by column name; per-column metadata: index (position in each row of data), name (display label), type (string / timestamp / integer / ...), visible, unique_key, sort, summary (count / min / max / sum / ...), filter (e.g. range), visualization, value_options (for transforms like datetime_usec) |
facets | Array of facet records: {id, name, options[]} where each option is {id, name, count}. Use to drill down by field value. |
histogram | If requested: time-bucketed counts. Object with chart, id, name, plus per-bucket data |
pagination | Cursor info (anchor, direction, last, ...) for the next page |
default_charts | Suggested chart configuration |
default_sort_column | Recommended sort column |
available_histograms | Field names that the agent can histogram-bucket |
_request | Echo of the parsed request (defaults applied) |
versions | Source/version map for cache invalidation |
last_modified | Last-data timestamp |
expires | Suggested cache expiry |
partial | True if the result was capped by sampling or timeout |
message | Optional info / warning string |
_journal_files / _fstat_caching / _sampling / _stats | systemd-journal-specific debug counters |
data is an array of rows. Each row is itself an array whose
positions match columns.<key>.index. To pretty-print a single
row by column name:
jq '.columns as $c
| .data[0] as $row
| $c | to_entries
| sort_by(.value.index)
| map({(.key): $row[.value.index]})
| add' response.json
The selections POST-payload key is a structured field-filter
mechanism the Netdata systemd-journal Function (powered by the
libnetdata facets engine) supports. It is distinct from raw
journalctl's KEY=value matches: a single field can carry
multiple allowed values, and multiple fields are AND'd.
selections is an object whose keys are journal field names
and whose values are arrays of allowed values:
{
"selections": {
"FIELD1": ["A", "B", "C"],
"FIELD2": ["D", "E"]
}
}
Semantics (verified at
<repo>/src/libnetdata/facets/logs_query_status.h:386-466):
So the example above is logically:
(FIELD1 in A, B, C) AND (FIELD2 in D, E)
A namespace can hold tens of thousands to hundreds of thousands
of records per day. A bare query (FTS) scans every record's
indexed text fields. Structured selections matches use the
facet engine's per-field index, which is dramatically faster
once the time window is fixed.
Rule of thumb: narrow with selections first, then refine
with query (FTS) only as a residual narrower over the
already-sliced subset.
selections__logs_sources (per LQS_PARAMETER_SOURCE,
logs_query_status.h:407) is the source-type filter — pass the
source ids from info as an array. This is the only way to
scope sources in the JSON body; a top-level __logs_sources key is
not parsed. See Selecting log sources.query inside selections is ignored
(logs_query_status.h:398); use the top-level query.{
"after": -86400,
"before": 0,
"last": 500,
"selections": {
"__logs_sources": ["agent-events"],
"AE_AGENT_HEALTH": ["crash-first", "crash-loop", "crash-repeated", "crash-entered"],
"AE_AGENT_VERSION": ["v2.10.0", "v2.10.0-135-nightly"]
},
"query": "deadlock"
}
This selects the cross-product of crash-class records on those
two versions (index-resolved), then FTS-filters the result for
the substring deadlock. Index-friendly even on a
~200k-records-per-day namespace.
{
"after": -604800,
"before": 0,
"selections": { "__logs_sources": ["agent-events"] },
"query": "SIGSEGV"
}
A 7-day FTS over the entire namespace with no structured
narrowing. Slow and costly on large namespaces. Always pair FTS
with at least one structured selections field.
TOKEN="YOUR_API_TOKEN"
NODE="YOUR_NODE_UUID"
NAMESPACE="systemd" # or "agent-events", "any-namespace-name"
read -r -d '' PAYLOAD <<EOF
{
"after": -3600,
"before": 0,
"last": 50,
"direction": "backward",
"selections": { "__logs_sources": ["${NAMESPACE}"] }
}
EOF
curl -sS -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
"https://app.netdata.cloud/api/v2/nodes/$NODE/function?function=systemd-journal" \
-d "$PAYLOAD"
TOKEN="YOUR_API_TOKEN"
NODE="YOUR_NODE_UUID"
read -r -d '' PAYLOAD <<'EOF'
{
"after": -86400,
"before": 0,
"last": 100,
"query": "OOM",
"histogram": "PRIORITY",
"facets": ["_SYSTEMD_UNIT", "PRIORITY"],
"selections": { "__logs_sources": ["all-local-system-logs"] }
}
EOF
curl -sS -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
"https://app.netdata.cloud/api/v2/nodes/$NODE/function?function=systemd-journal" \
-d "$PAYLOAD"
TOKEN="YOUR_API_TOKEN"
NODE="YOUR_NODE_UUID"
ANCHOR=1700000123456789 # cursor from previous response
read -r -d '' PAYLOAD <<EOF
{
"anchor": ${ANCHOR},
"direction": "forward",
"last": 200,
"selections": { "__logs_sources": ["all-local-logs"] }
}
EOF
curl -sS -X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
"https://app.netdata.cloud/api/v2/nodes/$NODE/function?function=systemd-journal" \
-d "$PAYLOAD"
after/before are in SECONDS (struct fields after_s /
before_s, logs_query_status.h:345-346): positive = absolute Unix
seconds, negative = relative seconds (before:-3600 = "one hour
ago"; before:0 = now). anchor and the row timestamps are in
microseconds — do not reuse a row timestamp as a positive
after/before. Mixing the two units is the most common bug.data_only:true to
skip that aggregation, filter the source via selections, and/or
use sampling — see Selecting log sources.
If a query still "exits before responding", narrow the window further.selections.__logs_sources. With no
source filter the query targets every source, which on a busy host
can be hundreds of GB (Linux) or dominated by a noisy channel
(Windows) — slow, and prone to timeouts.logs). scope:all works;
scope:grafana-plugin does NOT.facets include high-cardinality
fields (MESSAGE_ID, _BOOT_ID, _PID). Pick facets carefully.For systemd-journal, if the host runs journalctl --namespace=<name>,
the same <name> is a valid source id you pass in
selections.__logs_sources. As always, the authoritative list (with
per-source size, entry count, and retention coverage) comes from the
info=true response's __logs_sources widget options[] — see
Selecting log sources for
the exact jq and the request shape.