FuseQL examples and use cases
This page combines a syntax-pattern cheatsheet with worked use-case scenarios. The first sections (Filtering, Aggregations, Pipeline operators) provide short, self-contained snippets built against the sample log lines below. For full operator detail, see the dedicated operator pages — text search, comparison, aggregation, parse, string.
Sample log lines
The examples below are written against these five representative log lines. They cover the most common log shapes you encounter in practice — labeled key=value, structured JSON, free-form text, and delimited records.
- nginx access log
-
A typical web server access log written by nginx or similar reverse proxies. The fields
path,status, andduration_msare key=value facets embedded in the log body, distinct from thesourcelabel applied at ingestion.source="nginx" path=/api/orders status=503 duration_ms=842none - Java app log
-
A structured application log.
levelandmsgare common label/facet names.trace_idlinks this log line to a distributed trace — useful for correlation across services.source="orders-service" level="ERROR" msg="Connection refused" trace_id=abc123none - Plain text key=value record
-
An unstructured log line where fields are embedded inline in
key=valueformat. These fields are not indexed at ingestion — you must extract them with aparseoperator at query time before you can aggregate or filter on them.source="payments" user_id=u123 amount=42.50 latency_ms=420none - JSON-structured log
-
A log line whose body is a pure JSON object.
sourceis a label attached by the log shipper at ingestion — it is not part of the log body.level,user.id, andhttp.statusare fields inside the JSON body. Use| json "path"with dot-notation to extract nested fields.{"level":"warn","user":{"id":"u123"},"http":{"status":500,"path":"/cart/checkout"}}noneFilter on the source label and then extract body fields:
source="checkout" | json "http.status" as status | where status >= 500fuseql - Delimited record (used by
splitexamples) -
A comma-separated audit record with no field names. The raw log body is the CSV —
sourceis a label attached at ingestion and is not included in__kf_msg(the raw message body). Columns are zero-indexed, so column 0 is the timestamp.2026-06-06T11:23Z,login,u123,success,10.0.0.7noneFilter by the source label, then split the CSV body into named columns:
source="audit" | split __kf_msg extract 0 as ts, 1 as event, 2 as user, 3 as outcome, 4 as ipfuseql
Filtering
Search filters appear before the first | in a FuseQL query. They narrow the set of log lines the pipeline operates on and are evaluated against the index — they do not scan raw log bodies. Filtering early is the single most effective way to keep queries fast.
Two types of fields are filterable before the pipe:
-
Labels — metadata attached to each log line at ingestion time, such as
source,level,kube_namespace. Use barename="value"syntax. -
Facets — structured fields extracted from the log body at ingestion and indexed separately. Use
@namesyntax.
Fields that are only present in the raw log body text (like user_id in the key=value line) cannot be filtered before the pipe — extract them with parse first, then use | where to filter.
Label equality and inequality
Labels are the most efficient filter. They are indexed at ingestion, so equality checks against them incur near-zero scan cost regardless of volume. Always start with a label filter (source=, level=, kube_namespace=) to scope the query before adding any pipe stages.
Match a source exactly — restricts all downstream pipe stages to nginx lines only:
source="nginx"
Exclude a label value. Returns every line in the query window except those from the orders-service:
source!="orders-service"
Combine multiple label filters on the same line — both conditions must be true (implicit and):
source="nginx" level="ERROR"
Facet equality, range, and regex
Facets are structured fields extracted from the log body at ingestion. They support equality, numeric comparison, and regex. Use @ prefix to reference a facet.
Exact match on a string facet — find all activity for a specific user:
@user_id="u123"
Numeric comparison — find slow requests or error responses. Useful for SLO burn-rate queries where you want only the lines that breach a threshold:
@status>=500
@duration_ms>1000
Regex on a facet — matches any user_id whose entire value is u followed by one or more digits (for example, u42 or u1000). Patterns are automatically anchored, so u[0-9]+ will not match u123-admin. Use this when values follow a known pattern but you don’t know the exact string. Regex matching is more expensive than equality; prefer equality or range filters where possible:
@user_id=~"u[0-9]+"
Substring, prefix, and suffix
These operators match against field values using positional string checks. They are most useful when you know part of a value but not the full string — for example, filtering API paths by prefix or identifying error classes by suffix.
Substring contains (**) — matches any log line where the label kube_deployment contains the substring. The match is case-sensitive. Scoping the search to a label makes it more precise than a free-text search, because the match applies only to that field’s value rather than the full log line:
kube_deployment**"ingress-nginx"
Starts-with (*~) — restricts to paths under /api. The match is case-sensitive. Using a facet (@path) makes this more precise than a free-text search, because the match is scoped to a single field rather than the full log line:
@path*~"/api"
Ends-with (~*) — matches any path ending with /checkout regardless of the base URL. The match is case-sensitive. Using a facet (@path) scopes the match to that field rather than the full log line:
@path~*"/checkout"
Free-text terms and grep
FuseQL has two free-text search operators that both scan the full log line — labels and raw message body — but work differently:
-
Term search (single quotes
'…') looks up complete tokens in an inverted index. It is fast and case-insensitive. Tokens are delimited by whitespace and punctuation, so'error'matcheserroranderror_count(underscore splits the token) but noterrored(no boundary aftererror). -
Grep (double quotes
"…") scans the reconstructed log body character-by-character. It is slower, case-sensitive, and matches any character sequence regardless of boundaries —"error"matcheserrored,errors, anderror_count. Use grep when you need an exact phrase, a partial-word match, or a wildcard pattern.
Term existence — matches any log line where error appears as a complete token. Case-insensitive. Matches error and error_count (punctuation boundary), but not errored (no boundary between error and ed):
'error'
Negated term — excludes any line containing the word healthcheck. Use this to suppress high-volume polling traffic that would otherwise dominate your results:
!'healthcheck'
Grep — case-sensitive substring match against the raw log body. Matches the exact character sequence Connection refused, including the capital C. Unlike term search, this would also match ConnectionRefused or Connection refused: if those characters appear in sequence:
"Connection refused"
Boolean combinators
Use and, or, and parentheses to express compound conditions. The implicit join between adjacent conditions is and, but explicit and is required when mixing it with or to avoid ambiguity.
Scope to nginx lines AND (high-status OR slow response). Without the parentheses, or would bind only to the two adjacent terms:
source="nginx" and (@status>=500 or @duration_ms>=1000)
Match any of several sources. Equivalent to SQL WHERE source IN ('checkout', 'payments'):
source="checkout" or source="payments"
Facet existence
Test whether a facet is present. In a log-search query (no aggregation), use the bare @facet form — it matches any line where the facet key exists, regardless of value:
@trace_id
When combining with aggregation operators, use != "" to filter for lines where the facet is present and non-empty:
@trace_id != "" | count by source
trace_id is present only on lines that are part of a traced request. Using this filter before an aggregation scopes the count to only traced activity.
Aggregations
Aggregation operators collapse many log lines into a summary table. They always follow a pipe (|) and are typically the last stage in a query. The by clause breaks results into groups — omitting it returns a single row for all matched lines.
timeslice is not itself an aggregation operator, but it is the standard way to bucket results by time. A timeslice stage adds a _timeslice column that downstream aggregations can group on. Choose the bucket size based on your query window: 1m buckets for a 1-hour window, 5m for 6 hours, 1h for 24+ hours.
Count and distinct count
count answers "how many log lines matched?" — it counts rows, not unique values. Use it to measure event volume, request rates, or error frequency.
Total log volume bucketed by minute — the foundation of any traffic dashboard:
* | timeslice 1m | count by _timeslice
count_unique answers "how many distinct values?" — equivalent to COUNT(DISTINCT field) in SQL. Use it for cardinality questions: how many unique users, sessions, trace IDs, or error fingerprints occurred in a window.
Distinct active users over the time range. A sudden drop may indicate an authentication problem; a sudden spike may indicate a bot:
* | count_unique(@user_id)
Numeric aggregations
avg, sum, min, and max all operate on numeric fields. They ignore null values, so lines where the field is absent do not affect the result. After using parse to extract a field from the log body, FuseQL automatically coerces the string value to a number.
Average request latency per endpoint per minute. Grouping by both _timeslice and @path reveals whether a latency spike is global or isolated to a specific route:
source="nginx" | timeslice 1m | avg(@duration_ms) by _timeslice, @path
sum is most useful for financial totals or byte counts, where you want the running total rather than the average. min and max show the range — a very large gap between min and max often indicates an outlier worth investigating with p99:
source="payments" | timeslice 5m | sum(@amount) by _timeslice, @currency
source="payments" | timeslice 5m | min(@latency_ms) by _timeslice
source="payments" | timeslice 5m | max(@latency_ms) by _timeslice
Percentiles
avg hides the distribution — a small number of very slow requests can shift the mean while the majority of users see normal performance. Percentiles expose the tail. Use p50 (the median) as a robust measure of typical behavior, p95 or p99 for SLO thresholds, and compare p50 against p99 to gauge how heavy the tail is.
p95 of request latency per endpoint per minute. If p95 is 10x higher than p50, you have a long-tail distribution:
source="nginx" | timeslice 1m | p95(@duration_ms) by _timeslice, @path
Multiple percentiles in one query — shows the full distribution shape without running separate queries. Note that the aliases must differ from the operator names (p50, p95, p99 clash with the operator names themselves):
source="nginx" | timeslice 5m | p50(@duration_ms) as latency_p50, p95(@duration_ms) as latency_p95, p99(@duration_ms) as latency_p99 by _timeslice
Multiple aggregations in one query
Comma-separate aggregation operators in a single pipe stage to compute several metrics at once. The result table has one column per operator, and each column appears as a selectable option in the chart’s Aggregate drop-down. This avoids running two separate queries for two related metrics.
Request count alongside the peak response size — useful for capacity planning where you need to know both how many requests came in and how large they were:
* | timeslice 30s |
count as number,
max(@_number_0) as maximum
by (_timeslice)
Grouping by multiple dimensions
Adding more fields to by slices the result further. Each unique combination of values produces its own row. Be mindful of cardinality — grouping by a high-cardinality field like user_id or trace_id on a large dataset produces millions of rows and is slow. Group by low-cardinality fields like source, level, kube_namespace, or status.
Error count per service per minute — shows which service started erroring first and how error volume compares across the fleet:
level="ERROR" | timeslice 1m | count by _timeslice, source
Pipeline operators
Pipeline operators transform or enrich each log line as it passes through the query. They appear after the | symbol. The most common pattern is: filter with a search expression → extract fields with parse → filter further with where → aggregate.
parse regex
Use parse regex when the field you want to extract has no consistent surrounding delimiter — for example, when the value could be followed by a space, a comma, or end-of-string. Named capture groups ((?P<name>…)) become new columns. Regex parsing is the most flexible option but also the most expensive — for simple delimited fields, parse anchor is faster. See parse regex for full syntax and options.
|
|
Extract user_id from the key=value line, where the value ends at a space or end-of-line:
* | parse regex "user_id=(?P<user_id>[a-zA-Z0-9]+)"
Given the key=value log line:
source="payments" user_id=u123 amount=42.50 latency_ms=420
The parse stage extracts:
| Field | Value |
|---|---|
|
|
Extract multiple fields in one regex from an nginx access log:
source="nginx" | parse regex "(?P<method>GET|POST|DELETE) (?P<path>[^ ]+) HTTP/(?P<version>[0-9.]+)"
Given an nginx access log line such as:
203.0.113.42 - - [30/Jun/2026:12:04:11 +0000] "GET /api/orders HTTP/1.1" 200 1482
The parse stage extracts:
| Field | Value |
|---|---|
|
|
|
|
|
|
parse anchor
Use parse anchor (parse "prefix*suffix" as field) when the field value is bounded by predictable surrounding text. The * wildcard captures everything between the prefix and suffix strings. It is faster than regex and easier to read for simple cases. See parse anchor for full syntax and options.
Extract user_id from the key=value line using the surrounding text as delimiters. The trailing space after user_id= is the end anchor:
* | parse "user_id=* " as user_id
Extract multiple fields with one wildcard pattern — useful for fixed-format log lines like nginx access logs:
source="nginx" | parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
Given an nginx access log line such as:
203.0.113.42 - - [30/Jun/2026:12:04:11 +0000] "GET /api/orders HTTP/1.1" 200 1482 "-" "Mozilla/5.0"
The parse stage extracts:
| Field | Value |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
json extraction
Use | json "path" when log lines have a JSON body. The path uses dot notation to navigate nested objects and bracket notation for arrays. JSON extraction is the right choice for structured logging output from frameworks like Logback, Zap, or structlog.
Given the JSON log body:
{"level":"warn","user":{"id":"u123"},"http":{"status":500,"path":"/cart/checkout"}}
Extract a single nested field:
source="checkout" | json "user.id" as user_id
| Field | Value |
|---|---|
|
|
Extract multiple fields in one json stage — field paths and aliases are matched by position:
source="checkout" | json "user.id", "http.status", "http.path" as user_id, status, path
| Field | Value |
|---|---|
|
|
|
|
|
|
After extraction, the fields behave like any other column — filter with where, aggregate with count, avg, etc.:
source="checkout" | json "http.status" as status | where status >= 500 | count by status
split extraction
Use | split when the log body is a positional delimited record with no field names — CSV output, space-separated audit records, or similar. Extract by zero-based column index.
Extract four columns from the CSV audit line — columns are numbered from 0:
source="audit" | split __kf_msg extract 0 as ts, 1 as event, 2 as user, 3 as outcome, 4 as ip
Given the CSV audit log line:
2026-06-06T11:23Z,login,u123,success,10.0.0.7
The split stage extracts:
| Column | Field | Value |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
where post-filter
Use | where to filter on a value that only exists after a parse stage. The search expression before the pipe only has access to indexed labels and facets; where has access to anything computed in the pipeline.
Keep only 5xx responses after extracting the HTTP status from the JSON body:
source="checkout" | json "http.status" as status | where status >= 500
Combine multiple conditions — and and or work the same as in the pre-pipe search expression:
source="nginx" | parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| where (method="GET" or method="POST") and bytes > 10000
| count by method
Typecasting
Fields extracted by parse are strings. Aggregation operators like avg, sum, and p99 require numeric input. Use toInt or toFloat to convert before aggregating. FuseQL also coerces automatically in many cases, but explicit conversion makes the intent clear and avoids silent null results when a value is not parseable.
Parse a status code from the log body and convert it to an integer before aggregating:
* | parse regex "status=(?P<status>[0-9]+)" | toInt(status) as status_int | count by status_int
Convert a duration string (e.g. "842ms") to a numeric millisecond value for comparison or averaging:
* | toDuration("842ms") as latency_ns
Convert to float to preserve decimal precision when computing averages of financial amounts:
source="payments" | toFloat(@amount) as amount_f | avg(amount_f) as avg_amount
Scenarios
These scenarios include the query, what to look for in the output, and specific situations where the analysis is useful.
|
All scenario queries that start with |
Count all logs
Count the total number of log lines over time, bucketed into 30-second intervals. The resulting time series reveals traffic shape at a glance: ramp-up patterns at service start, flat lines during quiet periods, and sharp drops that may indicate a process crash or network partition.
source="my-service" | timeslice 30s | count by (_timeslice)
When to use this query:
- Baselining normal traffic patterns
-
Run this query over a week-long window (with 1h timeslice) to establish what "normal" volume looks like for your system at different times of day. Once you know the baseline, deviations — unexpected spikes or drops — become immediately visible. This baseline also informs alert thresholds so you avoid false positives during expected high-traffic periods.
- Capacity planning and scaling decisions
-
A steadily rising floor on this chart means your overall log (and likely request) volume is growing. Track it weekly to detect organic growth trends and decide when to add capacity before performance degrades. A sudden step-change upward after a deployment often indicates the new version is more verbose or is generating errors.
- Post-deployment validation
-
After a release, run this query scoped to the deployed service (
source="my-service") for the 30 minutes before and after the deployment. A significant change in log volume is an early signal of a regression — increased errors inflate the count, while a crashed process drops it to near zero.
Count all fingerprints
Kloudfuse clusters log lines into fingerprints — templates that capture the structural pattern of a log line with values replaced by placeholders. Two log lines that differ only in their variable values (timestamps, IDs, amounts) share the same fingerprint. Counting distinct fingerprints over time is a measure of structural diversity: how many different kinds of things are happening in your system.
* | timeslice 30s | count_unique(fingerprint) by (_timeslice)
When to use this query:
- Detecting new code paths
-
When a deployment introduces new log statements — new error handlers, new feature flags, new integrations — the fingerprint count rises. A jump that correlates with a release is usually expected. A jump that does not correlate with any deployment is a signal that something unexpected is happening: a misconfiguration, an untested code path being triggered, or external input causing novel behavior.
- Early anomaly detection without alerting rules
-
Because fingerprints capture structure rather than values, a new error type shows up as a new fingerprint even before you’ve written an alert for it. Watching the fingerprint count is a low-effort early warning: if the count is stable, the system is producing the same kinds of logs it always has; if it rises, something new is happening that warrants investigation.
Count all logs grouped by level
Splitting log count by severity level turns a single total into a stack of signals. The INFO band tells you how busy the system is; the WARN band surfaces degraded-but-not-broken conditions; the ERROR band is your most actionable signal. Watching all three simultaneously helps distinguish "the system is handling more traffic" from "the system is failing more often."
* | timeslice 30s | count by (_timeslice, level)
When to use this query:
- Identifying the severity of an incident
-
During an on-call investigation, the first question is whether a traffic spike is load (more INFO logs) or breakage (more ERROR logs). If
INFOvolume is flat butERRORvolume jumps, the system is not handling more requests — it is failing the requests it has. If both spike together, the system is under load and beginning to fail at the edges. This distinction guides the response: scaling vs. rollback. - Catching warning storms before they become errors
-
WARNlogs often precedeERRORlogs by minutes — a connection pool approaching its limit logs warnings before it starts rejecting connections. MonitoringWARNcount separately fromERRORcount lets you catch degradation in progress. Set an alert on sustainedWARNelevation, not just onERRORspikes, to get earlier notice.
Count all fingerprints grouped by source
This query adds a service dimension to fingerprint diversity. Instead of asking "is something new happening?", it asks "which service is the source of the new behavior?" In a microservices environment with dozens of services, this is the difference between seeing that a problem exists and knowing where to look.
* | timeslice 30s | count_unique(fingerprint) by (_timeslice, source)
When to use this query:
- Root-cause localization
-
When the overall fingerprint count spikes, this query reveals which service contributed the new patterns. If a single service accounts for all the new fingerprints, the investigation is immediately scoped. If multiple services spiked simultaneously, look for a shared dependency — a database, a message queue, or a shared library — that all of them depend on.
- Identifying verbosity outliers
-
One service that produces far more distinct fingerprints than others is either doing far more distinct things (expected) or generating a large variety of errors (unexpected). Comparing fingerprint count to total log count for each source reveals the ratio: a service with high total count but low fingerprint count is repetitive (normal operation or a tight loop); a service with low total count but high fingerprint count is encountering many different conditions.
===Average of a duration or number facet
Averaging a numeric facet over time produces a performance trend line. Response times, queue depths, batch durations, and payment amounts are all candidates. The key insight from averaging is trend direction: is performance stable, gradually degrading, or suddenly changing? It does not tell you about distribution shape — combine with percentiles to get the full picture.
* | timeslice 30s | avg(@duration:duration_seconds) by (_timeslice)
When to use this query:
- Tracking gradual latency degradation
-
A sudden spike in average duration is easy to spot. Gradual degradation — average response time rising 10ms per hour — is harder. Running this query with a 24-hour window and a 5-minute timeslice makes the slope visible. Common causes include a growing database table without index maintenance, a memory leak causing increased GC pauses, or a third-party API that is slowly becoming overloaded.
- Correlating latency with traffic volume
-
Run this query alongside the count-all-logs query in two panels on the same dashboard. If average latency tracks with request volume (both rise and fall together), the system is under load and needs more capacity. If latency rises while request volume stays flat or falls, the cause is internal — a downstream dependency, a background job, or a resource contention issue.
Anomaly on count of error logs
Threshold-based alerts require you to know in advance what "too many errors" looks like. Anomaly detection removes that requirement — it learns the historical pattern of error counts (including daily and weekly seasonality) and alerts when the current count deviates from what the model predicts, regardless of what the absolute number is.
core:level="error" | timeslice 120s | count by (_timeslice) | anomaly (_count) by 120s, model=agileRobust, seasonality=hourly, bounds=1, band=3
The chart overlays a gray expected range band on the error count time series. Data points outside the band are anomalies, shown in red. The bounds parameter controls sensitivity: 1 flags moderate deviations, 3 flags only severe ones. The agile-robust model accounts for hourly patterns, making it suitable for services with predictable daily traffic cycles.
When to use this query:
- Detecting incidents without fixed thresholds
-
At 3am, 50 errors per minute might be alarming. At 2pm on a Monday, 50 errors per minute might be completely normal. A static threshold fires false positives in one case and misses real incidents in the other. The anomaly model adjusts its expected range to the time of day, so the same alert configuration works around the clock without manual tuning. In the screenshot, the spike around 8:40 breaches the band and appears as a red line — that is the signal to investigate, regardless of whether the absolute count crossed a fixed threshold.
Outlier detection
Where anomaly detection asks "is this service behaving differently than usual?", outlier detection asks "is this service behaving differently than its peers?" It compares multiple time series against each other and flags any that deviate from the group, using the DBSCAN clustering algorithm to define "the group."
level="error" | timeslice 120s | count by (_timeslice, kube_namespace) | outlier (_count) by 120s, model=dbscan, eps=3
The query groups error counts by kube_namespace, producing one time series per namespace. The outlier operator then identifies which namespaces behave differently from the cluster as a whole. In the chart, solid lines are outliers; dotted lines are non-outliers. The eps (epsilon) parameter controls the tolerance: lower values make the algorithm more sensitive and flag more series as outliers; higher values require a larger deviation to trigger.
When to use this query:
- Identifying the affected component in a multi-service incident
-
In a distributed system, an incident rarely affects all services equally. Outlier detection shows you which namespaces or services have error patterns that differ from the rest of the fleet. The affected service stands out as a solid line that diverges from the dotted non-outlier series — often minutes before users report a problem. This narrows the investigation to a specific component rather than requiring you to check each service manually.
Log math operator to scale down the Y-axis
When log count varies by orders of magnitude across different services or time periods — for example, a high-traffic service generating 10 million logs per minute alongside a low-traffic service generating 100 — plotting both on the same linear scale makes the low-traffic service invisible. Applying a log (logarithm) or cbrt (cube root) transform compresses the scale so that both series remain visible and comparable.
* | timeslice 1m | count as c by (_timeslice, source) | log(c) as log_count
When to use this query:
- Comparing services with very different volumes
-
Without the transform, a single high-volume service dominates the chart and makes it impossible to see what lower-volume services are doing. After
log, a service generating 10,000 logs/minute (log₁₀ ≈ 4) and one generating 100 logs/minute (log₁₀ ≈ 2) are both clearly visible on the same chart, and their trends can be compared directly. - Revealing trends hidden by outlier spikes
-
A single brief traffic spike can compress the rest of the time series to a flat line on a linear scale.
logreduces the visual dominance of extreme values, making it easier to see gradual trends — a slow ramp, a daily pattern, or a persistent elevated baseline — that would otherwise be obscured. For scale compression without the mathematical properties of logarithms,cbrt(cube root) is a gentler alternative that works well for counts that include zero.