Read the Logs: How Developers Are Ditching Observability Sprawl and Just Thinking Again
At some point, the tools we built to understand our systems became systems we couldn't understand.
Trace IDs that span seventeen services. Dashboards that take ten seconds to load while your production incident is actively happening. Alert fatigue so severe that engineers start treating PagerDuty like a car alarm in a parking garage — technically meaningful, practically ignored. We built all of this to get visibility into our applications, and now we need visibility into our visibility layer.
Some developers have had enough. They're not throwing away logging entirely — they're going back to basics with a vengeance, and they're building systems that don't require an observability platform to reason about.
The Observability Industrial Complex
Let's name what's happening. Observability has become an industry. Datadog, Honeycomb, New Relic, Dynatrace — each offering increasingly sophisticated tools for understanding distributed systems. And to be clear, those tools are technically impressive. Distributed tracing is a genuine engineering achievement.
But here's the uncomfortable question: did we make our systems so hard to understand that we needed a $40,000-a-year SaaS product just to figure out what they're doing? And if so, is the right answer to buy the SaaS product — or to make the system easier to understand?
For a certain kind of builder, that question has a pretty obvious answer.
"I realized I was spending more time configuring OpenTelemetry than I was writing product code," says a solo developer based in Portland who builds B2B tooling. "At some point I asked myself: what would I need to change about my code so that I just... didn't need this?"
That question is more productive than it sounds.
Structured Logging Isn't Sexy, But It Works
The foundation of the low-observability approach is structured logging done right. Not console.log("it broke") — deliberate, consistent, machine-readable log output that tells a clear story about what your application is doing at any given moment.
The key word is structured. JSON logs with consistent fields — timestamp, request ID, user ID, operation name, duration, outcome — give you something you can grep, filter, and reason about without a dedicated platform. Tools like jq turn a stream of JSON logs into a queryable dataset. Your terminal becomes your dashboard.
The discipline required is actually the point. When you have to write the log line yourself, you think about what information actually matters. You stop emitting noise. You start emitting signal. The result is logs that tell a story rather than logs that just prove something happened.
This is a different muscle than configuring auto-instrumentation. It requires you to have opinions about what matters in your system — which turns out to be a useful thing to think about.
Deterministic Execution: Design for Readability
Beyond logging, the teams doing this well are making architectural choices that make their code easier to reason about without external tools.
Deterministic execution is a big one. If the same input always produces the same output, and side effects are explicit and bounded, you can reproduce bugs locally. You can write a test that demonstrates the failure. You don't need a distributed trace to understand what happened — you can just read the code.
This means being intentional about where randomness and external state enter your system. It means making time an explicit input rather than something you grab from the system clock mid-function. It means treating external API calls as boundaries that you mock and test, not invisible operations that happen in the middle of your business logic.
It's not a new idea — functional programming communities have been on this for years. But it's finding new relevance as developers burned by observability complexity look for alternatives.
"If I can reproduce the bug in a unit test, I don't care what happened in production," says one backend engineer at a startup in Denver that deliberately avoids microservices. "The observable behavior is the test. I don't need a flame graph."
Synchronous-First as a Debugging Philosophy
Async-everything was another gift from the distributed systems era that comes with hidden costs. Event-driven architectures are powerful, but they're notoriously hard to debug. When a message gets dropped or processed out of order, the failure often shows up nowhere near the cause. You're chasing ghosts across queues.
Synchronous-first design doesn't mean avoiding async entirely — it means defaulting to synchronous execution where the performance tradeoff is acceptable, and being explicit about the complexity cost when you go async.
A synchronous request-response cycle has a call stack. That call stack is readable. When something goes wrong, the error has a location. You can set a breakpoint. You can add a log line. The relationship between cause and effect is preserved in a way that an event-driven system actively obscures.
For a lot of CRUD-heavy web applications — which, let's be honest, is most of what gets built — synchronous-first is not a performance compromise. It's just a cleaner way to work.
What About AI and Automated Analysis?
Here's where it gets interesting for the AI-adjacent crowd. One emerging pattern is using LLMs as a lightweight alternative to observability dashboards — not for real-time monitoring, but for log analysis.
A structured log file is just text. And LLMs are very good at reading text, finding patterns, and surfacing anomalies. Some developers are piping log output directly into Claude or GPT-4 during incident investigation and asking it to identify what changed, what's failing, and what the most likely cause is.
This isn't a replacement for good logging practices — it's an amplifier for them. If your logs are structured and meaningful, an LLM can do in thirty seconds what a human might take twenty minutes to piece together. If your logs are noise, you'll get noise back.
The pattern is still early, but it fits neatly into the broader philosophy: make your system understandable by a human (or a language model) reading text, rather than requiring specialized infrastructure to visualize.
The Practical Starting Point
If you want to move in this direction, the entry point is simpler than you'd think:
Audit your log output. Read your actual logs as if you were an engineer seeing them for the first time. Can you understand what the system was doing? Can you reconstruct the sequence of events? If not, fix that before you add any new tooling.
Add request IDs everywhere. A single correlation ID that flows through every log line for a given request is worth more than a distributed tracing setup in most cases. It's also a fifteen-minute implementation.
Make your side effects explicit. Any function that calls an external service, writes to a database, or reads the current time should make that dependency visible — either through its signature or through a clear naming convention. Implicit side effects are where bugs hide.
Default to sync. Before you reach for a message queue or an event bus, ask whether a direct function call or a synchronous HTTP request would work. It usually will.
The Bigger Point
Observability tooling exists because distributed systems are genuinely hard to understand. But if the answer to "I can't understand my system" is always "add more tooling," you end up in an arms race with your own infrastructure.
The developers pushing back on this aren't anti-tooling. They're pro-understanding. They want to build systems that a competent engineer can reason about with a terminal, a text editor, and some coffee. That's not a step backward — that's a different kind of ambition.
Shipping software that's simple enough to debug without a map is harder than it sounds. But for the teams getting there, it's paying off in exactly the ways you'd hope: faster debugging, fewer incidents, and engineers who actually know what their code is doing.