Search for AWS

Command reference

Search for AWS adds nine search commands. Eight of them are generating commands: they start a search (they go first, after the leading |) and produce rows from a live AWS API. The ninth, awsjson, is a streaming formatter you pipe results into.

All eight generating commands require the run_aws_search capability. See Access control.

Conventions shared by all commands

A few arguments and behaviors work the same everywhere, so they are documented once here.

Accounts (account=). Targets the account you registered under that name. account=* fans out to every registered account in parallel. Without account=, the command uses your single registered account (or fans out across all of them when you have several). Every row carries an account field naming the one it came from.

Regions (region=). A region id (us-east-1), or *. Without region=, the command uses the account’s default region. region=* means every region in the account’s allowed-regions list when one is configured; with no list it uses the default region and warns that it did so. A region outside an account’s allowed list is rejected for that account. A comma list, quoted (region="eu-west-1,us-east-1"), queries each region once, and a * in it adds the account’s allowed regions: region="*,us-east-1" reads every allowed region and us-east-1. Each region the list names is still checked against the account’s allowed regions: one outside them is skipped with a warning for that account, and the others are read. Every row carries a region field, except the rows of | awscost, which runs once per account (use group_by=REGION there for a regional breakdown), and of | awsget kind=credential-report, which reads the account’s one IAM report. See Accounts.

concurrency= (default 8, ceiling 64) caps how many account and region pairs are queried at once during a fan-out.

cache= overrides the freshness of the on-disk result cache for this search: cache=0 bypasses it and always calls AWS; cache=5m accepts a result up to five minutes old. Each command has its own default. See Concepts - caching.

Time windows (start=, end=). Where a command takes a window, both accept a date (2026-09-01), epoch seconds, or Splunk relative time (-24h, -30d@d, @mon, now). Without them, the command uses the search’s time-range picker where that makes sense (awscost) or a per-command default.

Quote commas. SPL splits an unquoted argument on commas and spaces, so any value that carries a comma or a space must be quoted: dimensions="BucketName=logs,StorageType=StandardStorage", groups="/aws/lambda/a,/aws/lambda/b". The commands reject a stray fragment with an error that shows the quoted form rather than silently dropping it.

Errors are per target. During a fan-out, an account or region whose AWS call fails emits a single row carrying _target_error, and the search continues against the others. The search also shows the same message as a warning, prefixed with the account and region, which a dashboard shows as the warning icon on every panel drawn from it. A lookup by identifier= of something that does not exist, such as the policy of a bucket that has none, is the answer “none”: its row carries _target_error and it raises no warning. A target that fails earlier - its stored secret cannot be read, or its role cannot be assumed - is skipped with a warning in the search’s messages instead of a row, so read the messages as well.

Incomplete answers end with a notice row. When a command knows its answer is short - CloudWatch marked it incomplete, a Logs Insights query reached limit=, limit= stopped a raw log tail or a CloudTrail lookup before the start of its window, a listing passed its page cap - the rows end with one carrying notice, which says what was left out and how to get it. Some of these are also a warning on the search; each command’s section says which. | awsget’s limit= caps the rows with no notice. Keep the row out of a count with where isnull(notice).


| awsget - list and get resources

Use awsget whenever you would open a service’s console page or run aws <service> describe-*. It lists resources of a kind over the Cloud Control API, or fetches one resource by identifier, and emits one row per resource with its properties as JSON in _raw and as fields.

| awsget kind=<TypeName|alias>
         [identifier=<id>] [detail=<bool>] [limit=<int>]
         [resource_model=<json>]
         [cluster=<arn>] [function_name=<name>] [load_balancer_arn=<arn>]
         [rest_api_id=<id>] [route_table_id=<id>] [scope=<REGIONAL|CLOUDFRONT>]
         [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]
