Cut the CloudWatch log bill per pattern

Share
CloudWatch charges most to ingest logs, billed per GB, and every repeat of a message pays again. 10x gives each line a stable pattern ID, computed from the log statement that printed it rather than by clustering, and records each ID's volume and cost. Two levers move a chosen pattern and keep every line: tier down sends it to the Infrequent Access class, CloudWatch's cheaper tier, and offload sends it to an S3 bucket the account owns, retrievable by pattern ID. The routing is ordinary; deciding which pattern to move is what 10x adds.

The expensive line on a CloudWatch Logs bill is ingestion, the charge for each GB ingested; storage after it costs far less. Ingestion is billed per GB, so a message printed in a loop, a health check or a retry, pays the full rate every time it repeats, whether or not anyone reads it.

10x runs as a sidecar to your log forwarder (Fluent Bit, the OpenTelemetry Collector, Vector, and more) and reads each line before CloudWatch receives it. The engine gives each line a stable pattern ID, computed from the log statement that printed it, so lines from one statement share an ID. 10x records what each pattern costs to ingest, then moves the expensive patterns that no alarm or dashboard reads to the Infrequent Access class or to an S3 bucket the account owns; nothing is deleted, and every line stays retrievable. The levers cover the sources a forwarder fronts; logs a Lambda function or a managed service writes straight to CloudWatch stay on the standard bill.

The 10x engine gives each line a stable pattern ID

Here are two lines from an API gateway.

2026-07-12 14:03:11 INFO api-gateway request complete route=/v1/charge status=200 bytes=1420 duration_ms=183
2026-07-12 14:03:12 INFO api-gateway request complete route=/v1/refund status=200 bytes=644 duration_ms=97

Both come from one logging statement; only the values differ. Some tokens are the same in every line the statement prints: INFO, request complete, and the field names route, status, bytes. The rest change each time: the route, the status code, the byte count, the timestamp. The engine keeps the fixed tokens, the structure, and drops the changing values; what remains is the pattern, and its hash is the pattern ID.

The engine identifies the fixed tokens from a vocabulary, the strings the code can print, compiled from application and framework code ahead of time. It does not decide by how often a token repeats, and that is what makes the ID stable: the same statement gives the same ID next week and on every host. A line with no code behind it, a raw access log, is the exception, where a changing value can pass for a fixed token and one statement yields a few IDs.

CloudWatch's pattern command in Logs Insights clusters the events of a query into shared shapes too, but it clusters per query, over the log groups and time range the query names, with nothing durable behind the clusters and no cost history. Anomaly detection tracks patterns durably per log group, yet exposes no identity to key a cost series or a routing rule to. Either kind of cluster can change when the lines behind it change, measured against Drain, a common clustering algorithm, in its own post; the pattern ID does not, because it comes from the vocabulary rather than the traffic. When a deploy adds a field or writes a value into the text, a metric filter matching that text stops matching and the metric drops to zero with no signal, but the pattern ID and its cost history continue; only rewriting the message itself gives a new ID.

Each pattern has its own numbers. Here is one, measured over a week in a demo environment running the OpenTelemetry demo shop, at CloudWatch list rates for us-east-1: ingestion $0.50 per GB in the Standard class, storage $0.03 per GB per month. It is a high-volume INFO line from the cart service, ValkeyCartStore GetCartAsync called with userId, ID FU1__vh8hbY, about 1.5 KB per event.

Measured Per day Per 30 days
Lines 16.2M 486M
Volume 24.2 GB 726 GB
Ingestion $12.10 $363
Storage $0.73 $22

Ingestion is 94% of what this message costs, and CloudWatch bills storage on compressed bytes, so the real share is higher still. The traffic is synthetic and the rates are list rates, so the numbers show the accounting, not a saving.

Two levers that keep every line

Tier down sends a pattern to the Infrequent Access class, CloudWatch's cheaper tier, which ingests at half the Standard rate. The lines stay queryable from the same Logs Insights console the on-call already uses, the read path the class supports. Because the class is fixed when a log group is created, the engine marks the events to move and the forwarder writes them to a second log group in the Infrequent Access class, so CloudWatch bills each group at its own rate. The discount is on ingestion, the dominant line for these patterns; storage and Logs Insights queries cost the same in both classes.

Offload sends a pattern's events out of CloudWatch, so they never reach it and never pay ingestion. The forwarder writes them to an S3 bucket the account owns, as plain JSON lines any client can read, with a pattern-ID index beside them. A retrieval by pattern ID reads only the matching objects and returns the events, with nothing re-ingested. An operator who has a raw line instead of an ID pastes it into the same tool, which finds its pattern first.

The decision is what 10x adds

Neither lever is new: AWS sells the Infrequent Access class, and any forwarder can write to S3. The open part is choosing which pattern to move, and keeping that choice right as the logs change.

Nothing moves by default. An operator sets a monthly budget; the per-container limits from it are proposed by the log10x MCP in a pull request that a person merges, and a container with no budget is left alone. Inside a budgeted container, only a pattern that dominates recent volume, above a fifth of it over a few-minute window, has its excess moved.

Everything under that share stays in the Standard class, along with the first events of each window and, from severity floors, at least half of a pattern's Error events. A wrong choice loses no data: every moved line is queryable in the Infrequent Access group or retrievable from the bucket by ID.

One exemption keeps alarms and metrics working. A pattern that feeds a metric filter, a subscription filter, or the embedded metrics format is never moved by either lever, because the Infrequent Access class does not compute them and offload removes the events they read. A dependency check reads the alarms, dashboards, and metric filters that reference a pattern, so the exempt list matches what uses it. The engine also reports each pattern's pre-move volume as a metric series, with CloudWatch among the backends, so the real number stays on the dashboard.

The budgets and exemptions are committed files, keyed by pattern ID, each with a reason and an expiry, and with the per-pattern history they are the record behind every move. To see it on real data, the log10x MCP server installs into an AI client and runs against a live demo with no account; the table above came from there. 10x decides which pattern to move, and the platforms enforce it.