I verified 25 new commands and functions added to CloudWatch Logs Insights (July 2026 edition)
This page has been translated by machine translation. View original
Introduction
On July 15, 2026, 25 new commands and functions were added to CloudWatch Logs Insights.
This is the third installment following the May edition (13 items) and June edition (23 items), bringing the three-month cumulative total to 61. This release focuses on analysis-oriented features such as row-based sequence operations, sessionization, outlier detection, and CIDR lookup.
Below is a list of the added features.
| Category | Function/Command Name | Purpose |
|---|---|---|
| Type conversion | hexToAscii | Converts hex string to ASCII |
| Type conversion | hexToDec | Converts hex string to decimal |
| Type conversion | decToHex | Converts decimal to hex string |
| Date/time | parseDate | Parses date string to epoch milliseconds |
| Date/time | formatDate | Formats timestamp using strftime format |
| Date/time | queryStartTime | Returns query window start time |
| Date/time | queryEndTime | Returns query window end time |
| Date/time | queryTimeRange | Returns query window width in milliseconds |
| Row sequence | autoregress | Generates lagged fields from previous row values |
| Row sequence | accum | Calculates cumulative sum of a numeric field |
| Row sequence | filldown | Forward-fills missing values with the last non-null value |
| Row sequence | fillmissing | Inserts specified value into empty time bins |
| Session | sessionize | Sessionizes based on identity + inactivity gap |
| Statistics | variance | Calculates population variance |
| Statistics | topk | Returns the top k most frequent values |
| Statistics | countFrequent | Returns approximate counts of field values in descending order |
| String | messageSize | Returns the byte length of a field |
| JSON | jsonArraySize | Returns the number of elements in a JSON array string |
| JSON | jsonArrayContains | Determines whether a JSON array contains a specified value |
| Condition | isNumeric | Determines whether a field value is numeric |
| Query composition | where | Alias for the filter command |
| Analysis | outlier | IQR-based outlier detection |
| Time comparison | logcompare | Compares current and past time windows |
| Query composition | appendcols | Joins columns from a subquery by row position |
| Lookup | cidrlookup | Matches IPs against a lookup table using CIDR ranges |
Comparing the addition trends over three months, the direction of features differs by month.
| Period | Added | Trend |
|---|---|---|
| May 2026 | 13 | Read & format (extended parse, display, string functions) |
| June 2026 | 23 | Aggregation & validation (conditional branching, statistical functions, IP validation) |
| July 2026 | 25 | Analysis, sessionization & joining (row operations, outlier, lookup) |
In this article, all 25 of these were actually executed against log group /test/insights-new-2026-07 (60 test records) in the us-east-1 region. The observed behaviors and practical notes are summarized below. Verification was conducted on 2026-07-17.
Verification Details
Type Conversion / Encoding
These are three functions — hexToAscii, hexToDec, and decToHex — that convert between hexadecimal and ASCII strings or decimal numbers.
filter ispresent(hex_ascii)
| display hex_ascii, hexToAscii(hex_ascii) as ascii_text, hex_num, hexToDec(hex_num) as dec_val, decimal, decToHex(decimal) as hex_out
| hex_ascii | ascii_text | hex_num | dec_val | decimal | hex_out |
|---|---|---|---|---|---|
| 436c6f7564576174636820496e736967687473 | CloudWatch Insights | 7fff | 32767 | 32767 | 0x7fff |
| 576f726c64 | World | 1a | 26 | 26 | 0x1a |
| 48656c6c6f | Hello | ff | 255 | 255 | 0xff |
hexToAscii converts a hex string to an ASCII string. "CloudWatch Insights" was successfully restored from a 38-character (19-byte) input, confirming support for variable-length inputs. hexToDec accepts lowercase hex without a 0x prefix and returns a decimal number. The reverse direction, decToHex, returns lowercase hex with a 0x prefix.
Date/Time Functions
Five functions were added: parseDate and formatDate for converting between date strings and timestamps, and queryStartTime, queryEndTime, and queryTimeRange for referencing the query window times themselves. These are verified together.
Verification results for parseDate and formatDate:
filter ispresent(date_str)
| display event, date_str, parseDate(date_str, "yyyy-MM-dd", "UTC") as parsed, formatDate(@timestamp, "%Y-%m-%d %H:%M:%S", "UTC") as fmt
| event | date_str | parsed | fmt |
|---|---|---|---|
| rollback | 2026-07-01 | 2026-07-01 00:00:00.000 | 2026-07-16 18:09:23 |
| release | 2026-07-10 | 2026-07-10 00:00:00.000 | 2026-07-16 18:09:18 |
| deploy | 2026-07-15 | 2026-07-15 00:00:00.000 | 2026-07-16 18:09:13 |
parseDate takes a Java DateTimeFormatter pattern as the second argument and a timezone as the third argument. The return value is in epoch milliseconds, but it is rendered in timestamp format in the Logs Insights console. formatDate uses an strftime-format string, and the official documentation states it can also be called using the alias strftime.
Next, the three functions that return query window times:
fields queryStartTime() as qst, queryEndTime() as qet, queryTimeRange() as qtr | limit 1
| qst | qet | qtr |
|---|---|---|
| 2026-07-16 16:13:38.000 | 2026-07-16 18:14:38.999 | 7260999 |
queryTimeRange returns the width of the query window in milliseconds. For a window of approximately 2 hours, 7260999ms was returned, making it usable within queries as a reference value for aggregation or normalization.
Row Sequence / Null Handling
autoregress, accum, and filldown were added as operations that depend on row ordering, while fillmissing was added to fill in missing time bins. For the former three commands, sort @timestamp asc must be specified earlier in the pipeline (behavior confirmed during verification).
autoregress expands previous row values into new columns as lag fields.
filter ispresent(metric) | sort @timestamp asc | fields @timestamp, value | autoregress value p=1-2
| @timestamp | value | value_p1 | value_p2 |
|---|---|---|---|
| 2026-07-16 18:09:43.682 | 10 | ||
| 2026-07-16 18:09:48.682 | 12 | 10 | |
| 2026-07-16 18:09:53.682 | 11 | 12 | 10 |
| 2026-07-16 18:09:58.682 | 13 | 11 | 12 |
| 2026-07-16 18:10:03.682 | 12 | 13 | 11 |
Specifying p=1-2 automatically generates value_p1 (holding the value from one row prior) and value_p2 (holding the value from two rows prior). The leading rows are empty since there are fewer rows than the lag amount. Since previous row values become accessible, this can also be used to create derived columns such as taking the difference with value - value_p1.
accum calculates the cumulative sum of a numeric field.
filter ispresent(metric) | sort @timestamp asc | fields @timestamp, value | accum value AS running_total
| @timestamp | value | running_total |
|---|---|---|
| 2026-07-16 18:09:43.682 | 10 | 10 |
| 2026-07-16 18:09:48.682 | 12 | 22 |
| 2026-07-16 18:09:53.682 | 11 | 33 |
| 2026-07-16 18:09:58.682 | 13 | 46 |
| 2026-07-16 18:10:03.682 | 12 | 58 |
filldown forward-fills missing values using the most recent non-null value.
filter ispresent(sensor) | sort @timestamp asc | fields @timestamp, sensor, region, reading | filldown region
| @timestamp | sensor | region | reading |
|---|---|---|---|
| 2026-07-16 18:11:23.682 | temp-1 | us-east-1 | 22.5 |
| 2026-07-16 18:11:28.682 | temp-1 | us-east-1 | 23.1 |
| 2026-07-16 18:11:33.682 | temp-1 | us-east-1 | 22.8 |
| 2026-07-16 18:11:38.682 | temp-1 | eu-west-1 | 19.2 |
| 2026-07-16 18:11:43.682 | temp-1 | eu-west-1 | 19.5 |
In the original data, the region field was missing in rows 2, 3, and 5, but filldown filled them in using the most recent non-null value. This is useful for sensor data or event logs where contextual information is only recorded in some records.
fillmissing is used after stats ... by bin() to insert a specified value into time bins with no data.
filter metric = "cpu_usage" | stats avg(value) as avg_val by bin(10s) | fillmissing with 0 for avg_val
| bin(10s) | avg_val |
|---|---|
| 2026-07-16 18:27:50.000 | 0 |
| 2026-07-16 18:27:40.000 | 0 |
| 2026-07-16 18:27:30.000 | 0 |
All three displayed rows were bins with no data, and fillmissing inserted 0 into them. Since the verification data contained no bins with actual data within the display range, all values are 0.
Sessionization
sessionize is a command that splits a sequence of events into sessions based on an identity field and an inactivity gap (maxspan).
filter ispresent(userId) | sort @timestamp asc | fields @timestamp, userId, action, deviceId | sessionize userId, deviceId maxspan 20s
| @timestamp | userId | action | deviceId | session_id |
|---|---|---|---|---|
| 2026-07-16 18:11:48.682 | user-A | login | mobile | user-A\x1fmobile-1 |
| 2026-07-16 18:11:53.682 | user-A | view_page | mobile | user-A\x1fmobile-1 |
| 2026-07-16 18:11:58.682 | user-B | login | desktop | user-B\x1fdesktop-1 |
| 2026-07-16 18:12:03.682 | user-A | click | mobile | user-A\x1fmobile-1 |
| 2026-07-16 18:12:08.682 | user-B | view_page | desktop | user-B\x1fdesktop-1 |
| 2026-07-16 18:12:13.682 | user-B | logout | desktop | user-B\x1fdesktop-1 |
| 2026-07-16 18:12:18.682 | user-A | login | mobile | user-A\x1fmobile-1 |
| 2026-07-16 18:12:23.682 | user-A | purchase | mobile | user-A\x1fmobile-1 |
Using the combination of userId and deviceId as the identity, events that are consecutive within maxspan 20s were grouped into the same session. A session_id field is automatically generated. In this verification, the session_id was displayed in a format where identifier field values are joined by the control character \x1f (Unit Separator) with a sequential number appended at the end, as in user-A\x1fmobile-1. This format is not explicitly documented in the official documentation and may be implementation-dependent. According to the official documentation, the default value for maxspan is 30 minutes. It is now possible to perform session analysis of user behavior using Logs Insights alone.
Statistical Commands
Three statistical functions have been added: variance (population variance), topk (top k most frequent values), and countFrequent (approximate frequency count).
variance was verified alongside the existing stddev and avg.
filter ispresent(response_time) | stats variance(response_time) as v, stddev(response_time) as sd, avg(response_time) as avg_rt
| v | sd | avg_rt |
|---|---|---|
| 4788776.5156 | 2188.3273 | 1017.4667 |
variance returns the population variance. Squaring the stddev value yields approximately the same result as variance (2188.3273² ≈ 4788776), confirming consistency with the standard deviation.
topk returns the top k most frequent values and their occurrence counts.
filter ispresent(status) | stats topk(3, status)
| status | count |
|---|---|
| 200 | 10 |
| 403 | 2 |
| 500 | 2 |
The official documentation states that use with a by clause is not supported.
countFrequent calculates approximate counts for each combination of field values and returns them in descending order.
filter ispresent(method) | countFrequent method, status
| method | status | _approxcount |
|---|---|---|
| GET | 200 | 5 |
| POST | 200 | 5 |
| GET | 500 | 2 |
| GET | 403 | 2 |
| POST | 401 | 1 |
The _approxcount field is automatically generated.
String Functions
messageSize is a function that returns the byte length of a field.
fields messageSize(@message) as sz, @message | sort sz desc | limit 5
| sz | @message (excerpt) |
|---|---|
| 147 | {"level": "INFO", "service": "payment-service"...} |
| 146 | {"level": "INFO", "service": "payment-service"...} |
| 145 | {"level": "INFO", "service": "payment-service"...} |
JSON Functions
jsonArraySize and jsonArrayContains are functions that operate on JSON array strings.
filter ispresent(role_list)
| display user, role_list, jsonArraySize(role_list) as role_count, jsonArrayContains(role_list, "admin") as is_admin
| user | role_list | role_count | is_admin |
|---|---|---|---|
| charlie | ["admin","viewer"] | 2 | 1 |
| bob | ["viewer"] | 1 | 0 |
| alice | ["admin","editor","viewer"] | 3 | 1 |
jsonArraySize returns the number of elements in the array, and jsonArrayContains returns 1 if the specified value is included, or 0 if it is not. These functions cannot be used with arrays that have been expanded by auto-parsing (such as roles.0), and must be applied to fields that retain their string form.
Conditional Validation
isNumeric determines whether a field value can be interpreted as a number.
filter ispresent(field_a)
| display field_a, isNumeric(field_a) as a_num, field_b, isNumeric(field_b) as b_num
| field_a | a_num | field_b | b_num |
|---|---|---|---|
| 0 | 1 | 99.9 | 1 |
| not_number | 0 | 100 | 1 |
| 42 | 1 | hello | 0 |
Query Composition (where)
where is an alias for the filter command (described in the official documentation as "grammar alias for the filter command") and is intended for users familiar with SQL.
where response_time > 1000 | display service, response_time
| service | response_time |
|---|---|
| notification | 1100 |
| notification | 2045 |
| api-gateway | 8901 |
| auth-service | 1523 |
Data Analysis (outlier)
outlier is a statistical outlier detection command based on the IQR (interquartile range). The official documentation describes it as "Detects statistical outliers based on the interquartile range (IQR)."
filter metric = "cpu_usage" | sort @timestamp asc | fields @timestamp, value | outlier mark=true param=1.5 value
The command was accepted and matched=20, but a behavior was observed where the output was limited to one record. The cause has not been confirmed. For practical use, re-verification with a larger dataset is recommended. According to the official documentation, specifying mark=true adds a boolean marker field indicating whether each value is an outlier. In this verification environment, this field was displayed as _is_outlier. Available options include action=remove/transform, mark=true, and param (IQR coefficient).
logcompare
logcompare is a command that compares the current time window with a past window.
Verification was conducted with a 2-hour query window and a timeshift of 1h. The error "Diff doesn't support overlap between two time ranges we compare" occurs. If the timeshift is less than the query window width, the compared time ranges overlap, causing this error. This is behavior confirmed during verification, and this constraint was not found to be explicitly stated in the referenced official documentation. A timeshift larger than the query window must be specified for normal operation.
appendcols
appendcols is a command that joins result columns from a subquery to the main query by row position.
filter ispresent(service)
| stats count(*) as cnt by service
| appendcols ( SOURCE '/test/insights-new-2026-07' | filter ispresent(response_time) | stats avg(response_time) as avg_rt by service )
| service | cnt | avg_rt |
|---|---|---|
| notification | 3 | 1070.6667 |
| search-service | 3 | 212.6667 |
| api-gateway | 3 | 3145 |
| payment-service | 3 | 84.3333 |
| auth-service | 3 | 574.6667 |
A SOURCE clause is required in the subquery. Omitting SOURCE causes a MalformedQueryException. Joining is done by positional row matching, not key matching. In this verification, the results from the main query and subquery were returned in the same order, and the corresponding values were correctly joined. For practical use, it is recommended to explicitly specify sort in both the main query and subquery to align row order.
cidrlookup
cidrlookup is a command that matches IP addresses against a lookup table using CIDR ranges and retrieves the corresponding values.
A lookup table must be created in advance. This can be done from the CloudWatch console under Settings → Logs tab → Lookup tables → Create lookup table. There is also a method of directly uploading a CSV using the CreateLookupTable API.
The CSV used this time is as follows. The IP addresses listed are dummy values for verification purposes, including documentation addresses defined in RFC 5737.
cidr,region,owner,network_type
198.51.100.0/24,us-west-2,external,public
10.0.0.0/8,us-east-1,internal,private
172.16.0.0/12,ap-northeast-1,internal,private
192.168.0.0/16,eu-west-1,internal,private
203.0.113.0/24,documentation,example,reserved
filter ispresent(ip)
| fields @timestamp, ip, service
| cidrlookup net_table ip AS cidr OUTPUT region, owner, network_type
| display ip, region, owner, network_type
| ip | region | owner | network_type |
|---|---|---|---|
| 198.51.100.10 | us-west-2 | external | public |
| 10.0.2.101 | us-east-1 | internal | private |
| 192.168.1.11 | eu-west-1 | internal | private |
| 172.16.0.6 | ap-northeast-1 | internal | private |
| 203.0.113.50 | documentation | example | reserved |
AS cidr specifies the CIDR column name in the lookup table, and OUTPUT lists the columns to retrieve. It was necessary to explicitly specify the OUTPUT columns in display to show the results.
Summary
The 25 commands and functions added in July 2026 have further expanded the range of log analysis that can be handled within CloudWatch Logs Insights.
In particular, time-series data processing with autoregress, accum, filldown, and fillmissing, user behavior sessionization with sessionize, and outlier detection with outlier look promising for incident investigation and trend analysis. Additionally, logcompare enables comparison with past time periods, cidrlookup enables attribute assignment to IP addresses, and appendcols enables analysis that places multiple aggregation results side by side.
These are capabilities that previously might have required exporting logs to a database or external analytics service for processing. With this feature addition, some of that work can now be performed within Logs Insights queries. For example, these features can be used for the following types of log investigations:
- Checking differences from previous values or cumulative values of metric data
- Filling in missing time-series data or contextual information
- Sessionizing a series of operations per user or device
- Extracting outliers in response times or processing volumes
- Comparing current log trends with a past time period
- Attaching network or administrative classification information to IP addresses
While some features require care when using them — such as commands that depend on row position or time ranges — this update increases the number of situations where aggregation and processing during an investigation can be completed entirely within Logs Insights.
Test Data Structure and Ingestion Example (Excerpt)
The log group used for verification and the method for ingesting test data are shown below. The following is an excerpt of the structure and is not the complete code that generates all 60 records used in the article. The us-east-1 region was used.
Create the log group and log stream.
aws logs create-log-group --log-group-name /test/insights-new-2026-07 --region us-east-1
aws logs create-log-stream --log-group-name /test/insights-new-2026-07 --log-stream-name test-stream --region us-east-1
The following is an excerpt from the code that generated the 60 JSON logs, showing the portions for time-series metrics and hex conversion data.
import json, time
now = int(time.time() * 1000)
events = []
# Example: time-series metrics (20 records)
for i, v in enumerate([10, 12, 11, 13, 12]): # excerpt
events.append({
"timestamp": now + i * 5000,
"message": json.dumps({"metric": "cpu_usage", "value": v})
})
# Example: hex conversion data
events.append({
"timestamp": now,
"message": json.dumps({
"hex_ascii": "436c6f7564576174636820496e736967687473",
"hex_num": "7fff", "decimal": 32767
})
})
# Continuing, add logs for date, JSON array, session, and CIDR in the same manner
with open("events.json", "w") as f:
json.dump(events, f)
Ingest the generated logs using put-log-events.
aws logs put-log-events \
--log-group-name /test/insights-new-2026-07 \
--log-stream-name test-stream \
--log-events file://events.json \
--region us-east-1