ArgumentDescription
kind=Required. A CloudFormation type name (AWS::EC2::Instance, AWS::ECR::Repository, AWS::CloudWatch::Alarm) or one of the aliases below. Any type with a Cloud Control list handler works, including types AWS adds later; a type with no list handler, or a misspelled name, returns a clear error.
identifier=Fetch exactly one resource by its primary identifier: a single GetResource, no listing, no parent needed. Quote it - compound identifiers such as an ECS service’s carry a |.
detail=trueAdd a per-resource GetResource call so kinds whose list returns only an identifier (stacks, volumes, security groups, KMS keys, queues, and others) carry their full state. One extra call per resource; cap it with limit=. A resource whose read fails keeps the fields its listing carried and says why in detail_error, so a setting the read would have shown is unknown rather than absent.
limit=Cap the number of rows from each account and region. In a fan-out every target returns up to this many, so limit=10 over four regions is up to 40 rows, and with detail=true up to 40 extra calls.
resource_model=Raw {"Key":"Value"} JSON for an uncatalogued type whose list handler needs a parent identifier.
cluster=, function_name=, load_balancer_arn=, rest_api_id=, route_table_id=, scope=Parent identifiers for the kinds that need one - ECS services, Lambda function URLs and permissions, listeners, API resources, routes, WAF web ACLs. A parent-required kind fails fast with guidance when its parent is missing.

Key fields on each row: identifier, type (the CloudFormation type name), account, region, the resource’s top-level properties as fields, and _raw with the full JSON (ready for spath).

Aliases. These short names resolve to the type on the right; the type name always works too.

AliasTypeNotes
instances, ec2AWS::EC2::InstanceState is an object: spath path=State.Name
vpcs, subnets, security-groups, route-tables, internet-gateways, nat-gatewaysAWS::EC2::VPC, Subnet, SecurityGroup, RouteTable, InternetGateway, NatGateway
volumes, ebsAWS::EC2::Volume
volume-attachments, eip-associations, vpc-gateway-attachmentsAWS::EC2::VolumeAttachment, EIPAssociation, VPCGatewayAttachmentAssociation kinds - the identifier joins the two sides
routesAWS::EC2::RouteNeeds route_table_id=
buckets, s3AWS::S3::BucketAccount-global
bucket-policiesAWS::S3::BucketPolicyOne read per bucket: identifier=<bucket>
functions, lambdasAWS::Lambda::Function
event-source-mappings, esmAWS::Lambda::EventSourceMapping
function-urls, event-invoke-configs, lambda-permissionsAWS::Lambda::Url, EventInvokeConfig, PermissionNeed function_name=
roles, users, policiesAWS::IAM::Role, User, ManagedPolicyAccount-global
customer-managed-policies, customer-policiesAWS::IAM::ManagedPolicyAccount-global; only the policies the account created, without the AWS managed ones policies lists
dynamodb-tables, ddbAWS::DynamoDB::Table
db-instances, rds and db-clusters, auroraAWS::RDS::DBInstance, DBCluster
ecs-clusters and ecs-servicesAWS::ECS::Cluster, ServiceServices need cluster=
eks-clusters, eksAWS::EKS::Cluster
topics, sns and queues, sqsAWS::SNS::Topic, AWS::SQS::Queue
stacks, cfnAWS::CloudFormation::StackStackStatus needs detail=true
load-balancers, albs and listenersAWS::ElasticLoadBalancingV2::LoadBalancer, ListenerListeners need load_balancer_arn=
log-groupsAWS::Logs::LogGroup
rest-apis, http-apis, api-resourcesAWS::ApiGateway::RestApi, AWS::ApiGatewayV2::Api, AWS::ApiGateway::ResourceResources need rest_api_id=
state-machines, sfnAWS::StepFunctions::StateMachine
kms-keys, kmsAWS::KMS::Key
web-acls, wafAWS::WAFv2::WebACLNeeds scope=
glue-databasesAWS::Glue::Database
hosted-zonesAWS::Route53::HostedZoneAccount-global
kinesis-streams, firehoseAWS::Kinesis::Stream, AWS::KinesisFirehose::DeliveryStream
alarmsAWS::CloudWatch::AlarmThe model has no state: use | awsmetrics alarms=true, and alarms=true history=true for what the alarms did
certificates, acmAWS::CertificateManager::CertificateNo status or expiry in the model: the expiry is the DaysToExpiry metric
distributions, cloudfrontAWS::CloudFront::DistributionAccount-global; the list carries only Id, so use detail=true
health-checksAWS::Route53::HealthCheckAccount-global
event-rules, eventbridge-rulesAWS::Events::RuleThe default event bus; the list carries Arn and RuleName, so use detail=true
schedules, schedulerAWS::Scheduler::ScheduleEventBridge Scheduler; the expression and time zone need detail=true
email-identities, sesAWS::SES::EmailIdentityNo verification status in the model
credential-report(synthetic)The account’s IAM credential report: one row per IAM user, the root user first as <root_account>, with the report’s own columns (password_enabled, password_last_used, mfa_active, access_key_1_last_used_date, and the rest) as IAM writes them. Generates the report and waits up to a minute for it; IAM keeps a report four hours. Needs iam:GenerateCredentialReport and iam:GetCredentialReport.
regions(synthetic)The account’s allowed regions, or every region enabled on it via ec2:DescribeRegions; is_default=true marks the default. Feeds the dashboards’ region pickers.

