ShrinkRay All Articles
Developer Productivity

Drowning in Logs: How Verbose Logging Quietly Wrecks Your Cloud Budget

By ShrinkRay Developer Productivity
Drowning in Logs: How Verbose Logging Quietly Wrecks Your Cloud Budget

Let's say you're running a mid-sized SaaS app on AWS. You've got CloudWatch hooked up, maybe Datadog or Grafana Cloud on the side, and your engineers have been dutifully sprinkling console.log and logger.debug calls throughout the codebase for the past two years. Feels responsible, right? Thorough, even.

Then your infrastructure bill hits $18,000 for the month — and a solid $6,000 of that is log ingestion and storage.

Welcome to the logging apocalypse. It's quieter than you'd expect, and it's been running on your dime for months.

The Problem Nobody Audits

Logging is one of those things that engineers add in good faith and nobody ever cleans up. You add a debug statement during a hairy incident at 2 a.m., it ships to production, and it starts firing 40,000 times per hour. Multiply that across a distributed microservices setup with a dozen services all logging at DEBUG level, and you're generating data at a rate that would make your DBA faint.

The real kicker is how log pricing works at major cloud providers. AWS CloudWatch charges for log ingestion and storage. Datadog's pricing scales with the volume of logs indexed. New Relic, Splunk, Elastic Cloud — they all have their own flavors of the same trap. You're not just paying to store logs. You're paying to ingest them, index them, query them, and retain them. Every noisy service is a compounding tax.

One engineering team at a mid-market e-commerce company discovered they were logging the full serialized user object — including hashed passwords, preferences, and address history — on every single authenticated API call. Not because anyone wanted that. Because someone copy-pasted a debug block during a feature sprint and it never got pulled. That one oversight was generating roughly 2TB of logs per month.

Two terabytes. Of the same user object. Over and over.

Log Levels Are a Feature, Not a Formality

Most logging libraries support severity levels: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. The intended contract is simple — you run DEBUG locally, INFO or WARN in staging, and ERROR or above in production.

In practice? Teams frequently ship to production with DEBUG still enabled because nobody updated the environment config. Or worse, someone turned it on to investigate an issue six months ago and it never got turned back off.

The fix here is almost embarrassingly simple: audit your log level configuration per environment, enforce it in your deployment pipeline, and make DEBUG-in-production a build check failure rather than a convention. Tools like winston in Node.js or logback in Java make environment-aware log levels trivial to configure. There's no good reason not to.

Sampling: You Don't Need Every Log, You Need the Right Logs

Here's a mental shift that pays for itself fast: not every event needs a log entry.

If your app handles 500 requests per second and 499 of them succeed without incident, logging all 499 successful requests is noise. You're not learning anything. You're just filling buckets.

Log sampling lets you capture a statistically meaningful percentage of normal traffic — say, 1% or 5% — while still logging 100% of errors, warnings, and anomalies. Most modern observability stacks support this natively. OpenTelemetry, for instance, has built-in trace sampling. Datadog's log pipelines support sampling rules based on log level and service. You can configure these without losing meaningful signal.

For high-throughput services, the savings from sampling alone can cut your log volume by 80–90% with zero meaningful loss in observability. That's not a rounding error — that's a budget line item.

Structured Logging and Field Discipline

Another underrated lever: what you actually put in a log entry.

Unstructured logs are a mess. "User 1042 did the thing at 14:32" is useless at scale. But the move to structured logging (JSON-formatted entries with typed fields) sometimes creates a different problem — engineers start logging everything because the format makes it feel organized.

Be deliberate about fields. Do you need the full request body in the log? Probably not — log the request ID and pull the body from your request tracing system if you need it. Do you need every HTTP header? Almost certainly not. Define a schema for your log entries, review it like you'd review an API contract, and strip anything that doesn't serve a specific debugging or compliance need.

This pairs well with log filtering at the pipeline level. Tools like Fluent Bit and Vector (from the folks at Datadog) let you drop, redact, or transform log fields before they ever hit your storage backend. You can strip verbose fields from high-volume services at the collector layer without touching application code.

Retention Policies: The Bill That Keeps Growing

Storage is cheap in isolation. Storing 90 days of unfiltered debug logs across a fleet of services is not.

Most teams set a retention policy once and forget it. If you're on CloudWatch with the default "Never Expire" setting — yes, that's a real default — you're accumulating logs indefinitely. Go check right now. Seriously.

A tiered retention strategy makes a lot more sense: keep ERROR and WARN logs for 90 days, keep INFO logs for 30 days, and either drop DEBUG logs entirely in production or retain them for 7 days maximum. For compliance-sensitive data, archive to S3 in compressed format (Gzip or Zstandard) rather than keeping it hot in your logging platform.

The cost delta between hot log storage and cold S3 archival is substantial. We're talking roughly 10–20x cheaper per GB depending on your setup.

Alerting on Log Volume Itself

Here's the meta-move: treat log volume as a first-class metric.

Set up alerts when a service's log output spikes unexpectedly. If a service that normally emits 500MB of logs per day suddenly starts emitting 5GB, something changed — either a bug shipped, a log level got flipped, or a new code path is firing in an unexpected loop. Catching that early saves money and often surfaces real production issues before users notice.

Datadog, Grafana, and CloudWatch all support metric-based alerts on log ingestion volume. Wire this up the same way you'd wire up a CPU or memory alert. Log bloat is a system health signal.

The Bottom Line

Logging is essential. Logging everything, always, at full verbosity, forever? That's just burning money in a very sophisticated way.

The path forward isn't logging less — it's logging smarter. Right levels, right environments, right fields, right retention. Add sampling for high-volume services, filter at the collector layer, and treat log volume like any other infrastructure metric worth monitoring.

Your observability doesn't have to cost a fortune. It just has to be intentional.

Compress more. Ship faster. Waste nothing — including your logs.