| name | logsql |
| description | VictoriaLogs LogsQL query language: filters (word, phrase, prefix, regexp, range, IPv4 range, exact, stream, time), pipe operators (stats, sort, fields, format, extract, unpack_json, unpack_logfmt, math, limit, top, uniq), stats functions, log stream filtering with Prometheus-style label selectors, field extraction, and performance patterns. Invoke whenever task involves any interaction with LogsQL — writing, debugging, optimizing, or reviewing VictoriaLogs queries. |
LogsQL
VictoriaLogs query language. Three stages: filters select logs, pipes transform, stats aggregate. Master the
shape; rest is detail.
References
- Filters — [
${CLAUDE_SKILL_DIR}/references/filters.md] All filter types (word, phrase, prefix, substring, regexp,
range, IPv4/IPv6/string range, length range, exact, multi-exact, contains_all/any, subquery, case-insensitive,
sequence, value_type, field comparison); _time, _stream, _stream_id; AND/OR/NOT precedence.
- Pipes — [
${CLAUDE_SKILL_DIR}/references/pipes.md] All pipe operators: field manipulation, sorting/limiting,
transformation, structured parsing, exploration, stream context, joining.
- Stats — [
${CLAUDE_SKILL_DIR}/references/stats.md] Stats pipe shape, grouping (fields, time buckets,
timezone-aware, field buckets, IPv4 buckets, additional filters); all stats functions; running_stats/total_stats;
math pipe.
- Patterns — [
${CLAUDE_SKILL_DIR}/references/patterns.md] Recipes (top-N, error counting, latency percentiles,
field extraction, rate over time), performance tips, troubleshooting workflows.
Read the relevant reference before proceeding.
The Three-Stage Shape
<filters> | <pipe1> | <pipe2> | ... | <pipeN>
- Filters select logs. At least one required. Run first; downstream sees only matched logs.
- Pipes transform. Each reads previous-stage output, emits new rows. Reshape (
fields, rename), parse
(unpack_json, extract), aggregate (stats), limit (limit, sort).
- No final stage marker. Last pipe's output is the response.
Simplest valid query — one word, searched in _msg:
error
Returns every log containing error, in arbitrary order, across all time.
The _time Filter — Almost Always Required
Without _time, the query scans all stored logs.
Common forms:
_time:5m — last 5 minutes.
_time:1h — last hour.
_time:1d — last day.
_time:2.5d15m42.345s — composite duration.
_time:>=2024-01-01 — since.
_time:<=2024-01-01 — before.
_time:[2024-01-01, 2024-01-02) — closed-open range.
_time:2024-03-15T10:00:00+02:00 — exact RFC3339 with timezone.
Grafana plugin and web UI inject the dashboard time range. Don't add _time: in dashboard queries — disables time
picker. Do add it for ad-hoc API queries.
Stream Filter — The Performance Lever
Logs belong to streams — Prometheus-style label sets identifying the source. Use {...} braces with PromQL selector
syntax:
{app="nginx"}
{app="nginx", instance=~"web-.*"}
{app in ("nginx", "envoy")}
{app not_in ("buggy", "deprecated")}
Optional _stream: prefix: _stream:{app="nginx"}.
Performance: most aggressive pruning available. Restricts which on-disk blocks need reading. A precise stream filter
_time range may read 0.1% of what free-text alone would.
Combine _time + {stream} at the top of every production query.
Filter Types
Full catalog: [${CLAUDE_SKILL_DIR}/references/filters.md].
Word Filter
error
log.level:error
Bare word matches _msg. field:word matches named field. Case-sensitive by default; log.level:i(error) for
case-insensitive.
Phrase Filter
"error: cannot open file"
"event.original":"cannot open file"
Quoted phrases match literally, whitespace and punctuation included.
Prefix Filter
error*
log.level:error*
"warn"*
* matches anything starting with the prefix.
Exact / Multi-Exact
log.level:="error"
log.level:in("error", "fatal", "panic")
= requires whole-field equality. in(...) matches any of multiple exact values — much faster than regex.
Regexp
~"(?i)error|warn"
log.level:~"^err"
log.level:!~"^debug"
RE2 syntax. !~ negates. Slower than word/exact — use sparingly.
Range Filter
duration_ms:>1000
duration_ms:[100, 1000]
duration_ms:>=500
Numeric. Supports >, >=, <, <=, and [low, high] / (low, high) intervals.
IPv4 Range
client_ip:ipv4_range("10.0.0.0", "10.255.255.255")
client_ip:ipv4_range("192.168.0.0/16")
CIDR or low/high. IPv6: ipv6_range.
Length Range
_msg:len_range(100, 1000)
Byte length of field value. For suspiciously short or long lines.
Logical Composition
error AND _time:5m AND {app="nginx"}
error _time:5m {app="nginx"} # AND can be omitted
error AND NOT buggy_app
error -buggy_app # - is shorthand for NOT
(error OR warn) AND _time:1h
error -(buggy_app OR foobar)
Precedence: NOT > AND > OR. Parenthesize when in doubt — error -buggy_app OR foobar parses as
(error AND NOT buggy_app) OR foobar.
The * Wildcard for Fields
Multi-field search by name prefix:
*:error # any field containing 'error' word
kubernetes.*:nginx # any field starting with 'kubernetes.' containing 'nginx'
Slows queries — scans more field data. Prefer explicit field names.
Pipe Composition
Pipes chain with |.
fields — Select Output Fields
error _time:5m | fields _time, _stream, _msg
Reduces to named subset — faster transit, easier reading.
sort — Order Results
error _time:5m | sort by (_time)
error _time:5m | sort by (_time desc)
Unordered by default. Expensive — only sort small sets (< few million).
limit / first / last
error _time:5m | sort by (_time) desc | limit 10
error _time:5m | first 10 by (_time desc)
error _time:5m | last 10 by (_time)
limit truncates after sort. first/last combine sort+limit. Aliases: head = limit, skip = offset.
stats — Aggregate
_time:5m | stats count() as total_errors
_time:5m | stats by (_stream) count() as errors_per_stream
_time:5m | stats by (_time:1m) count() as per_minute_errors
stats and as keywords are optional: _time:5m | count() total_errors works.
Most powerful pipe. Grouping by fields, time buckets, field buckets, IPv4 buckets, per-row filters. Full mechanics:
[${CLAUDE_SKILL_DIR}/references/stats.md].
unpack_json / unpack_logfmt — Parse Structured Logs
{app="my-app"} | unpack_json from _msg
{app="my-app"} | unpack_logfmt from _msg
{app="my-app"} | unpack_json from _msg | stats by (level) count()
Parses field as JSON/logfmt, flattens into top-level fields. Downstream pipes filter and group by extracted fields.
extract / extract_regexp — Pattern-Based Field Extraction
_msg:"GET" | extract "GET <path> HTTP" from _msg
_msg:"client" | extract_regexp "client (?P<client_ip>[0-9.]+)" from _msg
extract uses <field_name> placeholders. extract_regexp uses RE2 named groups.
math / eval — Numeric Calculations
| math duration_ms / 1000 as duration_sec
| eval (errors / total) * 100 as error_percent
Arithmetic, bitwise, defaults, max/min, abs/ceil/floor/round/exp/ln on numeric field values.
top — N Largest Groups
_time:5m | top 10 (_stream)
_time:5m | top 10 (host, path)
Shortcut for stats by (group) count() | sort desc | limit N.
uniq — Distinct Rows
_time:5m | uniq by (host, path)
Like SQL SELECT DISTINCT. One row per unique field combination.
Common Query Recipes
# Recent error logs from one service
_time:5m {app="api"} error | sort by (_time) desc | limit 100
# Error count per service over last hour
_time:1h error | stats by (_stream) count() as errors | sort by (errors) desc
# Per-minute error rate
_time:1h error | stats by (_time:1m) count() as errors_per_min
# Top 10 noisiest streams
_time:5m | stats by (_stream) count() as logs | sort by (logs) desc | limit 10
# Equivalent:
_time:5m | top 10 (_stream)
# Latency percentiles per endpoint
_time:1h {app="api"} | unpack_json from _msg
| stats by (path) quantile(0.5, duration_ms) p50, quantile(0.95, duration_ms) p95
# Errors from JSON logs, parsed and filtered
{app="api"} _time:5m | unpack_json from _msg
| filter level:error
| stats by (component) count()
Stats Functions — Quick Reference
Most common; full catalog: [${CLAUDE_SKILL_DIR}/references/stats.md].
count() — row count.
count(field) — rows with non-empty field.
count_empty(field) — rows with empty/missing field.
count_uniq(field, ...) — distinct combinations. Memory scales with cardinality.
count_uniq_hash(field, ...) — cheaper approximate distinct via hashing.
sum, avg, min, max, median of field.
quantile(phi, field) — phi in [0..1].
uniq_values(field) — distinct values as JSON array.
values(field) — all values including duplicates.
field_min(target, sort) / field_max(target, sort) — target value for smallest/largest sort row.
row_min(sort) / row_max(sort) — full row at min/max.
row_any() — arbitrary row per group.
rate() — per-second rate of matching rows.
histogram(field) — histogram of numeric values.
stddev(field).
Grouping in Stats
By Fields
| stats by (host, path) count() as logs
by keyword optional.
By Time Buckets
| stats by (_time:1m) count() as per_minute
| stats by (_time:5m, host) count() as per_5m_per_host
Bucket = any duration. Named buckets: nanosecond, microsecond, millisecond, second, minute, hour, day,
week, month, year (month/year account for variable lengths).
Time Buckets with Timezone
| stats by (_time:1d offset 'America/New_York') count() as per_day_ny
Aligned to the named timezone, not UTC. Needed for daily/weekly stats in non-UTC offices.
By Field Buckets
| stats by (duration_ms:100) count() as histogram
Buckets numeric values into ranges. duration_ms:100 → [0,100), [100,200), etc.
With Additional Filters
| stats
count() as total,
count() if (level:error) as errors,
count() if (level:warn) as warns
Per-aggregate filtering. One pass beats N separate queries.
Performance Tips
- Always include
_time. Otherwise every block is candidate for scan.
- Use
{...} stream filters when possible. Block-level pruning — orders of magnitude faster than field-value.
- Exact over regex.
log.level:="error" is much faster than log.level:~"^err".
- Filter before aggregating. Push filters left.
- Avoid sorting > 10M rows. Combine with
limit or use first N by (...).
count_uniq_hash over count_uniq for high cardinality when ±1% is acceptable.
fields early to drop unused columns.
- Stream filter at top level. Multiple stream filters work but are less optimized.
Query Options
_time:5m error | options concurrency=4, parallel_readers=8
concurrency=N — CPU cores.
parallel_readers=N — parallel block readers.
ignore_global_time_filter — bypass dashboard time (mostly absolute-time alert rules).
allow_partial_response — partial results on timeout instead of erroring.
time_offset=duration — shift query time perception.
global_filter — applies a filter to every subquery.
Application
When writing:
- Start with
_time + {stream} + content filter. Avoid free-text-only in production.
unpack_json once near top; downstream references are cheap.
- Group by
_stream for per-service rollups.
- Parenthesize OR inside negation:
error -(a OR b), not error -a OR b.
When debugging:
- Drop pipes, run filter alone, sample 10 results.
- Add pipes one at a time with
| limit 10 to keep intermediate small.
- For unexpected stats:
| limit 5 before stats to inspect input.
- Use
field_names and field_values to enumerate what's available.
When reviewing:
- Flag missing
_time in production dashboards.
- Flag bare regex without exact pre-filter.
- Flag
sort without preceding limit or stream filter.
Integration
metricsql and promql — Prometheus metric queries. Cross-correlate at the stream level: log streams use the same
label selectors.
observability — general logging discipline. This skill — LogsQL syntax and performance.