Aggregation operators

Aggregation operators enable you to create log-based metrics. Log-based metrics help you cut through the noise of high-volume logs to identify trends and patterns in your application activity.

FuseQL groups aggregations by time buckets and supports additional grouping dimensions.

avg

Computes the arithmetic mean of a numeric field across matched log lines within the selected query time window. The avg operator ignores null or missing values. Use it to understand the typical value of a metric — for example, the mean response body size per HTTP method over the last hour.

Syntax

| avg(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to average. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _avg.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and compute the average response body size (in bytes) per HTTP method. A much higher average for GET than POST reflects that GET requests return full resource payloads while POST responses are typically small acknowledgements.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| avg(bytes) as avg_bytes by method
Expected output
method avg_bytes

GET

87,284.33

POST

37.77

OPTIONS

69.0

avg aggregates over every log line that matches the query within the selected time window (for example, the last 1 hour). A single returned row represents the mean across all matching events in that window. After extracting fields with parse, FuseQL coerces the string values to numbers automatically — no explicit conversion is needed.

count_unique

Counts the number of distinct values of a field across matched log lines. count_unique works on string, UUID, and IP address typed facets — it cannot be applied to purely numeric fields. Use it to answer questions like "how many distinct URLs were requested per HTTP method?" or "how many unique user sessions made requests in this window?"

Syntax

| count_unique(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The string, UUID, or IP address facet to count distinct values of.

as <alias>

Optional

Renames the output column. Defaults to _count_unique.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example: distinct URLs per HTTP method

Parse nginx access logs and count how many unique URLs were requested for each HTTP method. A high count for GET compared to POST indicates broad read access across many resources.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| count_unique(url) by method
Expected output
method _count_unique

DELETE

2

GET

3,353

POST

197,143

count_unique works on string, UUID, and IP address typed facets. Fields extracted by parse are treated as strings, so they work directly with count_unique without any conversion.

count

Counts the total number of log lines matched by the query. Unlike most aggregation operators, count takes no field argument — it counts rows, not field values. Use it to measure request volume, error frequency, or event occurrence across any grouping.

Syntax

| count [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

as <alias>

Optional

Renames the output column. Defaults to _count.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example: request count by HTTP method

Parse nginx access logs and count requests per HTTP method to understand traffic distribution.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| count by method
Expected output
method _count

DELETE

4

GET

92,253

POST

67,267,496

Example: time-bucketed request counts

Count requests per minute to spot traffic spikes or drops over time.

source="nginx"
| timeslice 1m
| count as req_count by _timeslice
Expected output
_timeslice req_count

2024-06-10 09:00

38,412

2024-06-10 09:01

41,987

2024-06-10 09:02

39,631

count counts every matched log line, including lines where a particular field is null. To count only lines where a specific field has a value, use count_unique on that field, or add a where filter before counting.

first

Returns the value of a field from the chronologically earliest log line within the query window. When grouped with by, first returns the earliest value observed per group. Use it to capture the initial state — for example, the first URL requested via each HTTP method at the start of a traffic window.

Syntax

| first(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The field whose earliest value to return. Works with string and numeric fields.

as <alias>

Optional

Renames the output column. Defaults to _first.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and retrieve the first URL requested for each HTTP method. This shows the first request path seen for each method within the selected time window.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| first(url) by method
Expected output
method _first

DELETE

/grafana/apis/…​/dashboards/dcb2ef50

GET

/oauth2/sign_in?rd=%2F HTTP/1.1

POST

/ingester/otlp/metrics HTTP/2.0

first is determined by log ingestion order within the query window, which reflects the timestamp on each log line. It is most useful for baselining or detecting cold-start behaviour. Compare first against last to see whether values changed significantly over the window.

last

Returns the value of a field from the chronologically latest log line within the query window. When grouped with by, last returns the most recent value observed per group. Use it to capture the current or final state — for example, the most recent URL requested via each HTTP method at the end of a traffic window.

Syntax

| last(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The field whose latest value to return. Works with string and numeric fields.

as <alias>

Optional

Renames the output column. Defaults to _last.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and retrieve the most recent URL requested for each HTTP method. This shows the last request path seen for each method within the selected time window.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| last(url) by method
Expected output
method _last

DELETE

/grafana/apis/…​/dashboards/fc05b8c4

GET

/ingester/health/ HTTP/2.0

POST

/ingester/otlp/v1/logs HTTP/2.0

last is the counterpart of first — together they let you compare the earliest and most recent observations within a window to detect trends or state changes. last is most useful for capturing the final outcome of a process or the steady-state value after a warm-up period.

max

Returns the highest value of a numeric field across matched log lines. The max operator ignores null or missing values. Use it to surface worst-case conditions such as the highest HTTP status code, peak response latency, or maximum error count observed in a window.

Syntax

| max(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to find the maximum of. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _max.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and find the largest response body size (in bytes) per HTTP method. A very large max_bytes for GET reveals the biggest single payload served — useful for identifying unexpectedly large responses.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| max(bytes) as max_bytes by method
Expected output
method max_bytes

GET

35,590,999

POST

10,422,627

OPTIONS

138

max surfaces worst-case conditions. A single outlier can cause max to spike even when avg looks healthy — pair max with p99 to distinguish rare extreme outliers from the general tail. After extracting fields with parse, FuseQL coerces string values to numbers automatically.

min

Returns the lowest value of a numeric field across matched log lines. The min operator ignores null or missing values. Use it to find the best-case response time, the lowest HTTP status code, or the minimum resource usage observed in a window.

Syntax

| min(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to find the minimum of. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _min.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and find the smallest response body size (in bytes) per HTTP method. A min_bytes of 0 for GET and POST indicates that some responses sent no body — for example, 204 No Content or 304 Not Modified responses.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| min(bytes) as min_bytes by method
Expected output
method min_bytes

GET

0

POST

0

OPTIONS

0

min is useful for establishing a lower bound. A result of 0 bytes does not mean the response failed — status codes like 204 No Content and 304 Not Modified legitimately send no body. Combine min and max in a single query to see the full response size range in one result set. After extracting fields with parse, FuseQL coerces string values to numbers automatically.

percentiles

Computes a percentile of a numeric field across matched log lines. Seven percentile operators are available: p16, p50, p75, p84, p90, p95, and p99. Multiple percentile operators can be combined in a single query pipe stage to produce several percentile columns at once. Null or missing field values are ignored.

Syntax

| p50(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p75(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p90(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p95(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p99(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p84(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p16(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to compute the percentile of. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _p<N> (e.g. _p95).

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example: median and tail percentiles for HTTP status codes

Parse nginx access logs and compute p50, p90, and p99 of response body size per HTTP method. The large gap between GET p50 (138 bytes) and p99 (3,678,129 bytes) reveals that while most GET responses are small, the top 1% are multi-megabyte payloads — exactly the kind of tail behaviour that avg would understate.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| p50(bytes) as p50_bytes, p90(bytes) as p90_bytes, p99(bytes) as p99_bytes by method
Expected output
method p50_bytes p90_bytes p99_bytes

GET

138

3,026

3,678,129

POST

2

2

2

OPTIONS

69

124

137

Use p95 or p99 for SLO threshold checks and p50 (the median) for a robust measure of typical behaviour that is not skewed by outliers. A large gap between p50 and p99 indicates a long-tail distribution — common for endpoints that serve both small API responses and large file downloads. After extracting fields with parse, FuseQL coerces string values to numbers automatically.

stddev

Computes the standard deviation of a numeric field across matched log lines. stddev measures how spread out values are around the mean — a low value indicates that most log lines have similar values, while a high value indicates erratic or inconsistent behaviour. Null or missing field values are ignored.

Syntax

| stddev(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to compute the standard deviation of. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _stddev.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and measure the consistency of response body sizes per HTTP method. A very high standard deviation for GET (1,073,687 bytes) reflects that GET responses range from zero-byte 304 responses to multi-megabyte payloads. The low standard deviation for POST (1,894 bytes) shows that POST responses are consistently small.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| stddev(bytes) as stddev_bytes by method
Expected output
method stddev_bytes

GET

1,073,687.61

POST

1,894.84

OPTIONS

69.0

stddev is expressed in the same unit as the input field (bytes, in this case), making it easy to interpret alongside avg. A high stddev relative to avg signals a wide mix of response sizes — this is normal for GET endpoints that serve both small API responses and large file downloads. Prefer stddev over stdvar when you need a human-readable measure of spread. After extracting fields with parse, FuseQL coerces string values to numbers automatically.

stdvar

Computes the variance of a numeric field across matched log lines. Variance is the square of the standard deviation and quantifies the spread of values around the mean. It is primarily used in statistical calculations that combine variance across groups; for human-readable interpretation, prefer stddev. Null or missing field values are ignored.

Syntax

| stdvar(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to compute the variance of. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _stdvar.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and compute the variance in response body size per HTTP method. The very high variance for GET (1,149,810,367,291 bytes²) versus POST (3,591,132 bytes²) reflects the wide range of payload sizes returned by GET requests compared to the consistently small POST responses.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| stdvar(bytes) as var_bytes by method
Expected output
method var_bytes

GET

1,149,810,367,291.90

POST

3,591,132.22

OPTIONS

4,761.0

Because variance squares the unit of the input field (bytes², in this case), the result is difficult to interpret directly. Use stdvar when you need to mathematically combine variance across groups (for example, pooled variance calculations). For dashboards and alerts where you want a spread figure in the original unit (bytes), use stddev instead. After extracting fields with parse, FuseQL coerces string values to numbers automatically.

sum

Computes the total sum of a numeric field across matched log lines. The sum operator ignores null or missing values. Use it to measure cumulative load — for example, the total number of requests handled per HTTP method or the aggregate bytes transferred in a window.

Syntax

| sum(<field>) [as <alias>] [by <field1>, <field2>, ...]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field to sum. Null or missing values are ignored.

as <alias>

Optional

Renames the output column. Defaults to _sum.

by <field1>, …​

Optional

Groups results by one or more fields. Without by, returns a single aggregate row for all matched logs.

Example

Parse nginx access logs and compute the total bytes transferred per HTTP method. Summing response body sizes gives the total data volume served by each method during the query time window — useful for bandwidth analysis and capacity planning.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| sum(bytes) as total_bytes by method
Expected output
method total_bytes

GET

7,913,715,573

POST

2,524,318,781

OPTIONS

138

sum is most meaningful for fields that represent quantities that accumulate, such as bytes transferred, processing time, or event counts. The values above are totals across all log lines in the selected time window. After extracting fields with parse, FuseQL coerces string values to numbers automatically.