# Observability vs Monitoring Explained: Why Logs and Dashboards Alone Don't Cut It Anymore
Summary: Observability vs monitoring explained: monitoring verifies whether a system works according to predefined health thresholds, tracking "known unknowns" through aggregate metrics. Observability enables engineers to infer internal system states from external outputs, resolving non-deterministic "unknown unknowns." In modern distributed architectures, disconnected logs and static dashboards obscure tail latency and cascading failures. Distributed tracing, structured events, and high-cardinality telemetry provide the causal context necessary to pinpoint emergent failures across microservice boundaries.
---
1. The Architectural Divide: State Verification vs. State Inference
Every software engineering organization eventually confronts the "Green Dashboard Fallacy": every Prometheus gauge sits comfortably in the green, CPU utilization hovers below 40%, HTTP 5xx rates register under 0.05%, yet enterprise customers flood support channels because checkout requests hang indefinitely.
To understand why this failure mode is endemic to modern software, one must examine the foundational divergence between monitoring and observability.
+-------------------------------------------------------------------------+
| MONITORING |
| Question: "Is the system broken?" (Binary / Threshold) |
| Scope: Known Unknowns (CPU > 85%, Error Rate > 1%, Disk Full) |
| Mechanism: Pre-aggregated Metrics, Static Alerts, Isolated Dashboards |
+-------------------------------------------------------------------------+
vs.
+-------------------------------------------------------------------------+
| OBSERVABILITY |
| Question: "Why is the system behaving this way?" (Causal / Inferential)|
| Scope: Unknown Unknowns (Tail-latency spikes for specific cart SKUs) |
| Mechanism: High-Cardinality Events, Distributed Traces, Causal Graphs |
+-------------------------------------------------------------------------+The Mathematical Origin of Observability
The term observability is not a Silicon Valley marketing invention; it originates from 1960s linear control theory formulated by Hungarian-American mathematician Rudolf E. Kálmán. In control theory, a system is defined as observable if the current internal state $\mathbf{x}(t)$ can be fully determined from its external outputs $\mathbf{y}(t)$ over a finite time interval:
$$\dot{\mathbf{x}}(t) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}\mathbf{u}(t)$$
$$\mathbf{y}(t) = \mathbf{C}\mathbf{x}(t) + \mathbf{D}\mathbf{u}(t)$$
For distributed software architectures, this principle dictates a rigorous standard: can you understand any arbitrary internal failure state purely by interrogating the telemetry emitted at the boundaries, without shipping new code, adding debug logs, or SSH-ing into a container?
Monitoring: Tracking Known Unknowns
Monitoring is a verification mechanism. It assumes you already know what can break. Engineers write alert rules based on historical failure modes:
- Alert when free disk space falls below 15%.
- Alert when API Gateway p95 response time exceeds 250 milliseconds.
- Alert when a worker queue depth surpasses 5,000 items.
These are known unknowns. You know that disk capacity is finite, latency can spike, and queues can back up. Monitoring samples these predefined state variables periodically and checks them against static thresholds.
In a monolithic architecture deployed on static bare-metal instances, monitoring was frequently sufficient. When an alert triggered, an engineer could inspect the application server, examine the local Apache error log, match timestamps, and identify the root cause within minutes.
Observability: Inferring Unknown Unknowns
Modern cloud-native platforms—composed of ephemeral Kubernetes pods, asynchronous message brokers, serverless handlers, and distributed databases—do not fail in static, predictable patterns. They fail through emergent behaviors: non-deterministic interactions across disparate services where every individual component reports healthy status metrics, but the system as a whole degrades.
These are unknown unknowns. You cannot configure a dashboard alert for a failure mode you have never conceived. Observability focuses on outfitting systems with rich, context-dense, high-cardinality telemetry so that when an unprecedented failure occurs, operators can navigate through the data, ask arbitrary questions, and isolate the exact sub-path where execution derailed.
---
2. The Three Telemetry Pillars: Capabilities and Structural Limits
Industry literature frequently cites "Metrics, Logs, and Traces" as the three pillars of observability. While convenient, treating these telemetry streams as independent data silos is precisely why many organizations incur substantial tooling overhead yet fail to meaningfully reduce Mean Time to Resolution (MTTR).
Each pillar possesses distinct mechanical properties, data structures, and mathematical constraints.
[ METRICS ] [ LOGS ]
Aggregated Counters / Gauges Discrete Unstructured Strings
Low Cost, Zero High-Cardinality High Context, Zero Topology
\ /
\ /
\ /
[ DISTRIBUTED TRACING ]
Causal Directed Graphs
Correlates Metrics & Events
Across Network BoundariesMetrics: High Efficiency and the Cardinality Wall
Metrics are numeric values measured over intervals of time, optimized for rapid mathematical aggregation (sums, rates, percentiles). A metric sample typically consists of a name, a timestamp, a floating-point value, and a set of key-value labels:
http_requests_total{method="POST", handler="/checkout", status="200"} 4120 1729728000Time-Series Databases (TSDBs) like Prometheus, InfluxDB, or VictoriaMetrics organize data into continuous series keyed by the unique combination of these labels.
The critical architectural limitation of metrics is cardinality—the number of unique time series generated by the Cartesian product of label values:
$$\text{Total Series} = \prod_{i=1}^{n} |V_i| = |V_{\text{method}}| \times |V_{\text{handler}}| \times |V_{\text{status}}| \times |V_{\text{tenant}}| \times |V_{\text{user\_id}}|$$
If an engineer attaches high-cardinality labels such as user_id ($10^6$ values) or order_id ($10^7$ values) to a Prometheus metric, the time-series index explodes exponentially. The TSDB exhausts RAM, inverted indexes thrash disk I/O, and the monitoring infrastructure crashes.
Consequently, metrics require stripping away identifying details. They tell you that errors are increasing, but they can never tell you which specific request failed or why.
Logs: High Context, Zero Topology
Logs are timestamped, discrete strings or structured JSON records emitted during code execution:
{
"timestamp": "2026-10-24T00:15:22.104Z",
"level": "ERROR",
"service": "payment-gateway",
"message": "upstream gateway connection timeout",
"merchant_id": "m_8829104",
"error_code": "ETIMEDOUT"
}Logs provide deep, granular context about an event. Unlike metrics, structured logs comfortably house high-cardinality identifiers such as customer IDs, error stack traces, and database query strings.
However, logs have no native comprehension of distributed topology. In an event-driven architecture where an API request triggers three synchronous REST calls, emits an event to an Apache Kafka topic, and executes four background workers, logs produce millions of uncoordinated lines.
Without a shared causal identifier, finding the relevant log line in a multi-terabyte ElasticSearch or ClickHouse cluster resembles searching for a single grain of sand on a beach—especially when server clocks drift by tens of milliseconds across availability zones.
Distributed Tracing Explained: Causal Graphs Across Boundaries
Distributed tracing solves the fundamental topological blind spot of logs and metrics.
Where a log records an isolated point in time, a trace records the entire end-to-end journey of a transaction through a distributed graph. A trace is a Directed Acyclic Graph (DAG) composed of individual units of work called Spans.
A span encapsulates:
1. Trace ID: A globally unique 16-byte identifier shared across all services participating in the transaction.
2. Span ID: A unique 8-byte identifier for this specific unit of execution.
3. Parent Span ID: The identifier of the span that initiated this work (establishing the parent-child causal tree).
4. Timestamps: Explicit start and end microsecond timestamps.
5. Attributes (Tags): Key-value metadata containing high-cardinality context (db.statement, http.status_code, customer.tier).
6. Events: Internal timestamped annotations within the span (such as state machine transitions).
[Trace: 4bf92f3577b34da6a3ce929d0e0e4736]
---------------------------------------------------------------------------------
Span A: api-gateway [POST /orders] | 210ms |
├── Span B: auth-service [verify_jwt] | 15ms |
└── Span C: order-service [create_order] | 185ms |
├── Span D: inventory-service [reserve] | 42ms |
└── Span E: postgresql [INSERT INTO] | 138ms | (Contention!)
---------------------------------------------------------------------------------By propagating the Trace ID across HTTP headers, gRPC metadata, and message queue envelopes (standardized under the W3C TraceContext specification), distributed tracing connects what would otherwise be disconnected log events into a unified causal timeline.
---
3. Comparative Architectural Matrix: Monitoring vs. Observability
The following matrix contrasts the operational realities of traditional monitoring versus holistic observability:
| Architectural Dimension | Traditional Monitoring | Modern Observability |
|---|---|---|
| Primary Question | "Is the service healthy right now?" | "Why did service $X$ fail for user $Y$ under condition $Z$?" |
| System Philosophy | Predicts specific failure states ahead of time. | Models system outputs to decode emergent, unknown behaviors. |
| Core Telemetry | Aggregate metrics (counters, gauges, timers), threshold alerts. | Correlated traces, structured wide events, high-cardinality attributes. |
| Cardinality Tolerance | Low. Restricted to low-cardinality labels (region, status code). | High. Tolerates millions of unique values (UUIDs, IP addresses, hashes). |
| Failure Modes Addressed | Known Unknowns (host down, disk full, CPU saturation). | Unknown Unknowns (asynchronous race conditions, tail-latency locks). |
| Data Topology | Disconnected data silos (separate metric graphs and log streams). | Unified causal graphs (traces act as the index for logs and metrics). |
| Incident Response Stance | Reactive. Responds when a predefined threshold is breached. | Exploratory. Allows ad-hoc multidimensional slicing without new code. |
| MTTR Impact | High. Engineers spend hours correlating logs manually across pods. | Low. Pinpoints the exact faulty microservice hop and span instantly. |
---
4. The Cardinality Explosion Problem: Why Metrics Engines Collapse
To understand why traditional dashboards fail in modern platforms, one must analyze how metrics storage engines manage dimensional indices.
In Prometheus and comparable time-series databases, each distinct permutation of key-value pairs creates a distinct physical time-series chunk in memory. The TSDB maintains an inverted index (similar to a search engine index) that maps every label pair to an internal SeriesID.
Index Mapping:
service="checkout" -> [Series 1, Series 2, Series 3, Series 4]
status="500" -> [Series 2, Series 4]
user_id="user_9401" -> [Series 4] (Explodes when unique per request!)When an engineering team attempts to diagnose which specific users encounter errors by adding user_id or cart_id to their metric labels:
1. Every new user creates a new entry in the head block index.
2. The memory footprint of the index scales linearly with user traffic ($O(N)$ where $N$ is total unique users).
3. Compaction algorithms freeze as out-of-order chunks multiply.
4. The TSDB triggers Out-Of-Memory (OOM) kernel kills, blinding the entire infrastructure.
Observability shifts this paradigm through Structured Wide Events (often implemented via columnar engines like ClickHouse or modern tracing backends like OpenSearch, Honeycomb, and Jaeger).
Instead of pre-aggregating numbers into static counters, wide events record the full execution context—containing 50 to 100 contextual fields—into compressed, columnar formats. Columnar compression allows querying high-cardinality fields across billions of records in sub-second intervals without maintaining fragile multi-label inverted indices.
---
5. Concrete Debugging Scenario: The Phantom 5-Second Tail Latency Spike
To ground these theoretical concepts, consider a composite production debugging scenario modeled on real-world microservice failure patterns in high-throughput e-commerce architectures.
Architecture Topology
- API Gateway: Envoy Proxy handling edge routing and TLS termination.
- Order Service: Node.js/TypeScript microservice handling checkout orchestration.
- Inventory Service: Go microservice handling stock checks and reservation locks.
- Database: PostgreSQL 16 cluster running on managed cloud infrastructure.
The Failure Symptom
In this representative scenario, an e-commerce platform experiences a flash-sale traffic surge where customer drop-offs spike by approximately 24%. However, aggregate monitoring surfaces an apparent clean bill of health:
- The primary Grafana dashboard shows CPU utilization across all services at 34%.
- Memory consumption is stable across all Kubernetes nodes.
- The API Gateway reports an average latency of 48 milliseconds.
- The overall HTTP error rate sits at a negligible 0.04%.
- Synthetic health checks (
GET /healthz) run every 10 seconds and return200 OKin 6 milliseconds.
Under traditional monitoring, every indicator is green. The on-call engineer checks the dashboards and determines the platform is operating normally.
Why Monitoring Failed
The failure was isolated to the 99.9th percentile (p99.9) tail latency.
Because the average (mean) smooths out spikes, 9,900 fast requests executing in 15 milliseconds completely drown out 100 requests that hang for 5,000 milliseconds:
$$\text{Mean Latency} = \frac{(9900 \times 15\text{ms}) + (100 \times 5000\text{ms})}{10000} = \frac{148500 + 500000}{10000} = 64.85\text{ms}$$
A 64ms average looks completely safe on a dashboard graph. Furthermore, because Prometheus histograms utilize predefined static buckets (le="0.1", le="0.5", le="1.0", le="2.5"), requests exceeding 2.5 seconds were lumped together in the top +Inf bucket, disguising an extreme 5-second timeout cliff.
Isolating the Root Cause with Distributed Tracing
With an observability stack capturing distributed traces, the engineer does not inspect aggregate dashboards. Instead, they run an ad-hoc query filtering for spans where http.status_code == 200 and duration > 4000ms.
Immediately, the tracing system surfaces an exemplar trace DAG:
[Trace ID: 7f81a2b3c4d5e6f7] Duration: 5,124ms
└── api-gateway: POST /checkout [5,124ms]
└── order-service: processOrder [5,121ms]
├── order-service: validateCustomer [12ms]
└── inventory-service: reserveStock [5,098ms] <-- HOTSPOT DETECTED
├── inventory-service: findItems [4ms]
└── postgresql: query [5,092ms] <-- LOCK CONTENTIONInspecting the database span attributes reveals the exact SQL statement and execution context:
-- Span Attribute: db.statement
SELECT id, quantity, version
FROM inventory_items
WHERE sku = 'SKU-FLASH-PROMO-409'
FOR UPDATE;Identifying the Root Cause: Row-Level Contention
The trace reveals that every checkout request for the flash sale item was executing SELECT ... FOR UPDATE against the exact same database row inside an open transaction.
While the inventory service had ample CPU and connection pool slots, transactions were queueing sequentially waiting for the row-level exclusive lock to release. The 5-second spike was the client-side PostgreSQL statement timeout (statement_timeout = 5000) terminating the query, which the order service silently swallowed and retried, resulting in prolonged checkout hangs for high-value customers.
Monitoring reported green because the servers were mostly idle waiting on a lock.
Observability pinpointed the exact line of code, the specific SKU parameter, and the lock wait duration within two clicks.
---
6. Implementation Blueprint: OpenTelemetry in Modern Production Stacks
To transition from passive monitoring to full observability, engineering teams standardize on OpenTelemetry (OTel)—a vendor-neutral CNCF standard providing unified APIs, SDKs, and data protocols.
Below is a production-grade implementation of distributed tracing instrumentation in a Node.js/TypeScript microservice, illustrating how context is created, tagged, and propagated across boundaries.
1. Instrumenting the Node.js Application
This instrumentation module must execute before any application dependencies are imported to allow monkey-patching of core I/O modules (HTTP, pg, redis).
// tracer.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
// Optional: Enable internal diagnostic logging for troubleshooting
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.WARN);
const exporter = new OTLPTraceExporter({
// Forward traces to local OpenTelemetry Collector sidecar or daemonset
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
});
export const otelSDK = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'order-orchestrator',
[SemanticResourceAttributes.SERVICE_VERSION]: '2.4.1',
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || 'production',
}),
traceExporter: exporter,
instrumentations: [
getNodeAutoInstrumentations({
// Fine-grained control over automatic instrumentation overhead
'@opentelemetry/instrumentation-fs': { enabled: false }, // Avoid noisy disk I/O spans
'@opentelemetry/instrumentation-http': {
ignoreIncomingRequestHook: (req) => req.url === '/healthz', // Drop health checks
},
}),
],
});
// Graceful shutdown handling
process.on('SIGTERM', () => {
otelSDK
.shutdown()
.then(() => console.log('OpenTelemetry SDK terminated gracefully'))
.catch((err) => console.error('Error terminating OpenTelemetry SDK', err))
.finally(() => process.exit(0));
});
otelSDK.start();2. Manual Instrumentation and Context Propagation
When crossing asynchronous boundaries or calling downstream microservices via HTTP, traces must maintain causality using the W3C traceparent header.
// orderService.ts
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
import axios from 'axios';
const tracer = trace.getTracer('order-orchestrator', '2.4.1');
interface CheckoutPayload {
orderId: string;
customerId: string;
sku: string;
quantity: number;
}
export async function processCheckout(payload: CheckoutPayload): Promise<void> {
// Start an explicit business span linked to the active trace context
return tracer.startActiveSpan('processCheckout', async (span) => {
try {
// Attach high-cardinality attributes safely to the span
span.setAttribute('app.order.id', payload.orderId);
span.setAttribute('app.customer.id', payload.customerId);
span.setAttribute('app.product.sku', payload.sku);
span.setAttribute('app.cart.quantity', payload.quantity);
// Perform downstream call.
// OpenTelemetry auto-instrumentation will automatically inject:
// 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'
const response = await axios.post('http://inventory-service/api/v1/reserve', {
sku: payload.sku,
quantity: payload.quantity,
orderId: payload.orderId,
});
span.addEvent('inventory_reserved', {
reservationId: response.data.reservationId,
timestamp: Date.now(),
});
span.setStatus({ code: SpanStatusCode.OK });
} catch (error: any) {
// Record exception details and mark span failure state
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message || 'Checkout processing failed',
});
throw error;
} finally {
// Spans must always be explicitly closed to compute duration
span.end();
}
});
}3. OpenTelemetry Collector Configuration
The OpenTelemetry Collector acts as an out-of-process data proxy that receives, transforms, batches, and exports telemetry. Running the collector as a Kubernetes DaemonSet or sidecar offloads telemetry transmission from the application runtime.
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
# Crucial: Prevent memory exhaustion on collector instances
memory_limiter:
check_interval: 1s
limit_percentage: 75
spike_limit_percentage: 20
# Compress and batch spans to reduce network roundtrips
batch:
send_batch_size: 8192
timeout: 500ms
# Tail-based sampling: drop successful 200 OKs, keep errors and slow paths
tail_sampling:
decision_wait: 10s
num_traces: 100000
expected_new_traces_per_sec: 2000
policies:
- name: drop-health-checks
type: string_attribute
string_attribute:
key: http.target
values: [ "/healthz", "/metrics" ]
enabled_regex_matching: false
invert_match: true
- name: sample-errors
type: status_code
status_code: { statuses: [ ERROR ] }
- name: sample-latency-spikes
type: numeric_attribute
numeric_attribute:
key: duration
value_condition: greater_than
value: 2000 # Keep all traces over 2 seconds
exporters:
otlp/jaeger:
endpoint: jaeger-collector:4317
tls:
insecure: true
prometheus:
endpoint: 0.0.0.0:8889
namespace: otel
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [prometheus]---
7. Operationalizing Observability: Sampling Strategies and Cost Governance
Capturing 100% of telemetry in high-throughput systems processing 100,000 requests per second is neither financially viable nor technically necessary. Telemetry storage costs can easily surpass primary infrastructure costs if left unmanaged.
Modern observability architecture relies on strategic sampling paradigms:
Head-Based Sampling vs. Tail-Based Sampling
- Head-Based Sampling: The sampling decision is made at the ingress point (the root span) before the transaction executes. For instance, a probabilistic sampler retains 5% of all incoming requests.
Disadvantage*: If a rare race condition causes a crash on 1 in every 10,000 requests (0.01%), head-based sampling drops 95% of those failing traces, blinding your team to critical edge-case bugs.
- Tail-Based Sampling: The sampling decision is deferred until the entire trace finishes executing across all downstream nodes. The collector buffers the trace DAG in memory for a short window (e.g., 5 to 10 seconds). Once the trace finishes, the collector evaluates rules:
Did any span within the trace return an HTTP 5xx or `SpanStatusCode.ERROR`? Retain*.
Did the total trace duration exceed the p95 threshold? Retain*.
Did the transaction touch an experimental canary deployment? Retain*.
Was the transaction a standard 200 OK executing within target SLAs? Sample down aggressively (e.g., 0.1% to 1%)*.
Tail-based sampling substantially lowers telemetry ingestion and storage costs by filtering high-volume routine traffic while prioritizing the capture of anomalous, slow, or failed traces for root-cause diagnosis. However, teams must dimension collector memory appropriately, as sustained traffic spikes or delayed spans can exhaust buffer queues and lead to dropped traces.
Organizations navigating this operational transformation and establishing resilient telemetry pipelines often partner with specialized cloud DevOps consulting and architecture services to design collectors, configure tail-based sampling, and align SLOs with business impact.
---
8. Frequently Asked Questions
Can an engineering team achieve observability without distributed tracing?
In a monolithic application backed by a single relational database, you can achieve a moderate level of observability using structured, high-cardinality logs and thread-local execution contexts.
However, in any distributed architecture containing microservices, event queues, or asynchronous serverless workers, achieving true observability without distributed tracing is impossible. Without causal metadata (Trace IDs, Parent Span IDs) passed through network headers, disconnected logs cannot be reconstructed into a coherent sequence of events.
Why does high-cardinality metadata degrade time-series databases like Prometheus?
Prometheus and traditional TSDBs store metrics as independent time series indexed by unique label sets. When high-cardinality data (such as UUIDs, user IDs, or container hashes) is used as label values, the Cartesian product produces millions of unique time series.
This causes memory exhaustion because the inverted index must keep all active series keys in RAM to process real-time queries. Columnar engines and modern tracing databases avoid this bottleneck by storing raw structured events in columnar chunks rather than maintaining distinct index records per unique label value.
What is the operational trade-off between head-based and tail-based trace sampling?
Head-based sampling is computationally lightweight and requires no memory buffering because the decision is made when the root span is created; however, it risks discarding rare, catastrophic errors.
Tail-based sampling allows selective retention of anomalous, slow, and failed traces, but it requires stateful collector deployments with sufficient RAM to buffer entire traces in memory while waiting for downstream spans to complete before making the sampling determination. Under heavy burst conditions or collector restarts, uncommitted in-flight spans risk being dropped.
What runtime performance overhead does OpenTelemetry add to production applications?
When configured following production best practices, OpenTelemetry introduces minimal computational and latency overhead. The exact resource footprint depends on the language runtime, span generation frequency, batch size, and network topology.
To maintain low overhead:
1. Avoid verbose auto-instrumentation modules (such as instrumenting high-frequency local filesystem reads or inner utility loops).
2. Use asynchronous batch exporters rather than synchronous network dispatching.
3. Transmit telemetry via gRPC to an out-of-process local collector over localhost or a Unix domain socket, offloading batching, retries, compression, and network transport from the primary application thread.
---
Sources
- Rudolf E. Kálmán - On the General Theory of Control Systems (1960) - Foundational paper formulating mathematical observability and state inference in dynamic systems (Proceedings of the First International Congress of Automatic Control, Moscow, 1960).
- W3C Trace Context Specification (Recommendation) - Official standards for distributed trace context propagation headers (
traceparent,tracestate). - OpenTelemetry Architecture & Specification - CNCF standards for vendor-neutral collection of metrics, logs, and distributed traces.
- Google Site Reliability Engineering: Monitoring Distributed Systems - Foundational principles of service level indicators, symptoms vs. causes, and alerting hygiene.
- Charity Majors, Liz Fong-Jones, George Miranda - Observability Engineering (O'Reilly) - In-depth architectural analysis of high-cardinality telemetry and structured event pipelines.