Written by
Halkwinds Editorial Team
Halkwinds Research & Editorial

Observability Stack in Practice: Logs, Metrics, and Distributed Traces
How to design an observability platform that gives engineers genuine insight into production systems — tooling, instrumentation, and alert design.
When a payment API starts returning 500s at 2 a.m., the difference between a five-minute incident and a five-hour outage usually comes down to one thing: whether your team can see what the system is doing. Traditional monitoring tells you that something is broken. Observability tells you why. For engineering managers responsible for uptime, on-call health, and the cost of running production, the gap between those two capabilities is where budgets and reputations quietly bleed out. This article breaks down how to design an observability stack that combines logs, metrics, and distributed traces into a coherent platform — one that engineers actually trust and use during incidents.
- Background / Why This Matters
- Core Concepts and Architecture
- Implementation Strategy
- Scaling and Operational Considerations
- Common Mistakes / What to Avoid
- Frequently Asked Questions
- Conclusion
Background / Why This Matters
Monitoring answers questions you knew to ask in advance. You set a threshold on CPU usage, and an alert fires when it's breached. That model worked when applications were monoliths running on a handful of servers. In a modern architecture — dozens of microservices, ephemeral containers, managed databases, third-party APIs — the failure modes are combinatorial. You cannot pre-define a dashboard for every way the system can degrade.
Observability is the property of a system that lets you ask arbitrary questions about its behavior without shipping new code to answer them. The industry consensus, popularized by teams like Honeycomb and reflected in the CNCF ecosystem, is that observability rests on three pillars: logs (discrete events), metrics (aggregatable numeric time series), and distributed traces (the causal path of a request across services).
Why should an engineering manager care beyond the technical elegance? A few reasons that show up in real budgets and team morale:
- Mean time to resolution (MTTR) is a direct function of how quickly engineers can localize a fault. Research consistently suggests that most incident time is spent identifying root cause, not deploying the fix.
- On-call burnout is driven by noisy, low-signal alerts. A well-designed stack reduces pages and improves retention.
- Observability spend can run away. Estimates vary, but many teams find their observability bill rivals or exceeds their compute bill once volume grows unchecked.
Takeaway: Treat observability as a product with users (your engineers) and a cost center that needs governance — not as a checkbox you satisfy by installing an agent.
Core Concepts and Architecture
A modern observability stack has four logical layers: instrumentation, collection, storage, and query/visualization. Keeping these separate is what lets you swap components without re-instrumenting every service.
The Three Signal Types
| Signal | Best for | Cost profile | Typical tool |
|---|---|---|---|
| Metrics | Trends, SLOs, alerting, capacity planning | Low — pre-aggregated, cheap to store | Prometheus |
| Logs | Detailed forensic context, audit trails | High — volume grows fast, expensive to index | Loki, Elasticsearch |
| Traces | Request flow across services, latency attribution | Medium — controllable via sampling | Jaeger, Tempo |
The mistake many teams make is treating these as three separate silos with three separate UIs. The value multiplies when they're correlated: a spike in a metric links to the traces from that window, and each span links to the logs it produced. This correlation is what turns "the checkout service is slow" into "the checkout service is slow because the inventory gRPC call is timing out against a specific database replica."
OpenTelemetry as the Standard
OpenTelemetry (OTel) is the most important architectural decision you'll make. It is a vendor-neutral standard for generating and shipping all three signal types. Instrument your code once with the OTel SDK, run the OTel Collector as a pipeline, and route data to whatever backend you choose. If you later decide to move from a SaaS vendor to self-hosted Grafana, you change the Collector's export config — not thousands of lines of application code.
A Reference Architecture
- Instrumentation: OpenTelemetry SDKs and auto-instrumentation libraries embedded in each service, emitting metrics, logs, and spans.
- Collection: The OTel Collector running as a sidecar or per-node agent plus a central gateway, handling batching, sampling, and enrichment.
- Storage: Prometheus (or a long-term store like Thanos/Mimir) for metrics, Loki for logs, Jaeger or Tempo for traces.
- Visualization: Grafana as the single pane of glass across all three, with correlated links between panels.
Takeaway: Standardize on OpenTelemetry for instrumentation before you commit to any storage or visualization vendor. It's the layer that preserves your optionality.
Implementation Strategy
You cannot instrument everything at once, and you shouldn't try. A phased rollout keeps the project shippable and lets the team build muscle memory.
Phase 1: Get Metrics and Alerting Right
Start with metrics because they give you the most operational value per dollar. Deploy Prometheus, add exporters for your infrastructure (node_exporter, kube-state-metrics), and instrument your services with OTel to expose the four golden signals: latency, traffic, errors, and saturation. Build Grafana dashboards per service.
Crucially, define Service Level Objectives (SLOs) and alert on error budget burn rate rather than raw thresholds. Alerting "when 5xx errors exceed 1% over 5 minutes" is far more meaningful than "when CPU > 80%," because it reflects user impact.
Phase 2: Structured Logging
Convert application logs to structured JSON with consistent field names (service.name, trace_id, severity). Ship them via the OTel Collector to Loki. The single most valuable field to include is the trace ID, because it's what stitches logs to traces in the next phase.
Phase 3: Distributed Tracing
Enable tracing on your highest-traffic request paths first. With OTel auto-instrumentation, HTTP and gRPC calls, database queries, and message queue operations get spans automatically. Send traces to Jaeger or Tempo. Now, when you're staring at a latency spike in Grafana, you can jump straight to the slow trace and see exactly which downstream span consumed the time.
The single most transformative moment for most teams is the first time an engineer clicks from a Grafana latency panel directly into the exact trace that caused it — and from that trace into the logs. That correlation loop is the whole point of an observability stack.
This is precisely the kind of end-to-end platform work our Engineering team at Halkwinds designs for clients migrating off fragmented, tool-per-team setups — establishing the OTel pipeline and correlation conventions so the three signals actually reinforce each other.
Takeaway: Ship in phases — metrics, then logs, then traces — and make trace-ID propagation a hard requirement early so correlation works when you need it.
Scaling and Operational Considerations
An observability stack that works for 10 services can fall over — or bankrupt you — at 200. Plan for scale deliberately.
Control Cardinality
The number one cause of Prometheus meltdowns is high-cardinality labels. Attaching a unique value like user_id or request_id as a metric label creates a separate time series for every value, and memory usage explodes. Keep labels bounded (service, endpoint, status code) and push high-cardinality context into traces and logs, where it belongs.
Sample Your Traces
Storing 100% of traces at scale is rarely worth the cost. Use tail-based sampling in the OTel Collector — keep all traces that contain errors or exceed a latency threshold, and sample a small percentage of the normal ones. This preserves the signals you need for debugging while cutting storage dramatically.
Long-Term Metric Storage
Vanilla Prometheus isn't built for multi-month retention or horizontal scale. Adopt Thanos or Grafana Mimir to offload historical data to object storage (S3/GCS) and enable global querying across clusters.
Self-Hosted vs. SaaS
| Dimension | Self-hosted (Grafana stack) | SaaS (Datadog, etc.) |
|---|---|---|
| Upfront cost | Lower licensing, higher ops effort | Higher per-GB/host pricing |
| Operational burden | You run and scale it | Vendor handles it |
| Cost predictability | Predictable infra cost | Can spike with volume |
| Data control | Full ownership | Data leaves your boundary |
Because you standardized on OpenTelemetry, this decision is reversible and can even be split per signal type — self-hosted logs, SaaS traces, whatever the economics favor.
Takeaway: Cardinality and sampling are the two levers that determine whether your stack scales gracefully or becomes your largest surprise invoice.
Common Mistakes / What to Avoid
- Alert fatigue. If engineers routinely ignore or silence pages, your alerting is broken. Every alert should be actionable and tied to user impact. Delete alerts that no one acts on.
- Dashboards nobody reads. A hundred dashboards is not observability. Curate a small set of service-level and SLO dashboards, and rely on ad-hoc querying for everything else.
- Ignoring trace-ID propagation. Without consistent context propagation across service boundaries, traces break into disconnected fragments and lose their value.
- Logging everything at DEBUG in production. This is
Explore Further