List Lambda functions in a region:

| awsget kind=lambdas region=us-east-1
| table FunctionName Runtime MemorySize Timeout

Stack health (StackStatus is only present with detail=true):

| awsget kind=stacks detail=true region=us-east-1
| stats count by StackStatus

One resource by identifier:

| awsget kind=AWS::SQS::Queue identifier="https://sqs.us-east-1.amazonaws.com/123456789012/payments-events" region=us-east-1

Console users without MFA, from the IAM credential report:

| awsget kind=credential-report
| where password_enabled="true" AND mfa_active="false"
| table user password_last_used

A type the app has no alias for - works because Cloud Control can list it:

| awsget kind=AWS::ECR::Repository region=us-east-1

Inventory across every account:

| awsget kind=instances account=* region=*
| spath path=State.Name output=state
| stats count by account region state

| awslogs - CloudWatch Logs

Use awslogs to read CloudWatch logs from any service - Lambda, API Gateway, ECS and Fargate, RDS, VPC Flow Logs, or your own applications. With query= it runs a Logs Insights query; without it, it tails raw events matched by filter=, newest first.

| awslogs groups=<group|prefix*|"a,b">
          [query=<insights query>] | [filter=<pattern>]
          [start=<time>] [end=<time>] [limit=<int>]
          [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]
ArgumentDescription
groups=Required. One or more log groups, comma-separated (quote the value when listing several). A trailing * is a name-prefix glob - /aws/lambda/payments-* - expanding to at most 200 groups for a raw tail, and at most 50 for a Logs Insights query, which is CloudWatch’s own cap per query. Past 50, a selector made of name prefixes alone (at most 5) is read with the Logs Insights SOURCE command, which reads Standard-class log groups only; the answer then begins with a notice row naming the prefixes an Infrequent Access group was left out under.
query=A Logs Insights query. Billed per GB scanned.
filter=A CloudWatch filter pattern for the raw tail. * or empty matches every event.
start=, end=The window. Default: recent events.
limit=For the raw tail, the total events returned, shared fairly across the selected groups (never below one per group). A group that fills its share is named in a notice row, so a busy group cannot silently crowd the others out. For a Logs Insights query, the most rows returned (default 10000, at most 100000); an answer that reaches it ends with a notice row saying how to narrow the query, also shown as a warning on the search. A query that ends with its own | limit N at or below limit= returns at most N rows and no notice. CloudWatch applies the request’s limit in place of the query’s own, so the command sends the smaller one itself when it can read it: a query with a / outside quotes and comments that is not a regex (after like, after =~, or as the pattern of parse) - a division, say - is sent with limit=.

Key fields: _time, logGroup, logStream, eventId, and _raw (the message). Logs Insights rows carry the query’s own columns.

A group the raw tail could not read - CloudWatch kept throttling the tail, or access to that group was denied - is a notice row naming it and saying why, in place of its events, and the other groups are still shown, without a warning. Once one group gives up on a throttle or on AWS not answering, the groups not started yet are not asked and get the same row. The answer is not cached, so running the search again asks AWS again. When no group could be read, the account and region answer with a _target_error row. FilterLogEvents allows 25 requests a second per account in us-east-1, 10 in most regions and 5 in a few, shared by every search and client reading the account’s logs; the tail reads four groups at a time.

Errors from a function in the last hour:

