LogQL Limitations

Kloudfuse supports LogQL for compatibility with Grafana Loki tooling, but the language carries structural limitations — particularly around string and regular-expression searching — that FuseQL was designed to avoid. This page describes each limitation, shows the LogQL behavior, and a comparable FuseQL query.

Summary

Search need LogQL FuseQL

Whole-word (token) search

Not expressible; substring or regex scan only

'error' — indexed term search

Case-insensitive search

(?i) regex scan

Term search is case-insensitive by design

Wildcard match

Full regex required

"err*"grep wildcards

Substring / prefix / suffix on a field

Regex with escaping

**, *~, ~* operators

Search a structured field

Parse at query time, then label filter

Indexed facet comparison, no parser stage

Search without selecting streams

Not allowed; requires a stream selector

Bare 'term' or * queries

LogQL’s only body-search primitives are the substring and regex line filters, which scan the log text character by character. There is no way to ask for a whole word. Searching for error also matches errored, errors, and preerror:

{source="payment"} |= "error"

The only whole-word workaround is a regex with word boundaries — which is still a scan, only slower:

{source="payment"} |~ "\\berror\\b"

FuseQL solution. Term search queries the Lucene inverted index that Kloudfuse builds from every log line at ingest. It matches whole tokens only, and because it is an index lookup rather than a scan, it is also the fastest way to search the log body:

'error'

This matches error, Error, and error_count (punctuation creates a token boundary) but not errored or errors. Multiple words — 'connection refused' — require all tokens in the line, in any order, still from the index. See FuseQL text search operators.

Case-sensitive matching only

The LogQL |= filter is strictly case-sensitive: |= "Timeout" misses timeout and TIMEOUT. The workaround is a case-insensitive regex, which abandons the cheaper substring path for a regex scan:

{source="payment"} |~ "(?i)timeout"

FuseQL solution. Term search is case-insensitive by design — the index lower-cases tokens at ingest, so one query covers every capitalization at index speed:

'timeout'

When you do want exact-case matching, FuseQL grep ("Timeout") remains case-sensitive, so both behaviors are available without using a regex expression.

No wildcard syntax

LogQL line filters have no wildcard support: matching err, error, or errored by prefix requires a full regular expression (|~ "err[a-z]*"), with regex escaping and regex cost.

FuseQL solution. Grep supports * (any characters) and ? (any single character) directly:

"err*"

Internally, FuseQL optimizes wildcard and regex greps by extracting a literal prefix, pre-filtering candidate lines through the Lucene text index, and then runs the regex check per row — an optimization LogQL line filters cannot express.

Regex engine and escaping constraints

LogQL regular expressions run on RE2, which has no look-ahead, look-behind, or back-references. Two further pitfalls make LogQL regex error-prone:

  • Inside double-quoted strings every backslash must be doubled: a digit class is \\d, a literal bracket is \\[.

  • Label matchers (=~, !~) are fully anchored{source=~"ngin"} matches nothing, because the expression must match the entire label value.

{source=~"nginx.*"} |~ "\"(GET|POST) /api/v[0-9]+/"

FuseQL solution. Most searches that force regex in LogQL have purpose-built FuseQL operators that need no escaping at all: ** (contains), *~ (starts with), ~* (ends with) for label and facet values, and grep wildcards for the log body:

url*~"/api/" and 'post'

When a real regular expression is required, the =~ filter and the matches predicate accept RE2 patterns, and the remaining cases are far fewer.

LogQL cannot filter on a field inside the message body until a parser stage (json, logfmt, regexp, pattern) has extracted it — and that parsing happens on every line, on every query, at read time:

{source="payment"} | json | status_code="503"

Lines that fail to parse pass through with the __error__ label and need extra handling. Checking that a field merely exists is similarly indirect (| json | status_code != "").

FuseQL solution. Kloudfuse extracts facets at ingest and indexes them, so field conditions are direct, pre-pipe, and served from the inverted index — no parser stage, no per-query parsing cost:

@status_code=503

Field presence is a first-class operator: @status_code alone (or key exists in regular search) matches any line that carries the facet. See FuseQL comparison operators.

A stream selector is mandatory

Every LogQL query must start with at least one non-empty label matcher. There is no way to search everything for a string; the closest form selects all streams first, then scans them:

{source=~".+"} |= "OutOfMemoryError"

On a busy cluster that selector reads every log line in the time range before the filter runs.

FuseQL solution. A bare search is a valid query, and because term search resolves through the index, searching everything does not mean scanning everything:

'OutOfMemoryError'

Performance of unanchored scans

Because every LogQL line filter and parser is a scan over the selected streams, broad regex or pattern searches over high-volume sources are expensive. A single unanchored regex can consume significant query capacity. To Mitigate problems with LogQL narrow stream selectors used, shortening the time range, and putting the most selective |= filters first.

FuseQL solution. Use index-backed operators changes will change the costs of the query rather than trimming it: term search ('…') and facet comparisons (@facet=value) resolve against the inverted index without touching raw log bodies. This will drastically increase performance.

Wildcard greps pre-filter the index before any per-row work. Use FuseQL term and facet searches whenever possible. Reserve the use of raw substring or regex scans for the final step.