42OpenTelemetry & Grafana
What you will master here
- The three pillars: logs, metrics, traces
- OpenTelemetry (OTel) — the open standard
- Spans, traces, propagation
- Instrumenting Node / Java / Python apps
- OTel Collector pipeline
- Grafana + Prometheus + Loki + Tempo stack
- SLI / SLO / error budget
- SDET-specific use: assert on telemetry from tests
42.1 Three pillars of observability
| Pillar | What | Tool stack |
|---|---|---|
| Logs | Discrete events with context | Loki, Elasticsearch, CloudWatch Logs |
| Metrics | Numeric time-series (latency, count, rate) | Prometheus, Datadog, CloudWatch Metrics |
| Traces | Request flow across services as a tree of spans | Tempo, Jaeger, Datadog APM |
42.2 OpenTelemetry
OTel is the open standard (CNCF) for generating telemetry. One SDK in your app emits traces+metrics+logs in OTLP format; you can swap backends without rewriting instrumentation.
Components
- SDK — library you import in your app to create spans/metrics
- Auto-instrumentation — JS/Python/Java agents that wrap common libs (Express, http, pg, kafka) without code changes
- Collector — separate process that receives OTLP, processes, exports to backends
- Exporters — backend-specific writers (OTLP, Prometheus, Jaeger, Datadog)
42.3 Spans and traces
// Node manual instrumentation
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('orders-service');
async function placeOrder(input) {
const span = tracer.startSpan('placeOrder');
span.setAttribute('user.id', input.userId);
try {
const order = await db.create(input);
span.setAttribute('order.id', order.id);
return order;
} catch (e) {
span.recordException(e);
span.setStatus({ code: 2 /* ERROR */ });
throw e;
} finally {
span.end();
}
}
A trace = a tree of spans sharing the same traceId. Propagation happens via the traceparent HTTP header (W3C Trace Context standard). Downstream services join the same trace.
42.4 Collector pipeline
# otel-collector.yaml
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
timeout: 1s
attributes:
actions:
- key: env
value: production
action: insert
exporters:
prometheus:
endpoint: 0.0.0.0:8889
otlp/tempo:
endpoint: tempo:4317
tls: { insecure: true }
loki:
endpoint: http://loki:3100/loki/api/v1/push
service:
pipelines:
traces: { receivers: [otlp], processors: [batch, attributes], exporters: [otlp/tempo] }
metrics: { receivers: [otlp], processors: [batch], exporters: [prometheus] }
logs: { receivers: [otlp], processors: [batch], exporters: [loki] }
42.5 Grafana
Visualisation layer. Connects to Prometheus (metrics), Loki (logs), Tempo (traces), and others. Dashboards combine all three in one view.
PromQL essentials
rate(http_requests_total[5m]) # req/s
sum by (status) (rate(http_requests_total[5m])) # by status
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m]))) # p95
1 - (sum(rate(http_requests_total{status=~"5.."}[5m])) /
sum(rate(http_requests_total[5m]))) # availability
42.6 SLI / SLO / error budget
| Term | Meaning |
|---|---|
| SLI (Service Level Indicator) | A measurable signal (e.g. p95 latency, availability) |
| SLO (Service Level Objective) | Target for an SLI ("99.9% availability over 30 days") |
| SLA (Service Level Agreement) | Contractual promise to customers (often weaker than internal SLO) |
| Error budget | 1 - SLO. 99.9% SLO → 0.1% allowed downtime = ~43 min/month |
If error budget is spent for the month, new feature deploys halt — only reliability work continues until next window. Mechanism to balance velocity vs reliability.
42.7 SDET use cases for OTel
- Assert that a test request produced a specific span graph (right services were called, in the right order, with expected attributes)
- Verify error spans on failure paths
- Check that latency stays within SLO budget under load
- Use trace ids in test output to link a failed test to the production trace UI
test('order flow generates correct trace', async ({ request }) => {
const r = await request.post('/api/orders', {
data: {/*...*/},
headers: { 'x-test-correlation': 'abc-123' },
});
expect(r.status()).toBe(201);
// Wait for trace ingestion
await expect.poll(async () => {
const traces = await fetch('http://tempo:3200/api/search?tags=test=abc-123').then(r => r.json());
return traces?.traces?.length ?? 0;
}, { timeout: 10_000 }).toBeGreaterThan(0);
const trace = await fetch('http://tempo:3200/api/traces/...').then(r => r.json());
const spans = trace.batches.flatMap(b => b.scopeSpans).flatMap(s => s.spans);
const names = spans.map(s => s.name);
expect(names).toContain('checkout-handler');
expect(names).toContain('charge-stripe');
expect(names).toContain('email-confirmation');
});
42.8 Logs vs metrics vs traces — when each?
| Use | Pick |
|---|---|
| "What happened at this exact moment for user X?" | Logs |
| "Is the system slow/erroring overall?" | Metrics |
| "How did this request flow through services?" | Traces |
Module 66 — OTel/Grafana Q&A
What are the three pillars of observability?
Logs (discrete events), metrics (numeric time-series), traces (request flow across services). Each answers different questions; modern observability stacks correlate them via trace ids.
What is OpenTelemetry?
Open CNCF standard for generating telemetry. SDK + auto-instrumentation + collector + OTLP wire format. Decouples instrumentation from backend — write code once, swap backends without changes.
What's a span?
A single unit of work in a trace — a service call, a DB query, an internal function. Has start/end time, attributes, status, optional events. Spans nest into a tree to form a trace.
What's W3C Trace Context?
Standard HTTP headers (
traceparent, tracestate) that propagate trace identity across services. Lets the receiving service add its spans to the same trace as the caller.What's the OTel Collector?
Standalone agent/gateway that receives telemetry from apps, batches/processes (sampling, filtering, redaction), and exports to backends. Lets you swap backends or fan out to many without changing the app.
What's PromQL rate()?
Per-second rate of increase of a counter over a window —
rate(http_requests_total[5m]) gives requests/sec averaged over 5 min. Counters are monotonic; rate makes them human-readable.SLI vs SLO vs SLA?
SLI: measurable signal. SLO: internal target on that signal. SLA: external contractual promise (usually weaker than SLO). Error budget = 1-SLO; spent budget pauses feature work.
How do you compute p95 latency in PromQL?
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m]))). Requires the metric to be a histogram (bucketed).How would an SDET verify a request hit the expected services?
Tag the test request with a unique correlation id. Wait for trace ingestion in Tempo/Jaeger. Query traces by that tag; assert expected span names + attributes appear. Catches "the request was supposed to call service X" failures that black-box tests miss.
What is the LGTM stack?
Grafana Labs' acronym: Loki (logs), Grafana (UI), Tempo (traces), Mimir/Prometheus (metrics). Open-source observability backend, one of the dominant stacks.