CloudWatch Logs is the default logging destination for AWS services: Lambda, ECS, API Gateway, VPC Flow Logs, and custom applications all write there. Over time, log groups accumulate. Some grow to hundreds of gigabytes because a service logs verbosely, a retention policy was never set, or a debug log level was left enabled in production.
The AWS console shows log group size, but checking dozens of groups manually across regions does not scale. This article builds a Bash automation that discovers all CloudWatch Log Groups in a region, reports their stored bytes and retention settings, flags unusually large groups, and produces a shareable report—without deleting anything.
What We Are Going to Build
AWS Region
↓
aws logs describe-log-groups (paginated)
↓
Parse: logGroupName, storedBytes, retentionInDays, arn
↓
Calculate size in human-readable units
↓
Flag groups exceeding size threshold
↓
Flag groups with no retention policy
↓
Output: formatted table + JSON + CSV
↓
Save report to file
The script is read-only by design. It identifies candidates for cleanup; it does not perform cleanup.
What You Need
- Operating system: Linux, macOS, or WSL on Windows
- Bash: 4.0 or later
- AWS CLI: v2.x (v1 works but v2 is recommended)
- jq: for JSON parsing
- AWS account with CloudWatch Logs read permissions
- Configured credentials (see Authentication section)
Verify your setup:
aws --version
# Example output: aws-cli/2.15.3 Python/3.11.6 Linux/6.17.0-1019-oracle exe/x86_64.ubuntu.24
aws sts get-caller-identity
# Should return your account ID, user ARN, and user ID
If aws sts get-caller-identity fails, configure credentials first (next section).
Authentication
Use one of these secure methods. never hard-code credentials in scripts.
Option 1: AWS CLI configured profile (recommended for local development)
aws configure
# Enter Access Key ID, Secret Access Key, default region, output format (json)
Option 2: Environment variables (CI/CD, containers)
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_DEFAULT_REGION=us-east-1
Option 3: IAM role (EC2, ECS, Lambda, CodeBuild) — attach an instance profile or task role with the required permissions. No credentials needed in the environment.
Option 4: AWS SSO (organizational accounts)
aws configure sso
# Follow the interactive flow
Verify authentication works:
aws logs describe-log-groups --limit 1
# Should return JSON with at least one log group or empty list
Permissions
The script only reads. Attach this minimal policy to the user or role running it:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CloudWatchLogsReadOnly",
"Effect": "Allow",
"Action": [
"logs:DescribeLogGroups"
],
"Resource": "*"
}
]
}
This grants logs:DescribeLogGroups — list log groups with metadata including storedBytes and retentionInDays.
It does not grant:
logs:DeleteLogGrouplogs:PutRetentionPolicylogs:FilterLogEvents(reads log content)- Any write or delete permissions
The Complete Automation Script
Save as find-large-log-groups.sh:
#!/usr/bin/env bash
#
# find-large-log-groups.sh
# Discover CloudWatch Log Groups, report stored bytes and retention,
# flag large groups and groups without retention policies.
#
# Usage: ./find-large-log-groups.sh [--region REGION] [--threshold-mb THRESHOLD] [--output-dir DIR]
#
# Requirements: aws-cli v2, jq
# Permissions: logs:DescribeLogGroups
set -euo pipefail
# ---------- Configuration (can be overridden by flags) ----------
DEFAULT_REGION="${AWS_DEFAULT_REGION:-us-east-1}"
DEFAULT_THRESHOLD_MB=100 # Flag groups larger than this (megabytes)
DEFAULT_OUTPUT_DIR="./reports"
# ----------------------------------------------------------------
REGION="$DEFAULT_REGION"
THRESHOLD_MB="$DEFAULT_THRESHOLD_MB"
OUTPUT_DIR="$DEFAULT_OUTPUT_DIR"
# Colors for terminal output
RED=' 33[0;31m'
YELLOW=' 33[1;33m'
GREEN=' 33[0;32m'
BLUE=' 33[0;34m'
NC=' 33[0m' # No Color
# ---------- Helper Functions ----------
usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS]
Discover CloudWatch Log Groups and report on size and retention.
OPTIONS:
-r, --region REGION AWS region (default: $DEFAULT_REGION or AWS_DEFAULT_REGION)
-t, --threshold-mb N Size threshold in MB to flag as "large" (default: $DEFAULT_THRESHOLD_MB)
-o, --output-dir DIR Directory for report files (default: $DEFAULT_OUTPUT_DIR)
-h, --help Show this help
EXAMPLES:
$(basename "$0")
$(basename "$0") --region eu-west-1 --threshold-mb 500
$(basename "$0") -r us-west-2 -t 50 -o /tmp/log-reports
EOF
}
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
bytes_to_human() {
local bytes="$1"
if [[ $bytes -ge 1099511627776 ]]; then
printf "%.2f TB" "$(echo "scale=2; $bytes/1099511627776" | bc)"
elif [[ $bytes -ge 1073741824 ]]; then
printf "%.2f GB" "$(echo "scale=2; $bytes/1073741824" | bc)"
elif [[ $bytes -ge 1048576 ]]; then
printf "%.2f MB" "$(echo "scale=2; $bytes/1048576" | bc)"
elif [[ $bytes -ge 1024 ]]; then
printf "%.2f KB" "$(echo "scale=2; $bytes/1024" | bc)"
else
printf "%d B" "$bytes"
fi
}
check_dependencies() {
local missing=()
command -v aws >/dev/null 2>&1 || missing+=("aws-cli")
command -v jq >/dev/null 2>&1 || missing+=("jq")
command -v bc >/dev/null 2>&1 || missing+=("bc")
if [[ ${#missing[@]} -gt 0 ]]; then
log_error "Missing required commands: ${missing[*]}"
log_error "Install them and try again."
exit 1
fi
}
# ---------- Main Logic ----------
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-r|--region)
REGION="$2"
shift 2
;;
-t|--threshold-mb)
THRESHOLD_MB="$2"
shift 2
;;
-o|--output-dir)
OUTPUT_DIR="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
log_error "Unknown option: $1"
usage
exit 1
;;
esac
done
# Validate threshold is numeric
if ! [[ "$THRESHOLD_MB" =~ ^[0-9]+$ ]]; then
log_error "Threshold must be a positive integer (MB)"
exit 1
fi
}
fetch_log_groups() {
log_info "Fetching CloudWatch Log Groups in region: $REGION"
# Use paginated describe-log-groups, output JSON
aws logs describe-log-groups
--region "$REGION"
--output json
--no-paginate
--query 'logGroups[*].{name:logGroupName,storedBytes:storedBytes,retentionInDays:retentionInDays,arn:arn,creationTime:creationTime}'
}
process_log_groups() {
local raw_json="$1"
local threshold_bytes=$((THRESHOLD_MB * 1024 * 1024))
local timestamp
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local report_date
report_date=$(date -u +"%Y%m%d-%H%M%S")
# Ensure output directory exists
mkdir -p "$OUTPUT_DIR"
local report_file="${OUTPUT_DIR}/cloudwatch-log-groups-${REGION}-${report_date}"
local json_file="${report_file}.json"
local csv_file="${report_file}.csv"
local txt_file="${report_file}.txt"
# Save raw JSON for programmatic use
echo "$raw_json" > "$json_file"
log_success "Raw JSON saved to: $json_file"
# Process with jq: human-readable size, large flag, retention flag
local processed
processed=$(echo "$raw_json" | jq -r --argjson threshold "$threshold_bytes" --arg region "$REGION" --arg timestamp "$timestamp" '
map(
.storedBytesHuman = (
if .storedBytes >= 1099511627776 then "(.storedBytes/1099511627776 | tostring) TB"
elif .storedBytes >= 1073741824 then "(.storedBytes/1073741824 | tostring) GB"
elif .storedBytes >= 1048576 then "(.storedBytes/1048576 | tostring) MB"
elif .storedBytes >= 1024 then "(.storedBytes/1024 | tostring) KB"
else "(.storedBytes) B"
end
)
| .isLarge = (.storedBytes >= $threshold)
| .hasRetention = (.retentionInDays != null and .retentionInDays > 0)
| .retentionDisplay = (if .hasRetention then "(.retentionInDays) days" else "NEVER EXPIRES" end)
| .region = $region
| .reportTimestamp = $timestamp
)
')
# Generate CSV
echo "$processed" | jq -r '
["Log Group Name","Stored Bytes","Size (Human)","Retention (Days)","Retention Display","Is Large","Has Retention","ARN","Region","Report Timestamp"],
(.[] | [.name, (.storedBytes|tostring), .storedBytesHuman, (.retentionInDays|tostring), .retentionDisplay, (.isLarge|tostring), (.hasRetention|tostring), .arn, .region, .reportTimestamp])
| @csv
' > "$csv_file"
log_success "CSV report saved to: $csv_file"
# Generate human-readable text report
{
echo "CloudWatch Log Groups Report"
echo "============================="
echo "Region: $REGION"
echo "Generated: $timestamp"
echo "Size threshold for 'large' flag: ${THRESHOLD_MB} MB"
echo ""
printf "%-60s %12s %15s %20s %10sn" "LOG GROUP NAME" "STORED BYTES" "SIZE (HUMAN)" "RETENTION" "FLAGS"
printf "%-60s %12s %15s %20s %10sn" "$(printf '%0.s-' {1..60})" "$(printf '%0.s-' {1..12})" "$(printf '%0.s-' {1..15})" "$(printf '%0.s-' {1..20})" "$(printf '%0.s-' {1..10})"
echo "$processed" | jq -r '
sort_by(.storedBytes) | reverse | .[] |
[.name, .storedBytes, .storedBytesHuman, .retentionDisplay,
(if .isLarge then "LARGE" else "" end), (if (.hasRetention | not) then "NO_RETENTION" else "" end)]
| @tsv
' | awk -F 't' '{ printf "%-60s %12s %15s %20s %10sn", $1, $2, $3, $4, ($5 " " $6) }'
echo ""
echo "Summary:"
local total_groups
total_groups=$(echo "$processed" | jq 'length')
local large_groups
large_groups=$(echo "$processed" | jq '[.[] | select(.isLarge)] | length')
local no_retention_groups
no_retention_groups=$(echo "$processed" | jq '[.[] | select(.hasRetention | not)] | length')
local total_bytes
total_bytes=$(echo "$processed" | jq '[.[] | .storedBytes] | add // 0')
echo " Total log groups: $total_groups"
echo " Large groups (>${THRESHOLD_MB} MB): $large_groups"
echo " Groups without retention policy: $no_retention_groups"
echo " Total stored bytes: $total_bytes ($(bytes_to_human "$total_bytes"))"
if [[ $large_groups -gt 0 ]]; then
echo ""
echo "Large log groups (candidates for review):"
echo "$processed" | jq -r '.[] | select(.isLarge) | " - (.name): (.storedBytesHuman) ((.retentionDisplay))"'
fi
if [[ $no_retention_groups -gt 0 ]]; then
echo ""
echo "Log groups without retention policy (logs never expire):"
echo "$processed" | jq -r '.[] | select(.hasRetention | not) | " - (.name): (.storedBytesHuman)"'
fi
} > "$txt_file"
# Also print summary to stdout
cat "$txt_file"
log_success "Text report saved to: $txt_file"
}
main() {
parse_args "$@"
check_dependencies
log_info "Starting CloudWatch Log Groups analysis"
log_info "Region: $REGION | Threshold: ${THRESHOLD_MB} MB | Output: $OUTPUT_DIR"
local raw_json
raw_json=$(fetch_log_groups)
if [[ -z "$raw_json" || "$raw_json" == "null" ]]; then
log_warn "No log groups found in region $REGION"
exit 0
fi
process_log_groups "$raw_json"
log_success "Analysis complete"
}
main "$@"
Script Breakdown
1. Configuration and Defaults
DEFAULT_REGION="${AWS_DEFAULT_REGION:-us-east-1}"
DEFAULT_THRESHOLD_MB=100
DEFAULT_OUTPUT_DIR="./reports"
Region defaults to the AWS_DEFAULT_REGION environment variable or us-east-1. The threshold defaults to 100 MB—groups larger than this are flagged as large. The output directory defaults to ./reports in the current working directory.
2. Dependency Check
check_dependencies() {
local missing=()
command -v aws >/dev/null 2>&1 || missing+=("aws-cli")
command -v jq >/dev/null 2>&1 || missing+=("jq")
command -v bc >/dev/null 2>&1 || missing+=("bc")
...
}
Verifies that aws, jq, and bc are installed. jq parses JSON; bc handles floating-point math for human-readable sizes.
3. Fetching Log Groups
aws logs describe-log-groups
--region "$REGION"
--output json
--no-paginate
--query 'logGroups[*].{name:logGroupName,storedBytes:storedBytes,retentionInDays:retentionInDays,arn:arn,creationTime:creationTime}'
--no-paginate returns all results in one call (up to the CLI limit). The --query flag projects only the fields we need, reducing output size. The command returns an array of objects with name, storedBytes, retentionInDays, arn, and creationTime.
What could go wrong: a region that does not exist returns InvalidParameterException; insufficient permissions return AccessDeniedException; API throttling returns ThrottlingException (the AWS CLI retries automatically).
4. Processing with jq
The script converts raw bytes to human-readable units, flags groups exceeding the threshold as isLarge, detects missing retention (retentionInDays is null or 0 means logs never expire), and adds region and report timestamp metadata.
5. Output Formats
- JSON — raw processed data for downstream automation
- CSV — spreadsheet-compatible, includes all fields
- Text — human-readable table with summary statistics
The text report includes a formatted table sorted by size (largest first), summary counts, total stored bytes, and explicit lists of flagged groups.
Dry Run / Testing
This script is read-only, no dry-run flag is needed. To test safely:
- Run against a non-production region (or a test account):
./find-large-log-groups.sh --region us-west-2 - Use a high threshold to see only the largest groups:
./find-large-log-groups.sh --threshold-mb 1000 - Inspect the JSON output programmatically:
cat reports/cloudwatch-log-groups-*.json | jq '.[] | select(.isLarge)' - Verify the CSV opens correctly in a spreadsheet application
Run the Script
chmod +x find-large-log-groups.sh
./find-large-log-groups.sh
Example output (illustrative):
[INFO] Starting CloudWatch Log Groups analysis
[INFO] Region: us-east-1 | Threshold: 100 MB | Output: ./reports
[INFO] Fetching CloudWatch Log Groups in region: us-east-1
[OK] Raw JSON saved to: ./reports/cloudwatch-log-groups-us-east-1-20260818-143022.json
[OK] CSV report saved to: ./reports/cloudwatch-log-groups-us-east-1-20260818-143022.csv
[OK] Text report saved to: ./reports/cloudwatch-log-groups-us-east-1-20260818-143022.txt
CloudWatch Log Groups Report
=============================
Region: us-east-1
Generated: 2026-08-18T14:30:22Z
Size threshold for 'large' flag: 100 MB
LOG GROUP NAME STORED BYTES SIZE (HUMAN) RETENTION FLAGS
------------------------------------------------------------ ------------ --------------- -------------------- ----------
/aws/lambda/production-api 2147483648 2.00 GB 30 days
/aws/ecs/my-service 1073741824 1.00 GB NEVER EXPIRES LARGE NO_RETENTION
/vpc/flowlogs/prod-vpc 536870912 512.00 MB 7 days LARGE
/aws/apigateway/prod 104857600 100.00 MB 30 days LARGE
/aws/lambda/dev-function 52428800 50.00 MB 14 days
Summary:
Total log groups: 12
Large groups (>100 MB): 3
Groups without retention policy: 1
Total stored bytes: 3984588800 (3.71 GB)
Large log groups (candidates for review):
- /aws/lambda/production-api: 2.00 GB (30 days)
- /aws/ecs/my-service: 1.00 GB (NEVER EXPIRES)
- /vpc/flowlogs/prod-vpc: 512.00 MB (7 days)
Log groups without retention policy (logs never expire):
- /aws/ecs/my-service: 1.00 GB
Automate the Automation
Option 1: cron (simple, server-based)
# Run daily at 6 AM UTC, save reports to /var/log/cloudwatch-reports
0 6 * * * /opt/scripts/find-large-log-groups.sh --region us-east-1 --output-dir /var/log/cloudwatch-reports >> /var/log/cloudwatch-reports/cron.log 2>&1
Option 2: AWS EventBridge + Lambda (serverless)
- Create a Lambda function with the script logic (Python preferred for Lambda)
- Create an EventBridge rule with schedule expression
cron(0 6 * * ? *) - Grant Lambda
logs:DescribeLogGroupspermission - Store reports in S3 with a lifecycle policy
Option 3: GitHub Actions (CI/CD scheduling)
# .github/workflows/cloudwatch-log-audit.yml
name: CloudWatch Log Groups Audit
on:
schedule:
- cron: '0 6 * * *' # Daily 6 AM UTC
workflow_dispatch: # Manual trigger
jobs:
audit:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: us-east-1
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsCloudWatchRead
- name: Run audit script
run: |
sudo apt-get update && sudo apt-get install -y jq bc
./find-large-log-groups.sh --region us-east-1
- name: Upload report artifact
uses: actions/upload-artifact@v4
with:
name: cloudwatch-log-report
path: reports/
Scheduling Example
Daily at 6 AM UTC (adjust for your timezone):
0 6 * * * /opt/scripts/find-large-log-groups.sh --region us-east-1 --output-dir /var/log/cloudwatch-reports
0— minute 06— hour 6 (6 AM UTC)*— every day of month*— every month*— every day of week
For US Eastern Time, use 0 1 * * * (1 AM EST) or 0 2 * * * (2 AM EDT). This schedule is an example, adapt it to your environment and time zone.
Logging
The script produces three report artifacts per run:
| File | Purpose |
|---|---|
cloudwatch-log-groups- |
Machine-readable, full data for downstream processing |
cloudwatch-log-groups- |
Spreadsheet analysis, sharing with stakeholders |
cloudwatch-log-groups- |
Human-readable summary, suitable for email/Slack |
stdout: human-readable summary (the text report). stderr: only errors (dependency missing, AWS CLI failures).
Exit codes: 0 for success; 1 for missing dependency, invalid argument, or AWS CLI failure.
For production, consider shipping JSON logs to CloudWatch Logs or a SIEM, sending a summary via SNS when findings exceed a threshold, and retaining reports in S3 with a lifecycle policy.
Error Handling
| Scenario | Behavior |
|---|---|
aws not installed |
Exits with a clear message listing missing commands |
jq not installed |
Same as above |
| Invalid region | AWS CLI returns InvalidParameterException, script exits non-zero |
| No credentials | AWS CLI returns Unable to locate credentials, script exits non-zero |
| Insufficient permissions | AWS CLI returns AccessDeniedException, script exits non-zero |
| Throttling | AWS CLI retries automatically |
| Empty log group list | Warns and exits 0 (not an error) |
| Network timeout | AWS CLI times out, script exits non-zero |
set -euo pipefail at the top ensures: -e exits on any command failure; -u treats unset variables as errors; -o pipefail fails a pipeline if any command fails.
Production Considerations
| Concern | Current Script | Production Improvement |
|---|---|---|
| Multi-region | Single region per run | Loop over regions or use CloudWatch cross-account observability |
| Cross-account | Not supported | Assume role into member accounts from a management account |
| Rate limits | Single describe call | Add –limit and pagination handling for 1000+ groups |
| Large output | Loads all JSON in memory | Stream process with jq –stream for 10,000+ groups |
| Notifications | None | Add SNS/email/webhook when thresholds exceeded |
| Persistent storage | Local filesystem | Write to S3 with versioning and lifecycle |
| Historical comparison | Single snapshot | Store in a time-series database or append to CSV |
| Authentication | Ambient credentials | Explicit role assumption with aws sts assume-role |
| Concurrency | Not safe for parallel runs | Add flock or S3-based locking |
Common Mistakes
- Running in the wrong region—log groups are regional;
us-east-1will not showeu-west-1groups - Forgetting pagination—
describe-log-groupsreturns max 50 by default;--no-paginatehandles this but hits CLI limits around 1000 groups - Using administrator credentials—only
logs:DescribeLogGroupsis needed - Hard-coding thresholds—make them configurable; 100 MB may be too low for some workloads
- Ignoring groups with no retention policy—these are the highest risk for unbounded growth
- Not checking VPC Flow Logs—
/vpc/flowlogs/groups often grow largest and are frequently overlooked - Running only in one region for multi-region deployments
- Deleting logs based on this report—the script only identifies candidates; verify before any deletion
- Scheduling in the wrong time zone—cron runs in system time; use UTC or set TZ explicitly
- Not verifying output—always review the first few reports manually before trusting automation
How to Improve the Script
Beginner
Single region, local file output, fixed threshold, basic table output, the script as presented.
Intermediate
- Multi-region support (loop over
aws ec2 describe-regions) - Cross-account role assumption
- S3 upload with presigned URLs for sharing
- SNS/email notification on findings
- Configuration file (YAML/JSON) instead of flags
- Historical comparison (diff against last run)
Production-oriented
- Lambda + EventBridge deployment (serverless, no servers)
- CloudWatch Dashboard widget showing the top 10 largest groups
- Integration with AWS Config for compliance tracking
- Automated retention policy recommendation (not enforcement)
- Cost estimation: stored bytes x $0.03/GB/month plus ingestion costs
- Slack/Teams webhook with formatted message blocks
- Terraform module to deploy the entire monitoring stack
Alternatives
Native AWS: CloudWatch Logs Insights — you can query log group sizes with stats sum(bytes) by logGroupName, but queries must be run manually or via scheduled queries (which write to S3). There is no built-in alert for when a log group exceeds a size.
Native AWS: CloudWatch Metrics + Alarms — the IncomingLogEvents and IncomingBytes metrics exist per log group, so you can alarm on ingestion rate. But this measures ingestion, not stored volume.
Native AWS: S3 export + lifecycle — export logs to S3 and use lifecycle policies to move them to Glacier. This does not reduce CloudWatch Logs storage charges (you pay for both).
Third-party: Datadog, New Relic, Splunk — rich dashboards and alerting, but significant cost; overkill if you only need log group size awareness.
This script fills a gap: there is no single CLI command that shows all log groups sorted by stored size with retention status. Use it as a scheduled audit, and consider native alarms on ingestion rate for real-time alerting.
DevStackIO Tools
The following free utilities from tools.devstackio.com can help with this workflow:
- Cron Expression Generator — build and test the cron schedule for your environment
- JSON Formatter — pretty-print the raw JSON output for inspection
- CSV to JSON Converter — convert the report CSV to JSON for downstream processing
- Timestamp Converter — convert the creationTime epoch milliseconds to readable dates
- Base64 Encoder/Decoder — if you need to embed reports in API calls
Related DevStackIO Articles
- Automate AWS EC2 Backups with Bash and AWS CLI — snapshot companion
- Find and Delete Unused AWS EBS Volumes Automatically — same read-first, report-then-act pattern for block storage
- Automate AWS S3 Lifecycle Management with AWS CLI — native lifecycle vs. custom scripts for object storage (coming soon)
- Build a Cloud Resource Audit Script with AWS CLI — broader inventory including EC2, EBS, S3, IAM (coming soon)
Conclusion
CloudWatch Log Groups grow silently. A Lambda function left on debug logging, a VPC Flow Log capturing all traffic, an ECS service without a retention policy. each can accumulate gigabytes per day. The AWS console does not make this obvious across regions.
This script gives you a repeatable, auditable way to answer: which log groups are large, which have no expiration, and what is the total stored volume.
The most important safety consideration: this script only reports. It never deletes. Treat every LARGE or NO_RETENTION flag as a starting point for investigation, not an automatic cleanup trigger.
The next logical improvement: deploy this as a Lambda function triggered by EventBridge, writing reports to S3 and sending an SNS notification when new large groups appear. That moves the audit from run-when-I-remember to continuous visibility.
Sources / References
- AWS CLI describe-log-groups Reference — docs.aws.amazon.com/cli/latest/reference/logs/describe-log-groups.html
- CloudWatch Logs Pricing — aws.amazon.com/cloudwatch/pricing/
- CloudWatch Logs Retention Policies — docs.aws.amazon.com/AmazonCloudWatch/latest/logs/RetentionPolicy.html
- AWS CLI JMESPath Query Tutorial — docs.aws.amazon.com/cli/latest/userguide/cli-usage-filter.html
- jq Manual — jqlang.github.io/jq/manual/
Related DevStackIO Resources
- Automatically Stop and Start AWS EC2 Instances on a Schedule — Cut compute spend with tag-driven schedules
Related Tools
Try these companion tools on DevStackIO Tools:
- days between dates calculator — track CloudWatch log retention periods.