| awslogs groups=/aws/lambda/payments-api filter="ERROR" start=-1h region=us-east-1

Lambda billed-duration trend from REPORT lines:

| awslogs groups=/aws/lambda/payments-api start=-3h region=us-east-1
    query="filter @type = 'REPORT' | stats avg(@billedDuration) by bin(5m)"

Every function in one prefix:

| awslogs groups=/aws/lambda/payments-* filter="Task timed out" start=-24h region=us-east-1
| where isnull(notice)
| stats count by logGroup

These are counts of the newest events limit= allows each group, not of the whole window. When a group holds more than its share, the command adds a row with a notice field saying so; where isnull(notice) keeps that row out of the count. For a count over the whole window, use a Logs Insights query= instead.


| awsmetrics - CloudWatch metrics

awsmetrics returns a metric time series via GetMetricData - one row per data point - or, with list=true, the metrics CloudWatch has via ListMetrics, which is what fills the Metrics Explorer’s pickers, or, with alarms=true, the alarms and the state each is in via DescribeAlarms, or, with alarms=true history=true, what happened to the alarms over the time range via DescribeAlarmHistory.

| awsmetrics namespace=<ns> metric=<name> [dimensions=<Name=Value,...>]
             [stat=<Average|Sum|Minimum|Maximum|SampleCount|p99>] [period=<seconds>]
             [expression=<expression>] [start=<time>] [end=<time>]
             [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]

| awsmetrics list=true [namespace=<ns>] [metric=<name>] [dimensions=<Name[=Value]>] [recent=<bool>]

| awsmetrics alarms=true [state=<OK|ALARM|INSUFFICIENT_DATA>] [prefix=<alarm name prefix>]

| awsmetrics alarms=true history=true [alarm=<alarm name>] [item=<state|action|configuration>]
             [start=<time>] [end=<time>]
