CloudWatch Logs has two log classes, one is half the ingestion price, and the choice is permanent: AWS does not let you change a log group's class after it is created. The cheaper one also silently removes the features most teams built their alarms on.
What do CloudWatch Logs cost?
Three meters, and the one people budget for is not usually the one that grows. Ingestion is charged per GB as data arrives. Storage is charged per GB per month for as long as retention keeps it. Query is charged per GB scanned by Logs Insights.
| Meter | Charged on | Lever |
|---|---|---|
| Ingestion | GB arriving, by log class | Log volume, and the class you chose |
| Storage | GB retained per month | Retention setting |
| Logs Insights query | GB scanned per query | Time range and field indexing |
Current per-GB rates are on the CloudWatch pricing page. Read them there. What is worth internalising instead is the structure, because the structure is what you can change.
One distinction to get right before comparing any numbers. Logs that AWS services deliver on your behalf are billed on a separate tiered meter called Vended Logs, described on that page as "logs from specific AWS services that are available at volume tiered pricing". VPC Flow Logs, and the tiering that applies to them, are covered in what VPC Flow Logs actually cost. This post is about the log groups your own applications write to.
What is the Infrequent Access log class?
A cheaper ingestion tier with features removed. The log classes documentation describes Infrequent Access as offering "a subset of CloudWatch Logs capabilities including managed ingestion, storage, cross-account log analytics, and encryption with a lower ingestion price per GB", and positions it as "ideal for ad-hoc querying and after-the-fact forensic analysis on infrequently accessed logs."
The pricing difference is narrower than it sounds, and AWS is precise about where it applies: "For charges, the Standard and Infrequent Access log classes differ in ingestion costs only. Storage charges and CloudWatch Logs Insights charges are the same in each log class."
So Infrequent Access reduces one of the three meters. If your bill is dominated by storage because retention is set to never expire, changing log class saves you nothing at all.
What do you give up in Infrequent Access?
Four features, and between them they cover most of what a log group is wired into. From the supported-features table:
| Feature | Standard | Infrequent Access |
|---|---|---|
| Managed ingestion and storage | Yes | Yes |
| Cross-account features | Yes | Yes |
| Encryption with AWS KMS | Yes | Yes |
| Logs Insights queries | Yes | Yes, most commands |
| Export to Amazon S3 | Yes | Yes |
| Metric filters | Yes | No |
| Subscription filters | Yes | No |
| Live Tail | Yes | No |
| Field indexing | Yes | No |
The first two are the ones that break things. No metric filters means you cannot turn a log line into a CloudWatch metric, which means you cannot alarm on the contents of an Infrequent Access log group. No subscription filters means you cannot stream it to Firehose, Lambda or OpenSearch either.
That combination rules Infrequent Access out for anything on an alerting path, and rules it in for exactly what AWS says: logs you keep in case somebody has to go looking. Debug logs, verbose access logs, per-request traces. The test is whether anything automated reads the log group. If nothing does, it is a candidate.
Can you change a log group's class later?
No, and this is the sentence to read twice: "After a log group is created, its log class can't be changed."
Which makes this a migration rather than a setting. To move a stream to Infrequent Access you create a new log group in that class, repoint whatever writes to it, and let the old group age out under its retention policy. On Lambda that is a function configuration change; on ECS or EKS it is a log driver or agent change. Budget for the work, and do it once per stream rather than discovering the constraint halfway through.
The corollary is that new log groups deserve a decision at creation time. Defaulting everything to Standard is the expensive habit, and defaulting everything to Infrequent Access breaks alarms you have not written yet.
Why is storage often the bigger number?
Because ingestion is charged once and storage is charged every month until retention deletes the data. And retention has no default expiry: AWS states "By default, log data is stored in CloudWatch Logs indefinitely. However, you can configure how long to store log data in a log group." A stream ingested two years ago into a log group nobody set retention on is still billing today.
Retention is the single highest-leverage setting in CloudWatch Logs and it is the one most often left unset. Find the groups with no expiry:
aws logs describe-log-groups \
--query 'logGroups[?retentionInDays==`null`].[logGroupName,storedBytes,logGroupClass]' \
--output table
Then sort by storedBytes and set retention on the largest ones
first. A single command changes it, and the effect is immediate on the storage
meter:
aws logs put-retention-policy \
--log-group-name /aws/lambda/my-function \
--retention-in-days 30
Where you genuinely need years of history, exporting to S3 and expiring the CloudWatch copy is usually cheaper than CloudWatch storage, and Export to S3 is available in both log classes. Check the object sizes before choosing a storage class though: log exports are small files, and the 128 KB minimum billable size on Standard-IA can make a cheaper class cost more.
What makes a Logs Insights query expensive?
Bytes scanned, which is a function of the time range rather than the result count. A query over 30 days scans 30 days of that log group whether it returns one row or a million, so narrowing the range is the whole optimisation. This is the same economics as Athena's per-query scan charge, without an equivalent hard cancel control.
AWS also publishes a dedicated guide to analyzing, optimizing and reducing CloudWatch costs, which is worth reading alongside the log group and retention reference.
Field indexing reduces scanned volume on Standard log groups, and it is one of the features Infrequent Access does not have. That is a real cost interaction: choosing the cheaper ingestion class removes the mechanism that makes querying cheaper. If a log group is both high volume and frequently queried, Standard with indexing can end up cheaper overall than Infrequent Access without it.
Can you attribute CloudWatch Logs cost to a team?
CloudWatch appears as its own service, and usage type separates ingestion from storage from queries:
aws ce get-cost-and-usage \
--time-period Start=2026-07-01,End=2026-08-01 \
--granularity MONTHLY \
--metrics UnblendedCost UsageQuantity \
--group-by Type=DIMENSION,Key=USAGE_TYPE \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["AmazonCloudWatch"]}}'
Per log group, that is as far as Cost Explorer goes. Log groups are not tagged resources in the cost data by default, so attributing spend to a team means either naming conventions you can group on or activating tags on the log groups themselves, which runs into the delays described in why cost allocation tags show up empty.
Worth knowing what is not covered anywhere in this data: CloudWatch is not in Cost Optimization Hub's supported resource list, so no AWS recommendation engine will tell you a log group has no retention policy. That check has to be yours, and the command above is the whole of it.