CloudWatch Logs Insights lets you search and aggregate log data with a purpose-built query language, without a metric filter defined in advance. Because a query can scan large volumes of logs, the SDK models it as an asynchronous job: you start it, then poll for results.
A Logs Insights query has five possible terminal-adjacent states: Scheduled, Running, Complete, Failed, Cancelled, and Timeout. Only Complete means you have final results; the others mean stop polling and handle the outcome.
StartQuery accepts one log group name (or, in newer API versions, multiple logGroupNames/logGroupIdentifiers) plus a Unix startTime/endTime range and the query string. It does not accept the query language as a structured object - it is always a plain string.
Logs Insights queries are piped commands, similar in spirit to a Unix shell pipeline:
fields @timestamp, @message
| filter @message like /ERROR/
| stats count(*) as errorCount by bin(5m)
| sort errorCount desc
| limit 20
fields selects or computes columns, including the built-in @timestamp, @message, @logStream fields.
filter narrows rows, similar to a WHERE clause.
stats aggregates, commonly with count(), avg(), pct(), grouped by a field or a time bucket like bin(5m).
sort and limit shape the final output.
This syntax is identical whether you submit it from boto3, SDK v3, the CLI, or the Console query editor - it is not part of either language's SDK surface, just a string you pass through.
StopQuery transitions the query to Cancelled; a subsequent GetQueryResults call still returns whatever partial results were computed before cancellation.
Treating GetQueryResults as blocking - it returns immediately with whatever progress exists so far. Fix: poll in a loop and check status until it reaches a terminal value.
Fixed sleep loops with no upper bound - a stuck query polls forever. Fix: cap total polling attempts or elapsed time, and call StopQuery if exceeded.
Passing the query language as a structured parameter - there is no field-by-field query builder. Fix: build the pipeline string yourself; treat it as opaque text to the SDK.
Overly broad time ranges - a wide startTime/endTime window over a busy log group can hit the per-query data scan limit before returning useful results. Fix: narrow the range or add an early filter.
Forgetting limit - without it, stats and fields queries can return far more rows than needed. Fix: always cap output with limit unless you specifically want everything.
Ignoring Failed/Timeout status - silently treating any non-Complete status as "still running." Fix: branch explicitly on Failed, Cancelled, and Timeout and handle each.
Queries can scan large volumes of log data, so AWS runs them as a background job. StartQuery submits the job and returns immediately; GetQueryResults is polled until it reports Complete.
Is the query language part of the SDK?
No. fields/filter/stats/sort/limit is a distinct query language you pass as a plain string in the queryString parameter. It is identical across boto3, SDK v3, the CLI, and the Console.
How do I know when a query is done?
Check the status field returned by GetQueryResults. Complete means final results are ready; Failed, Cancelled, and Timeout mean stop polling and handle the failure.
Can I cancel a query that's taking too long?
Yes, call StopQuery with the queryId. It transitions the query to Cancelled, and a following GetQueryResults call returns whatever partial results exist.
Can one query span multiple log groups?
Yes, pass logGroupNames (or logGroupIdentifiers) with more than one entry. Use the @log field in results to see which log group each row came from.
What shape are the results in?
results is a list of rows, and each row is itself a list of {field, value} pairs rather than a flat object - both SDKs leave that reshaping to your code.