The appendAggregation() function applies an
aggregation to a sequence of events and appends the result at
the end of that sequence.
Unlike a simple pipe operation, which replaces the existing
event sequence with the aggregation result, the
appendAggregation() function preserves the
original events and adds the aggregation output after them.
Preserving the original events makes it possible to include
summary rows, such as totals or averages, alongside the detail
rows that produced them.
Hide omitted argument names for this function
Omitted Argument NamesThe argument name for
functioncan be omitted; the following forms of this function are equivalent:logscale SyntaxappendAggregation("value")and:
logscale SyntaxappendAggregation(function="value")These examples show basic structure only.
appendAggregation() Syntax Examples
This example demonstrates appending a single summary row to a
sequence of detail events using the
appendAggregation() function.
Consider an event set recording individual transaction values keyed by category. The goal is to display each transaction alongside a final row showing the total value across all transactions.
head()
| appendAggregation({ sum(value, as="value") | key:="total" })If event data looks like this:
| key | value |
|---|---|
| a | 2 |
| b | 3 |
| c | 7 |
it would return:
| key | value |
|---|---|
| a | 2 |
| b | 3 |
| c | 7 |
| total | 12 |
This example demonstrates that the
appendAggregation() function appends the
result of an aggregation to the existing event stream, using
avg() to calculate the mean of the
value field across all events, and
assigning the result a custom key of
average_value.
appendAggregation({
avg(value, as="value") | key := "average_value"If event data looks like this:
| key | value |
|---|---|
| a | 2 |
| b | 3 |
| c | 5 |
| d | 6 |
it would return:
| key | value |
|---|---|
| a | 2 |
| b | 3 |
| c | 5 |
| d | 6 |
| average_value | 4 |
appendAggregation() Examples
Click next to an example below to get the full details.
Append Grand Total Row to Failed Login Counts
Summarize failed authentication attempts per user and append an
overall total using the appendAggregation()
function
Query
auth_result = "failure"
| groupBy(username, function=count(as="failed_attempts"))
| appendAggregation({ sum(failed_attempts, as="failed_attempts")
| username:="All Users" })Introduction
In this example, the appendAggregation() function
is used to produce a per-user count of failed login attempts and then
append a grand total row at the bottom of the result table. This allows
a security analyst to immediately see both which accounts are being
targeted and the total volume of failed authentication events across the
environment — a key indicator when investigating a potential
brute-force or credential stuffing attack.
The incoming events represent authentication log entries from an
identity provider or operating system authentication service. Each event
contains a username, a source IP address, and an authentication result.
The query filters for failed authentication events, groups and counts
them by username, and finally appends a grand total row using
appendAggregation().
Example incoming data might look like this:
| @timestamp | username | source_ip | auth_result |
|---|---|---|---|
| 2026-04-01T02:01:00Z | alice | 203.0.113.45 | failure |
| 2026-04-01T02:01:45Z | bob | 203.0.113.45 | failure |
| 2026-04-01T02:02:10Z | alice | 203.0.113.45 | failure |
| 2026-04-01T02:02:55Z | carol | 198.51.100.22 | failure |
| 2026-04-01T02:03:20Z | alice | 203.0.113.45 | failure |
| 2026-04-01T02:03:50Z | bob | 203.0.113.45 | failure |
| 2026-04-01T02:04:15Z | dave | 198.51.100.22 | failure |
| 2026-04-01T02:04:40Z | carol | 198.51.100.22 | failure |
| 2026-04-01T02:05:05Z | alice | 203.0.113.45 | success |
| 2026-04-01T02:05:30Z | bob | 203.0.113.45 | failure |
| 2026-04-01T02:06:00Z | dave | 198.51.100.22 | failure |
| 2026-04-01T02:06:25Z | carol | 198.51.100.22 | failure |
| 2026-04-01T02:07:00Z | alice | 203.0.113.45 | failure |
| 2026-04-01T02:07:30Z | dave | 198.51.100.22 | failure |
| 2026-04-01T02:08:00Z | bob | 203.0.113.45 | failure |
Step-by-Step
Starting with the source repository events.
- logscale
auth_result = "failure"Filters events to retain only those where the auth_result field equals
failure, discarding all successful authentication events from the pipeline. This ensures that only failed login attempts are passed to the subsequent aggregation steps. - logscale
| groupBy(username, function=count(as="failed_attempts"))Groups all remaining events by the username field and, for each group, applies
count()to count the number of failed login events. Theasparameter returns the result in a new field named failed_attempts. Each row in the output at this stage represents one distinct username and the total number of failed authentication attempts associated with that account. - logscale
| appendAggregation({ sum(failed_attempts, as="failed_attempts")Appends a grand total row to the result set by evaluating a sub-query independently against the current result set and adding its output as a new row at the bottom of the table. Inside the sub-query,
sum()calculates the total of all values in the failed_attempts field across all per-user rows, returning the result in a field also named failed_attempts using theasparameter. - logscale
| username:="All Users" })Sets the username field on the appended summary row to the literal string
All Users, making the grand total row clearly identifiable in the output table. This assignment closes the sub-query passed toappendAggregation(), resulting in a final output that contains all per-user failed attempt counts from thegroupBy()step followed by a single appended row showing the combined failed login total across the entire environment. Event Result set.
Summary and Results
The query is used to produce a per-user failed authentication count table that includes a grand total row appended at the bottom, enabling security analysts to assess both individual account exposure and the total volume of failed login activity across the environment in a single result set.
This query is useful, for example, to support triage during a brute-force or credential stuffing investigation, where an analyst needs to quickly identify the most targeted accounts while also understanding the overall scale of the attack — without running separate queries for per-user counts and environment-wide totals.
Sample output from the incoming example data:
| username | failed_attempts |
|---|---|
| alice | 4 |
| bob | 4 |
| carol | 3 |
| dave | 3 |
| All Users | 14 |
Note that the All Users row is always appended as the
last row in the result set regardless of any sorting applied to the
per-user rows above it.
Note also that the one successful login event for
alice is correctly excluded from the counts because
the initial filter retains only failure events before
any aggregation takes place.
The appendAggregation() sub-query sums the
already-aggregated failed_attempts values from the
groupBy() output rather than re-counting the raw
events, so the grand total accurately reflects the sum of all per-user
counts.
Append Overall Failure Rate to Individual Events
Calculating and appending an overall failure rate to each event
record using the appendAggregation() function
with percentage()
Query
head()
| appendAggregation({ percentage({auth_result="failure"}, as="failure_rate")
| format("%,.2f %%", field=failure_rate, as=failure_rate) })Introduction
In this example, the appendAggregation() function
is used to calculate the overall authentication failure rate across all
login events and append it as a formatted percentage string to every
individual event record.
In an authentication audit scenario, it may be necessary to retain each
individual login event while also appending a summary statistic showing
the overall failure rate across all events. The
head() function preserves the raw events, and
appendAggregation() with
percentage() calculates the proportion of events
where auth_result is failure.
The result is then formatted as a percentage string using
format() before being appended to the original
result set row.
Example incoming data might look like this:
| @timestamp | username | source_ip | auth_result |
|---|---|---|---|
| 2026-03-01T08:01:00Z | alice | 203.0.113.45 | failure |
| 2026-03-01T08:02:00Z | bob | 203.0.113.45 | failure |
| 2026-03-01T08:03:00Z | carol | 198.51.100.22 | failure |
| 2026-03-01T08:04:00Z | alice | 203.0.113.45 | failure |
| 2026-03-01T08:05:00Z | bob | 203.0.113.45 | failure |
| 2026-03-01T08:06:00Z | dave | 198.51.100.22 | failure |
| 2026-03-01T08:07:00Z | carol | 198.51.100.22 | failure |
| 2026-03-01T08:08:00Z | alice | 203.0.113.45 | success |
| 2026-03-01T08:09:00Z | alice | 203.0.113.45 | success |
| 2026-03-01T08:10:00Z | bob | 203.0.113.45 | failure |
| 2026-03-01T08:11:00Z | dave | 198.51.100.22 | success |
Step-by-Step
Starting with the source repository events.
- logscale
head()Preserves the raw individual event records in the result set, passing all events through to the next stage of the pipeline without aggregating or dropping any events. By default,
head()returns the first200events if no limit is specified. This step ensures that the individual event rows remain available forappendAggregation()to join the computed statistic back onto each record. - logscale
| appendAggregation({ percentage({auth_result="failure"}, as="failure_rate")Appends a computed aggregate statistic to every individual event row by evaluating a sub-query independently against the full result set and joining its output back onto each row. Inside the sub-query,
percentage()calculates the proportion of events where auth_result equalsfailurerelative to the total number of events. Theasparameter returns the result in a new field named failure_rate as a raw floating-point number, for example72.727272...for 8 failures out of 11 total events. - logscale
| format("%,.2f %%", field=failure_rate, as=failure_rate) })Formats the raw numeric value in failure_rate using the format string
%,.2f %%, which rounds the value to two decimal places and appends a literal percent symbol. Thefieldparameter specifies failure_rate as the source field, and theasparameter writes the formatted result back into the same failure_rate field, overwriting the raw numeric value with the human-readable string, for example72.73 %. The closing brace and parenthesis end the sub-query passed toappendAggregation(), resulting in the formatted failure_rate value being joined back onto every individual event row in the result set. Event Result set.
Summary and Results
The query is used to calculate the overall authentication failure rate across all login events and append it as a formatted percentage string to each individual event record, allowing both per-event detail and a global summary metric to be visible in the same result set.
This query is useful, for example, to audit authentication logs where security analysts need to review individual login attempts while simultaneously seeing the overall failure rate across the entire dataset, without having to run a separate aggregation query.
Sample output from the incoming example data:
| @timestamp | username | source_ip | auth_result | failure_rate |
|---|---|---|---|---|
| 2026-03-01T08:01:00Z | alice | 203.0.113.45 | failure | - |
| 2026-03-01T08:02:00Z | bob | 203.0.113.45 | failure | - |
| 2026-03-01T08:03:00Z | carol | 198.51.100.22 | failure | - |
| 2026-03-01T08:04:00Z | alice | 203.0.113.45 | failure | - |
| 2026-03-01T08:05:00Z | bob | 203.0.113.45 | failure | - |
| 2026-03-01T08:06:00Z | dave | 198.51.100.22 | failure | - |
| 2026-03-01T08:07:00Z | carol | 198.51.100.22 | failure | - |
| 2026-03-01T08:08:00Z | alice | 203.0.113.45 | success | - |
| 2026-03-01T08:09:00Z | alice | 203.0.113.45 | success | - |
| 2026-03-01T08:10:00Z | bob | 203.0.113.45 | failure | - |
| 2026-03-01T08:11:00Z | dave | 198.51.100.22 | success | - |
| - | - | - | - | 72.73 % |
Note that the failure_rate field carries the value
of 72.73 % on the last event row, reflecting that 8
of the 11 events had an auth_result of
failure.