Cloud Logging and Cloud Storage, where the quiet Google Cloud money goes
An exclusion filter at the sink is the highest-return control in the whole platform, and the storage class minimums punish a lifecycle rule written without reading them.
Two lines on a Google Cloud bill grow without anyone deciding they should. Logging grows because everything writes to it by default and nothing stops. Storage grows because deleting data requires a decision and keeping it does not.
Both are fixable in an afternoon each, and both are routinely larger than the compute line they sit next to.
Find out what is being ingested
Cloud Logging bills on volume ingested into a log bucket, with an allowance before charges start, plus storage beyond the default retention.
Before changing anything, find the shape. The logs usage dashboard breaks ingestion down by resource type and log name, and the distribution is always top-heavy. Three or four log names will carry most of the volume.
The usual offenders:
- Data access audit logs. Admin activity logs are free and mandatory. Data access logs are not free, are off by default for most services, and are enormous when someone turns them on across the organisation for a compliance reason nobody revisited. This is the single largest logging surprise we find.
- GKE container logs from every container in every namespace, including chatty sidecars and health check output.
- Load balancer request logs at a sampling rate of one, including health checks.
- VPC flow logs at full sampling on busy subnets.
- Application logs at debug level left on after an incident.
The exclusion filter is the control that matters
Sinks route logs to destinations. The _Default sink sends to the default log bucket, and that is what you are billed for. An exclusion filter on that sink drops matching entries before they are stored, which means before they are charged.
This is the highest-return change available, because it is one configuration edit and it takes effect immediately. Typical exclusions: health check requests from load balancer logs, successful readiness probes, debug severity in production, and the high-volume log names that nobody has queried.
Two refinements. Exclusions support a percentage, so you can keep a sample rather than dropping everything, which preserves the ability to spot a pattern while removing the bulk. And you can exclude from _Default while routing the same entries to a cheaper destination such as Cloud Storage, so the data still exists for an investigation without sitting in the queryable tier.
Before excluding anything, check nothing depends on it: a log-based metric, an alerting policy, or a sink someone built for a security team. Losing an alert is how this change goes wrong.
Buckets, retention and the export that moves the cost
Log buckets have a default retention, and storage beyond it is charged per gigabyte per month. Create separate buckets by class rather than accepting one retention for everything: audit logs to whatever the compliance requirement says, application logs to thirty days, debug output to days.
Log analytics on a bucket makes logs queryable with SQL, which is useful and changes the cost profile, so enable it deliberately on the buckets where you actually query rather than everywhere.
Exporting to BigQuery is the pattern teams reach for, and it moves the cost rather than removing it. You stop paying logging storage and start paying BigQuery storage plus query cost, and an unpartitioned table scanned by a dashboard is exactly the failure described in BigQuery bills that triple overnight. Partition the destination table by date, always.
Exporting to Cloud Storage is the cheap archive. Use it for the high-volume, rarely-read logs, with a lifecycle rule attached.
Storage classes, and the minimum durations that bite
Standard, nearline, coldline and archive trade storage price against retrieval price. Moving rarely-read data down a class is obviously right, and it is where teams lose money, because each class has a minimum storage duration with an early deletion charge if you delete or move the object before it elapses.
Nearline is thirty days, coldline ninety, archive three hundred and sixty-five. A lifecycle rule that moves objects to coldline on day one and deletes them on day sixty pays an early deletion charge on every object, and the bill goes up.
Two rules that work. Make the delete rule's age longer than the destination class minimum, always. And transition on a condition that reflects real access, not just age, where the access pattern is uneven.
Autoclass moves objects between classes automatically based on access, with no early deletion charges on the transitions, in exchange for a management fee per object. For a bucket with an unpredictable access pattern it is usually worth it. For a bucket you know is write-once-read-never, a manual lifecycle rule is cheaper.
Retrieval and operations charges are the other half. Coldline and archive charge more per operation and per gigabyte retrieved. A workload doing frequent small reads against coldline can pay more in retrieval than it saves in storage, which is the most common way a well-intentioned lifecycle policy backfires.
The things that accumulate
- Object versioning with no lifecycle rule. Enabled for safety, then every version of every object is kept forever. Add a rule expiring noncurrent versions after thirty to ninety days.
- Soft delete, which retains deleted objects for a default period and is billed. Check the setting on buckets with high churn.
- Multi-region buckets chosen by default. Multi-region costs more than regional and is the right choice only when you need it. A bucket serving one regional workload does not.
- Orphaned persistent disks and snapshots from deleted instances and clusters.
- Incomplete multipart uploads, which bill and appear in no object listing. A lifecycle rule cleans them.
- Old container images in Artifact Registry, which is storage too and grows with every build.
Data transfer, briefly
Ingress is free. Egress to the internet is tiered by volume and destination. Traffic between regions is billed, and traffic between zones within a region is billed at a lower rate, which is the effect that shows up inside a multi-zone cluster as described in GKE cost and autoscaling.
Put a CDN in front of anything serving the same bytes repeatedly. Use Private Service Connect or private access so traffic to Google services does not traverse the internet path. And check the network service tier: standard tier costs less than premium and routes differently, which is an acceptable trade for some workloads and not for latency-sensitive ones.
What to do this week
Run the logs usage view, take the log name at the top, and find out whether any alerting policy or log-based metric depends on it. If nothing does, write an exclusion filter for it today. Then list your buckets with their storage class and their lifecycle rules, and check whether any rule deletes objects before the class minimum. Both checks take under an hour and both usually find money. We run them in the cost phase of a cloud engagement.