CloudWatch log groups storing logs with no expiry
What does ZopNight detect here?
CloudWatch Logs storage bills $0.03 per GB-month, and a log group whose retention_days is 0 or unset keeps every byte forever. ZopNight detects those groups but currently abstains from a savings claim: AWS reports only total stored bytes, never their age, so a trim figure cannot be computed honestly.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-057 |
| Category | rightsizing |
| Severity | low |
| Metric | none — pure configuration read |
| Source | cloudwatch_logs.go |
Where it applies
Log groups that never expire anything
A CloudWatch log group created without a retention policy defaults to “never expire”. Every byte ever ingested stays, and stored data bills at $0.03 per GB-month indefinitely. Nothing self-corrects here: the group grows monotonically for as long as anything writes to it, which makes unbounded retention one of the quietest recurring costs in an AWS account.
What retention_days zero really means
The discoverer stamps each log group’s configured retention into metadata as retention_days,
where 0 encodes never-expire. The rule suppresses only when a real policy above 0 is present;
a value of 0 or a missing key both count as unbounded. An earlier version read retention from
a tag the discoverer never wrote. The suppression branch was unreachable and the rule
over-fired on every priced group regardless of its true setting. Reading the authoritative
metadata fixed that.
Why no dollar figure is attached yet
The rule currently abstains on every group rather than publish a saving. The honest trim
saving has two parts: stored gigabytes already older than the new window, plus the ongoing
accumulation you stop paying for. Computing either needs the age distribution of stored bytes
or an ingest rate, and AWS exposes neither. What AWS reports about a log group is one total storedBytes
figure, with no age breakdown. Multiplying that total by the storage rate would over-count by
every byte still inside the window, a fabricated number. A cost-driven recommendation must be
concrete or silent, so until an age-bucketed producer exists, this one is silent.
Finding groups with unbounded retention
aws logs describe-log-groups \ --query 'logGroups[?retentionInDays==null].[logGroupName,storedBytes]' --output tableGroups with a large storedBytes and no retentionInDays are your ranked to-do list.
Choosing a retention window
Set retention deliberately: 90 days suits most production workloads, 30 days is plenty for dev and test, and anything with a compliance hold belongs in S3 via an export task, where storage classes are far cheaper than log storage. Applying a policy deletes data older than the window on a rolling basis, so agree the number with whoever owns the audit trail first.