Log retention cost comes from two mistakes that add up: log groups and tables that never expire, and debug logs ingested in full though nobody reads them. The saving comes from ingestion being billed per GB and retention per GB-month, and both falling at three points: at the source through log levels and exclusions, at ingestion through cheaper classes and plans, and at retention through periods. The fix belongs to the team that runs the application that logs, because only there is it known which lines an incident needs.
Symptom
The bill shows ingestion and storage lines that grow with time, not with load. On AWS these are lineItem/UsageType EUC1-DataProcessing-Bytes for ingestion and EUC1-TimedStorage-ByteHrs for retention under AmazonCloudWatch, per log group as lineItem/ResourceId; plus VendedLog-Bytes for other services' logs. On Azure they are MeterCategory Log Analytics with Data Ingestion, Basic Logs Data Ingestion and Data Retention meters per workspace. On Google Cloud Cloud Logging carries the SKUs Log Volume for ingestion and Log Retention for data beyond the default retention, per project. What observability costs in principle is in observability, logging and monitoring; this page covers the logs nobody needs.
How to detect it
AWS
The log groups without a retention period, with stored volume and class:
aws logs describe-log-groups --query 'logGroups[?retentionInDays==null].[logGroupName,storedBytes,logGroupClass]' --output table
Ingestion per group over two weeks, as a sum per day:
aws cloudwatch get-metric-statistics --namespace AWS/Logs --metric-name IncomingBytes \
--dimensions Name=LogGroupName,Value=<log-group> --statistics Sum \
--start-time 2026-10-07T00:00:00Z --end-time 2026-10-21T00:00:00Z --period 86400
CloudWatch Logs has two classes: Standard with every feature and Infrequent Access with a lower price per GB ingested, without Live Tail, metric filters, subscription filters and Container Insights ingestion; the class is set at creation and cannot be changed afterwards, and storage prices are the same in both (read on 2026-10-21, log classes). Trusted Advisor and Compute Optimizer do not check log groups; the log groups with the highest IncomingBytes per day are the list. In billing data the candidate is a log group with growing TimedStorage-ByteHrs at steady DataProcessing-Bytes (AWS Cost and Usage Report).
Azure
A workspace's tables with plan and retention, and the ingestion per table over the last 14 days:
az monitor log-analytics workspace table list --resource-group <rg> --workspace-name <workspace> \
--query "[].{name:name, plan:plan, retention:retentionInDays, total:totalRetentionInDays}" -o table
az monitor log-analytics query --workspace <workspace-id> \
--analytics-query "Usage | where TimeGenerated > ago(14d) | summarize GB=sum(Quantity)/1000 by DataType | order by GB desc"
Log Analytics has three table plans: Analytics for monitoring and alerts, Basic for troubleshooting, Auxiliary for high volumes kept long and cheaply; a change is possible once per table per week, Basic and Auxiliary bill queries separately, and alerts do not work on Auxiliary tables (table plans). Azure Advisor recommends the Basic plan for eligible tables with more than 1 GB of ingestion a month, flags a rise in ingestion against the three previous weeks, proposes commitment tiers and recommends Prometheus-based metrics instead of log-based ones for Container Insights. In the cost export the candidate is a workspace with growing Data Retention meters (Azure cost exports).
Google Cloud
The log buckets with retention, and the sinks with their exclusions:
gcloud logging buckets list --format="table(name,location,retentionDays,locked)"
gcloud logging sinks list --format="table(name,destination,filter,exclusions[].filter.list())"
The _Default bucket keeps logs for 30 days, configurable between 1 and 3,650 days, and Google charges retention only for data beyond the default period; excluded logs are neither ingested nor charged (log buckets). The metric logging.googleapis.com/billing/bytes_ingested per project and resource type shows which service produces the volume. There is no recommender for logs. In the billing export the candidate is a project with Log Volume far above the free allotment and a Log Retention line (Google Cloud billing export).
How to fix it
- Answer per log group or table how many days back an incident needs and whether an obligation demands longer; the rest is retention without a use. The periods belong in the cost policy, per log type.
- Set periods:
aws logs put-retention-policy --log-group-name <log-group> --retention-in-days 30;az monitor log-analytics workspace table update --resource-group <rg> --workspace-name <workspace> --name <table> --retention-time 30;gcloud logging buckets update _Default --location=global --retention-days=30. None of these calls has a dry run; the list from the detection stands in for it. - Cut ingestion, at the source first: debug level only during troubleshooting, sampling for access logs, health check lines filtered out; then at the provider: a new log group of the Infrequent Access class for logs without metric filters and subscriptions,
az monitor log-analytics workspace table update --plan Basicfor tables without alerts, transformations in data collection rules that drop columns and rows, exclusions on Google Cloud withgcloud logging sinks update _Default --add-exclusion=name=debug,filter='severity<=DEBUG'. - Replace metrics from logs: counters filtered out of log lines today are emitted as metrics, and for containers the metric-based collection instead of the log-based one, as Microsoft recommends for Container Insights; logs that serve only as an archive are exported to S3, Blob Storage or Cloud Storage, where the GB-month costs less than in the log service.
A new period applies retroactively and deletes older data within days; the ingestion line falls in the next daily export after the change at the source.
Risks and side effects
- A period also deletes what an audit demands later; logs with a retention obligation go into an archive with its own, longer period before the period is set.
- Infrequent Access and Basic are one-way streets in features: no metric filters, no subscriptions, no Live Tail on AWS, no alerts and separate query costs on Azure. An alert based on a log line breaks when the group changes.
- Sampling and exclusions at the source lose lines a rare error needs; an exclusion by severity is safer than one by chance.
- The export to object storage moves the cost, it does not end it; the bucket needs its own lifecycle rule.
- Commitment tiers on Azure lower the price per GB but bind you to a volume; cutting ingestion below the tier leaves the tier idle (commitment coverage and utilisation).
How to verify the saving
Before: per log group, table or project, the GB ingested and the GB-months stored in the last full month, DataProcessing-Bytes and TimedStorage-ByteHrs, Data Ingestion and Data Retention, Log Volume and Log Retention. After: the same lines in the first full month after the period and the source change; the storage line has to fall to the level of the period, ingestion by the excluded volume, and the number of alerts and dashboards that still work stays unchanged. The difference from your own calculation is the estimate, the team's release makes it accepted, the change implemented, and the following month's invoice realises it in the pattern of estimated, accepted, implemented, verified. The number of log groups and tables without a period is the figure that then stays at zero.
Sources
Read on 2026-10-21. CloudWatch Logs log classes, aws logs, Azure Monitor table plans, Azure Advisor cost recommendations, Cloud Logging log buckets, gcloud logging sinks.