ArgumentDescription
namespace=, metric=The metric to read, e.g. AWS/Lambda and Throttles.
dimensions=Comma list of Name=Value. Quote it when it has more than one pair. A value holding a comma, a double quote or a backslash is written double-quoted with backslash escapes (Name="a,b") - the form list=true returns, so a listing’s dimensions pastes back as it is.
stat=The statistic. Default Average.
period=Datapoint period in seconds.
expression=A CloudWatch expression instead of a single metric. The request carries this one expression and nothing else, so it has to stand on its own: a Metrics Insights query (SELECT ... FROM SCHEMA(...)) or a SEARCH() expression. Math over other series, such as errors/invocations*100, has no series to refer to - compute a ratio in SPL from two searches instead. A SEARCH() expression finds only metrics that reported data within the past two weeks, and a Metrics Insights query reads at most the last two weeks; for a metric quiet longer than that, or an older stretch, name the metric with namespace=, metric=, and dimensions=. Inside a Metrics Insights query, double-quote a metric or dimension name that starts with a digit or is a reserved word - SUM(\"4XXError\"), SUM(\"Count\") - or CloudWatch refuses the query. A Metrics Insights query returns 500 series at most: one that matches more comes back with CloudWatch’s MaxQueryResultsExceeded message in the notice row, and ORDER BY with the function to rank by (ORDER BY SUM() DESC keeps the largest, and CloudWatch then starts each label with its rank) chooses which series those are.
alarms=trueAlarm mode: one row per metric, composite and log alarm with alarm, type (metric, composite, or log), state, reason, _time (when the state was last updated), state_transitioned (when it last changed), and the alarm’s settings - arn, description, actions_enabled, alarm_actions; for a metric alarm threshold (or threshold_metric_id, the band an anomaly-detection alarm compares against), comparison, period, evaluation_periods, datapoints_to_alarm and treat_missing_data, with namespace, metric, dimensions (the form dimensions= accepts, so the alarm’s metric charts from its own row) and statistic for a single metric, or expression, metrics and metric_queries for metric math and anomaly detection; for a composite alarm its rule; for a log alarm (a scheduled Logs Insights query) its query, log_groups, aggregation, schedule, threshold, comparison, evaluation_periods and datapoints_to_alarm - counted in query results, not metric periods - and treat_missing_data. state= keeps one state and prefix= the alarms whose name starts with it. This is the source of an alarm’s state: | awsget kind=alarms lists alarms through Cloud Control, whose model has none. An alarm listing caches for one minute whatever the default cache freshness.
history=trueWith alarms=true, alarm history: what CloudWatch recorded about the alarms over start= and end=, or without them the search’s time range, or the last 24 hours when the search has none, metric, composite and log alarms, newest first within each account and region. One row per item with alarm, type, item (state, action, or configuration), summary, _time, and _raw (the item’s JSON, for spath): for a state change old_state, new_state, and reason; for an action action_state (Succeeded when it ran), action (the SNS topic or other target), and error (why it did not run); for a configuration change change (Create, Update, or Delete), and for a deletion state, the state the alarm was deleted in. A multi-time-series alarm’s per-contributor rows (item of contributor_state or contributor_action) add contributor and contributor_attributes, and come back only without item=. alarm= reads one alarm and item= one kind of row (state, action, or configuration); state= and prefix= are refused here. An alarm that fired and recovered between two looks at the alarm list shows only here, and so does a notification that failed to send. CloudWatch keeps 30 days, a deleted alarm’s too, and a history past 100 pages (10,000 items) ends with a notice row.
list=trueListing mode: one row per metric with namespace, metric, dimensions (in the exact form dimensions= accepts back), and dimension_names. namespace=, metric=, and dimensions= become optional filters; a bare dimension name matches any value. recent=true keeps only metrics active in the last three hours (off by default, because S3 storage metrics publish once a day). Even without it, the listing has only metrics that reported data in the past two weeks. A listing past 40 pages stops with a notice row. list=true and alarms=true are two modes, and a search naming both is refused.

Key fields of a time series: _time, value, label (CloudWatch’s name for the series - the metric name, or the group of a Metrics Insights GROUP BY), and id. The rows do not repeat the namespace, metric, or dimensions you asked for; add them with eval when you combine searches. A listing returns namespace, metric, dimensions, and dimension_names instead. An answer CloudWatch marked incomplete (PartialData, InternalError, or Forbidden), or returned with messages, ends with a row whose notice names each incomplete status, how many series it hit, and the messages; the data rows are unchanged, and the notice is also a warning on the search. Keep it out of a count with where isnull(notice). An expression asking for more time buckets than one request answers - series times window over period, whether a bucket holds a point or not: 100,800 for a SEARCH(), 504,000 for a Metrics Insights query - comes back marked incomplete this way, every series missing the oldest part of the window; ask for fewer with a larger period=, a shorter start=, or a narrower expression.

Lambda throttles over the last three hours:

| awsmetrics namespace=AWS/Lambda metric=Throttles dimensions=FunctionName=payments-api stat=Sum period=300 start=-3h region=us-east-1

Throttles for every function at once - a Metrics Insights query, with the inner quotes escaped:

| awsmetrics expression="SELECT SUM(Throttles) FROM SCHEMA(\"AWS/Lambda\", FunctionName) GROUP BY FunctionName" period=300 start=-3h region=us-east-1
| stats sum(value) as throttles by label
| where throttles>0

S3 bucket size - two dimensions, so the value is quoted:

| awsmetrics namespace=AWS/S3 metric=BucketSizeBytes dimensions="BucketName=payments-archive,StorageType=StandardStorage" stat=Average period=86400 start=-7d region=us-east-1

Every alarm that is firing, the most recently fired first:

| awsmetrics alarms=true state=ALARM region=us-east-1
| sort - state_transitioned
| table alarm type reason namespace metric dimensions

Alarms that fired over the last week, the most often first - OK again or not:

| awsmetrics alarms=true history=true item=state start=-7d region=us-east-1
| where new_state="ALARM"
| stats count as fired latest(_time) as last_fired by account region alarm
| sort 0 -fired

Alarm notifications that failed to send, and why:

| awsmetrics alarms=true history=true item=action start=-7d region=us-east-1
| where action_state!="Succeeded"
| table _time alarm action error

Which functions publish Invocations - the dimension tuples a picker offers:

| awsmetrics list=true namespace=AWS/Lambda metric=Invocations region=us-east-1

| awscloudtrail - management events

awscloudtrail looks up recent CloudTrail management events with LookupEvents - who did what, when, from where. CloudTrail keeps 90 days of management events for this API.

| awscloudtrail [attribute=<name>] [value=<value>] [start=<time>] [end=<time>] [limit=<int>]
                [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]
ArgumentDescription
attribute=One of EventName, EventSource, Username, ResourceName, ResourceType, EventId, AccessKeyId, ReadOnly.
value=The value to match for that attribute.
start=, end=The window; start=-24h for the last day.
limit=Cap the events returned per account and region. A lookup that limit= stops before the start of its window ends with a notice row saying how far back it read, with no warning: the newest events are what a capped lookup is for.

Key fields: _time, event_name, event_source, username, event_id, access_key_id, read_only, and _raw with the full event JSON - spath it for the rest (sourceIPAddress, userAgent, requestParameters). CloudTrail answers 50 events a page at two requests a second per account and region, and the searches on a search head share that rate, so a lookup that reads N events takes at least N/100 seconds; narrow it with attribute= before raising limit=. Global services such as IAM record their events in us-east-1. STS is regional unless the caller used its global endpoint: a call to sts.<region>.amazonaws.com is recorded in that region, and this app’s own AssumeRole calls are made that way.

Who launched instances in the last day:

| awscloudtrail attribute=EventName value=RunInstances start=-24h region=us-east-1
| spath path=sourceIPAddress output=source_ip
| spath path=userAgent output=user_agent
| table _time username source_ip user_agent

Everything one user did this week:

| awscloudtrail attribute=Username value=alice start=-7d region=us-east-1
| stats count by event_source event_name

| awscost - Cost Explorer

awscost returns cost and usage from Cost Explorer. Every request is billed by AWS (about $0.01), so the command runs once per account whatever region= says, its results are cached for an hour, and the Cost dashboards wait for a Submit.

| awscost [start=<time>] [end=<time>] [granularity=<DAILY|MONTHLY|HOURLY>]
          [metrics=<UnblendedCost,...>] [group_by=<DIMENSION|TAG:key>]
          [filter=<KEY=VALUES,...>]
          [account=<name|*>] [concurrency=<int>] [cache=<duration>]
ArgumentDescription
start=, end=The window: a date, epoch seconds, or relative time. Without them, the search’s time-range picker; without that, the last 30 days. Bounds widen to whole UTC days, Cost Explorer’s granularity.
granularity=MONTHLY (default), DAILY, or HOURLY where enabled.
metrics=Comma list such as "UnblendedCost,UsageQuantity" - quote it. Default UnblendedCost.
group_by=A dimension (SERVICE, REGION, LINKED_ACCOUNT, USAGE_TYPE) or TAG:<key>.
filter=Narrows the lines summed: comma-separated clauses that must all hold, each KEY=VALUES or KEY!=VALUES with |-separated alternatives, KEY a dimension or TAG:<key>. Quote it. filter="RECORD_TYPE!=Credit|Refund" shows usage before credits and refunds, which otherwise net each service toward zero on an account living on credits. Empty or * applies none.

Key fields: _time and start (the period start), end, account, group (the dimension or tag value), and one field per metric with its amount (UnblendedCost) and its unit (UnblendedCost_unit).

Daily cost by service for a month:

| awscost granularity=DAILY group_by=SERVICE start=2026-08-01 end=2026-09-01 account=prod
| timechart span=1d sum(UnblendedCost) by group

Month over month by team tag:

| awscost granularity=MONTHLY group_by=TAG:team start=-2mon@mon account=*

Six months of one project’s spend by service, before credits and refunds:

| awscost granularity=MONTHLY group_by=SERVICE filter="RECORD_TYPE!=Credit|Refund,TAG:project=web" start=-5mon@mon

| awsathena - Athena queries

awsathena starts an Athena query, waits for it (cancelling your search cancels the query), and streams the result rows keyed by column name. AWS bills per TB scanned.

| awsathena query=<sql> [database=<name>] [workgroup=<name>] [output_location=<s3://...>]
            [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]
ArgumentDescription
query=Required. The SQL to run. Quote it.
database=The Glue database the query runs against.
workgroup=The Athena workgroup to run in. Default primary.
output_location=The S3 location (s3://bucket/prefix/) Athena writes the result to, needed unless the workgroup sets a query result location. A workgroup that enforces its settings uses its own location whatever this says.

Top CloudTrail event names from an Athena table:

| awsathena database=cloudtrail_logs query="SELECT eventname, count(*) AS c FROM cloudtrail GROUP BY 1 ORDER BY c DESC LIMIT 20" region=us-east-1

A query in a workgroup that supplies the result location:

| awsathena workgroup=analytics database=data query="SELECT dt, count(*) AS n FROM requests WHERE dt >= '2026-10-01' GROUP BY dt" region=us-east-1

| awsglue - the Glue Data Catalog

awsglue browses the Data Catalog that Athena queries: databases, tables, and partitions.

| awsglue resource=<databases|tables|partitions> [database=<name>] [table=<name>]
          [expression=<partition filter>] [catalog_id=<id>]
          [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]

tables requires database=; partitions requires database= and table=. Key fields depend on the resource: a database row has database, description, location_uri, and catalog_id; a table row has table, database, table_type, owner, and description; a partition row has database, table, and the partition values. Every row carries _raw with the catalog object’s full JSON, which is where a table’s storage location lives:

| awsglue resource=tables database=cloudtrail_logs region=us-east-1
| spath path=StorageDescriptor.Location output=location
| table table table_type location

| awstag - resources by tag

awstag finds resources by tag through the Resource Groups Tagging API. Each row carries the resource’s arn, and each tag is flattened into a tag_<key> field. That API knows a resource only if it carries a tag now or did once: a resource that was never tagged is not in its answer at all, so awstag is the right tool for “what carries this tag” and the wrong one for “what has no tags”. For that, list the kind with | awsget and read its Tags.

| awstag [tag=<Key=Value,...>] [resource_type=<service:type>]
         [account=<name|*>] [region=<id|*>] [concurrency=<int>] [cache=<duration>]
ArgumentDescription
tag=Comma list of Key=Value filters. Quote it when it has more than one pair: tag="env=prod,team=payments".
resource_type=Narrow by ARN service type, e.g. ec2:instance.
| awstag tag=env=prod region=us-east-1
| rex field=arn "arn:aws:(?<service>[^:]+):"
| stats count by service

| awsjson - format JSON for reading

| awsget, | awscloudtrail, and | awsglue return the whole JSON document of each row in _raw. Pipe through awsjson to read it as YAML, or as indented JSON with format=json. The other commands have nothing for it by default: | awslogs puts the log message in _raw, which is JSON only when the application logged JSON, and | awsmetrics, | awscost, | awstag, and | awsathena return fields and no _raw. Point field= at any field that holds JSON. This is a streaming command - it transforms rows, it fetches nothing.

... | awsjson [field=<field>] [output=<field>] [format=<yaml|json>]
ArgumentDescription
field=Input field holding the JSON (default _raw).
output=Field to write the result to (default: rewrite the input field in place).
format=yaml (default) or json.

Rows whose field is empty or is not JSON pass through untouched.

| awsget kind=roles account=prod
| awsjson field=AssumeRolePolicyDocument output=trust format=json
| table RoleName trust

Recipes

Instances not running, with their type and launch time:

| awsget kind=instances account=* region=*
| spath path=State.Name output=state
| where state!="running"
| table account region InstanceId InstanceType state LaunchTime

Unattached EBS volumes (two listings joined on the association kind; volumes list by identifier only, so detail=true brings the size and type):

| awsget kind=volumes detail=true region=us-east-1
| join type=left identifier
    [ | awsget kind=volume-attachments region=us-east-1
      | rex field=identifier "^(?<identifier>vol-[^|]+)\|(?<instance>i-.+)$"
      | table identifier instance ]
| where isnull(instance)
| table identifier Size VolumeType

Correlate a CloudTrail change with the resource as it is now:

| awscloudtrail attribute=EventName value=ModifySecurityGroupRules start=-24h region=us-east-1
| table _time username _raw
| awsget kind=security-groups identifier=sg-0a1b2c3d detail=true region=us-east-1
| awsjson

History from CloudTrail, current state from Cloud Control - in the same session, without leaving Splunk for the console.