Wise Hustlers — Digital Product & App Development Studio Logo
Get Consultation
By Wise Hustler Admin•10/24/2026•18 min read

Observability vs Monitoring Explained: Why Logs and Dashboards Alone Don't Cut It Anymore

Observability vs Monitoring Explained: Why Logs and Dashboards Alone Don't Cut It Anymore

# 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 Boundaries

Metrics: 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 1729728000

Time-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 DimensionTraditional MonitoringModern Observability
Primary Question"Is the service healthy right now?""Why did service $X$ fail for user $Y$ under condition $Z$?"
System PhilosophyPredicts specific failure states ahead of time.Models system outputs to decode emergent, unknown behaviors.
Core TelemetryAggregate metrics (counters, gauges, timers), threshold alerts.Correlated traces, structured wide events, high-cardinality attributes.
Cardinality ToleranceLow. Restricted to low-cardinality labels (region, status code).High. Tolerates millions of unique values (UUIDs, IP addresses, hashes).
Failure Modes AddressedKnown Unknowns (host down, disk full, CPU saturation).Unknown Unknowns (asynchronous race conditions, tail-latency locks).
Data TopologyDisconnected data silos (separate metric graphs and log streams).Unified causal graphs (traces act as the index for logs and metrics).
Incident Response StanceReactive. Responds when a predefined threshold is breached.Exploratory. Allows ad-hoc multidimensional slicing without new code.
MTTR ImpactHigh. 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 return 200 OK in 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 CONTENTION

Inspecting 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

Related articles