What ObservabilityOps is
Link to section “What ObservabilityOps is”In 30 seconds: the short answer
Link to section “In 30 seconds: the short answer”ObservabilityOps has no standard definition: searches on 2026-10-16 found no definition, standards body, certification or vendor programme for the label (see term status). In this Atlas it means running observability as an internal product: operating the telemetry pipeline and back ends, setting instrumentation standards, keeping cardinality and cost within budgets and keeping alerts useful, so that any engineer can answer a new question about production quickly, from trustworthy data, at a predictable cost.
Observability itself is well defined. OpenTelemetry describes it as “the ability to understand the internal state of a system by examining its outputs”, typically traces, metrics and logs.[1] DORA separates monitoring, which watches predefined metrics or logs, from observability, which explores properties and patterns “not defined in advance”.[2] ObservabilityOps is the operational work that keeps that ability available and affordable.
For engineers who run the stack, SREs and service owners who depend on it, and leaders who pay for it.
At a glance
Link to section “At a glance”| Item | Summary |
|---|---|
| Term status | Ambiguous. Almost unused, with two plausible readings; this page takes “operating the observability system”. The practice exists under other names: GitLab’s Observability team, the UK Ministry of Justice’s Observability Platform team, New Relic’s “Observability Center of Excellence”.[3][4][5] |
| Goals | Telemetry that answers new questions; alerts worth acting on; predictable, allocated cost; trustworthy data (no silent loss, no secrets). |
| In scope | Instrumentation standards; telemetry pipelines, including log collection and trace propagation; metric, log, trace and profile back ends; cardinality, sampling and retention; dashboard and alert design; monitoring the monitoring; cost governance; tools. Terms are defined in the glossary. |
| Out of scope | SLO targets and error-budget policy → SRE; incident roles and communications → IncidentOps; ML event correlation → AIOps; security telemetry and forensic retention → SecDataOps; resilience in code → AppOps; the cloud bill → FinOps. |
| Prerequisites | Running services in production (DevOps); SLI and SLO vocabulary (SRE). |
| Key outputs | Instrumentation standard, pipeline with SLOs, per-team telemetry budgets, reviewed alerts, unit-cost report. |
The problem it solves
Link to section “The problem it solves”When nobody owns the observability system, three failures recur.
- Cost grows faster than value. Telemetry volume follows traffic and code, not the questions anyone asks. In Grafana Labs’ 2025 survey (1,255 self-selected respondents, so indicative at best) mean reported observability spend was 17% of compute infrastructure spend (median 10%), and 37% named “Costs too much” as a concern.[6]
- Noise drowns signal. In the same vendor’s 2026 survey, the top three concerns were complexity/overhead (38%), signal-to-noise challenges (34%) and cost (31%).[7] The human side of alert noise is covered in AIOps and IncidentOps.
- The tools fail when they are needed most. One label filled with user-supplied values can create millions of series. Cloudflare describes how such a “cardinality explosion” can make Prometheus run out of memory, so that “you lose all observability as a result”.[8]
| Operating reality | Nobody owns it | Run as a product |
|---|---|---|
| Instrumentation | Each team picks libraries and names | One standard (OpenTelemetry and semantic conventions), defaults in templates |
| Data volume | Whatever the code emits | Budgets per team; sampling and retention set by value |
| Alerts | Accumulate; nobody deletes them | Reviewed against “Every page should be actionable”[9] |
| Platform health | Discovered during an incident | Platform SLOs and end-to-end checks |
| Cost | One shared bill | Shown per team and per unit of work |
Term status and the two readings
Link to section “Term status and the two readings”The label could mean either of two things:
- Operating with observability: running production by looking at telemetry, which duplicates SRE, IncidentOps and AIOps.
- Operating the observability system: treating telemetry as an internal product with users, SLOs, a budget and governance. This page takes this reading.
What it is not
Link to section “What it is not”- Not the product “ObserveOps”. Motadata sells an observability platform with that name.[11]
- Not SRE. SRE sets reliability targets and policy; ObservabilityOps runs the measurement system they depend on.
- Not monitoring under a new name. Exploring what was “not defined in advance”[2] needs high-dimensional data, which is where most cost and governance problems sit.
- Not a team that does observability for everyone. DORA writes: “Monitoring systems should not be confined to a single individual or team within an organization.”[2] A central team can run those systems, provided every team can query and alert on them itself. Service teams still instrument their code and own their alerts.
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”| Year | Milestone |
|---|---|
| 1960 | Kalman defines observability in control theory: internal state inferred from outputs (as cited by the CNCF whitepaper)[12] |
| 2012, 2015 | Brendan Gregg publishes the USE method; Tom Wilkie creates the RED method[13][14] |
| 2016 | Google’s SRE book: four golden signals, paging on symptoms[9] |
| 2018 | Prometheus graduates in the CNCF[15] |
| 2019 | OpenTracing and OpenCensus merge into OpenTelemetry, accepted by the CNCF in May[1][16] |
| 2020 | W3C Trace Context becomes a Recommendation (edited Recommendation 2021)[17] |
| 2023 | Prometheus 2.47 adds experimental OTLP ingestion;[18] the CNCF TAG Observability whitepaper v1.0 prefers “primary signals” to “three pillars”[12] |
| 2024 | Prometheus 3.0 adds OpenTelemetry interoperability features; Jaeger v2 is rebuilt on the OpenTelemetry Collector[19][20] |
| 2026 | OpenTelemetry Profiles enters public alpha (March); OpenTelemetry graduates (CNCF moved it on 11 May; announced 21 May)[21][16][22] |
No source dates the first observability teams; one practitioner wrote in 2022 that the concept was “relatively new”.[23]
Glossary and mental model
Link to section “Glossary and mental model”| Term | Meaning on this page |
|---|---|
| Telemetry, signal | Data a system emits about itself: metrics, logs, traces, profiles (“primary signals” in the CNCF whitepaper).[12] |
| Time series, sample | A metric name plus one unique set of labels is one time series; each timestamped value is a sample.[8] |
| Label, attribute, cardinality | A key-value dimension (Prometheus label, OpenTelemetry attribute); cardinality is its number of distinct values, or of a metric’s label combinations. |
| Active series | Series a time-series database holds in memory; the main driver of its memory use. |
| Span, trace | One timed operation; the tree of spans for one request, linked by propagated context such as the W3C traceparent header.[17] |
| Exemplar | A trace ID attached to a metric sample, linking a spike to an example request.[12] |
| Semantic conventions | OpenTelemetry’s standard names for attributes, spans and metrics (v1.44.0, October 2026).[24] |
| OTLP, Collector | OpenTelemetry’s protocol, and its proxy that receives, processes and exports telemetry in pipelines.[25] |
| Head or tail sampling | Keeping a trace or not at its start (cheap, blind to the outcome) or after seeing it (can keep every error; stateful).[26] |
| RED, USE, golden signals | Rate, errors, duration per service;[14] utilisation, saturation, errors per resource;[13] latency, traffic, errors, saturation.[9] |
| Context propagation, baggage | Passing trace context, and optional key-value baggage, to the next service in HTTP or message headers.[27] |
| Telemetry budget | This Atlas’s term: an agreed limit per team on series, log volume or spend, with an exception path. |
One slow request, four signals (illustrative). A checkout request takes 4 seconds instead of 300 ms.
| Signal | What you see | What it answers | Main cost driver |
|---|---|---|---|
| Metric | p99 of http.server.request.duration rises for one route |
Is it happening, how often, since when? | Active series (label combinations) |
| Trace | The request’s spans: 3.6 s waiting in the payment client | Where in the call path did the time go? | Spans kept (sampling rate) |
| Log | A structured line with trace_id and “retrying payment provider” |
What did the code say at that moment? | Bytes ingested and indexed |
| Profile | CPU and memory per function across many requests | Which code is expensive in aggregate? | Sampling frequency and retention |
Mental model: a telemetry supply chain. Producers emit signals; a pipeline moves, enriches, redacts and samples them; back ends store them; consumers (dashboards, alert rules, queries, AIOps) turn them into decisions. ObservabilityOps owns the middle. Its unit of value is a question answered quickly; its unit of cost is the bytes and series kept.
How ObservabilityOps relates to other Ops
Link to section “How ObservabilityOps relates to other Ops”Diagram source (Mermaid)
flowchart TB
subgraph prod["Producers"]
A["`AppOps:
service code`"]
K["`KubernetesOps, CloudOps:
infrastructure`"]
end
F["`FinOps:
cost allocation`"]
O["`ObservabilityOps: pipeline,
storage, standards, budgets`"]
SD["`SecDataOps:
security telemetry`"]
subgraph cons["Consumers"]
S["`SRE:
SLOs`"]
I["`IncidentOps:
response`"]
AI["`AIOps:
correlation`"]
end
A --> O
K --> O
F -. "budgets" .-> O
O -. "shared tools" .- SD
O --> S
O --> I
O --> AI
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
class O core
class A,K,S,I,AI,F,SD neutral
Text equivalent: service owners (AppOps) and infrastructure teams (KubernetesOps, CloudOps) produce telemetry; ObservabilityOps runs the pipeline, storage, standards and budgets; SRE, IncidentOps and AIOps consume the data. Dotted lines connect FinOps, which sets budgets, and SecDataOps, which runs security telemetry on similar tools.
| Neighbour | Shared scope | This page’s focus | Go there for |
|---|---|---|---|
| SRE | SLIs, alerting | The measurement system; alert hygiene | SLO targets, error-budget policy, burn-rate thresholds |
| IncidentOps | Dashboards during incidents | Available, trustworthy data | Roles, communications, MTTA and MTTR |
| AIOps | Event data | Clean, labelled, affordable input | Correlation models and how to evaluate them |
| SecDataOps | Pipelines and tools | Operational telemetry | Security schemas, coverage, forensic retention |
| AppOps | Instrumentation | Standards, libraries, defaults | Resilience patterns, the owner’s runbook |
| PlatformOps | Platform as a product | One capability (the CNCF lists observability)[28] | Portals, golden paths |
| FinOps | Cost | Telemetry unit cost and budgets | Allocating the cloud bill, commitments |
Principles and operating model
Link to section “Principles and operating model”Charter and principles (this Atlas’s proposal)
Link to section “Charter and principles (this Atlas’s proposal)”No official ObservabilityOps manifesto exists. Mission: every engineer can answer a new question about production within minutes, from trustworthy data, at an agreed cost.
- Collect for a consumer. Each signal serves a dashboard, an alert or a known debugging question. Signals used by no prebaked dashboard and no alert “are candidates for removal” (Google SRE book).[9]
- Open standards at the edges. Instrument with OpenTelemetry so the back end can change: “You own the data that you generate.”[1]
- Bounded dimensions in metrics, rich context in events. Unbounded identifiers go into traces and logs, never metric labels.[29][12]
- Symptoms page; causes explain. Page on what users feel and use cause-level data to diagnose.[9][30]
- The platform has SLOs and is monitored end to end.[30]
- Cost is visible and allocated to the teams and units of work that generate it.
- Enable, do not gatekeep. Self-service within guardrails; the platform team is not the only team that can query or alert.[2]
- Minimise and redact at the edge. Personal data and secrets do not enter the pipeline unless needed.[31]
Invariants: dropped data is counted and alerted on; no secrets in telemetry; every page has an owner and a runbook. Non-goals: owning other teams’ SLOs, being on call for their services, reading every log.
Building blocks
Link to section “Building blocks”| Layer | Typical components |
|---|---|
| Instrumentation | OpenTelemetry SDKs and zero-code agents, infrastructure exporters, eBPF instrumentation |
| Collection and pipeline | Collector agents and gateways; optionally a message queue |
| Storage | Time-series database, log, trace and profile stores, object storage |
| Query, dashboards, alerting | PromQL, LogQL and similar; dashboards as code; rule evaluation and paging |
| Governance | Standards, budgets, signal catalogue, access control, cost reports |
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”- Observability platform team: a product owner and engineers who run the pipeline and back ends. In a small organisation this may be one person, as at Mastodon (Case 1).[32]
- Community structure: New Relic’s guidance proposes a core team (typically one to three people), a council that ratifies standards and a volunteer guild.[5] It is one vendor’s model, useful when a full team is not justified.
| Activity | Platform team | Service team | SRE | Security / DPO | FinOps |
|---|---|---|---|---|---|
| Instrumentation standard and SDK defaults | A/R | C | C | C | I |
| Instrumenting a service; its dashboards and alerts | C | A/R | C | I | I |
| Pipeline and backend operations; platform on-call | A/R | I | C | I | I |
| SLO targets and error-budget policy | C | R | A | I | I |
| Redaction rules and retention policy | R | C | C | A | I |
| Telemetry budget and cost allocation | R | C | I | I | A |
| Tool selection and contracts | A/R | C | C | C | C |
R = responsible, A = accountable, C = consulted, I = informed. Adjust to your organisation, but leave no row blank.
Lifecycle of a signal (Day 0, Day 1, Day 2)
Link to section “Lifecycle of a signal (Day 0, Day 1, Day 2)”Diagram source (Mermaid)
flowchart TB
P["Propose: owner, question it answers, expected series or volume"]
R{"Meets standard and budget?"}
X["Redesign: move IDs to span attributes, or request a time-limited exception"]
I["Instrument and ship through the pipeline"]
U["Use: dashboards, alerts, ad hoc queries"]
Q{"Used in 90 days?"}
T["Downsample, move to a cheaper tier, or retire"]
P --> R
R -- "yes" --> I
R -- "no" --> X
X --> P
I --> U
U --> Q
Q -- "yes" --> U
Q -- "no" --> T
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
class P,I,U core
class R,Q decision
class X,T risk
Text equivalent: a new signal is proposed with its owner, question and expected size. If it meets the standard and budget it is instrumented and used; otherwise it is redesigned (for example, identifiers moved to span attributes) or given a time-limited exception, and proposed again. A signal unused for 90 days is downsampled, moved to a cheaper tier or retired.
- Day 0 (design): choose standards, architecture, tools, budget rules and retention per data class.
- Day 1 (onboarding): templates give each service OpenTelemetry defaults, a RED dashboard and a symptom alert.
- Day 2 (operations): capacity, upgrades, reviews and pruning. Collector releases are frequent and rename components (see the tooling landscape).[33][34] Mastodon upgrades “within a day or two of each release” and sees breaking changes as a sign of healthy development, provided you stay current.[32]
Operating model and governance
Link to section “Operating model and governance”| Ceremony | Cadence | Inputs | Output |
|---|---|---|---|
| Platform health review | Weekly | Platform SLIs, dropped data, queue use, pending upgrades | Actions, upgrade plan |
| Budget review (showback) | Monthly | Series, log volume and cost per team | Budget changes, exceptions with expiry |
| Alert review | Monthly per team | Pages, each labelled actionable or not | Deleted, merged or fixed alerts |
| Signal pruning | Quarterly | Signals unused for 90 days | Retirements and tier moves |
| Standards council | Quarterly | Proposals (new conventions, tools) | Architecture decision records |
Change control: pipeline and rule configuration lives in Git, is validated in CI (otelcol validate, promtool, a linter such as
pint)[25][35][36] and is deployed by reconciliation, as Mastodon does with the OpenTelemetry Operator and Argo
CD.[32] Escalation: a platform outage is an incident like any other (IncidentOps); budget exceptions
end with whoever owns the cost.
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture
Link to section “Reference architecture”Diagram source (Mermaid)
flowchart TB
subgraph src["1. Producers"]
APP["`Services with
OpenTelemetry SDKs`"]
INF["`Hosts, clusters,
container logs`"]
end
subgraph agt["2. Agents"]
AG["`Agent per node: metadata,
memory limit, logs, routing`"]
end
subgraph gwt["3. Gateway replicas, mTLS"]
TS["`Tail sampling:
spans by trace ID`"]
SM["`Span metrics:
spans by service`"]
PL["`Metrics, logs, profiles:
redact, filter`"]
end
T[("`Trace
store`")]
M[("`Metric
store`")]
L[("`Log
store`")]
PR[("`Profile
store`")]
D["`Dashboards
and queries`"]
R["`Alert rules
and routing`"]
W["`Meta-monitoring:
independent watcher`"]
APP --> AG
INF --> AG
AG -- "by trace ID" --> TS
AG -- "by service" --> SM
AG -- "any replica" --> PL
TS --> T
SM --> M
PL --> M
PL --> L
PL --> PR
M --> D
L --> D
T --> D
PR --> D
M --> R
W -.-> PL
W -.-> R
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
class AG,TS,SM,PL core
class APP,INF,M,L,T,PR,D,R neutral
class W risk
Text equivalent: services and infrastructure send telemetry to a Collector agent on each node, which adds metadata, limits memory, collects container logs and sends data to gateway replicas over mutual TLS. Spans travel twice: by trace ID to tail sampling, which stores the kept traces, and by service to the span-metrics connector, which writes RED metrics. Metrics, logs and profiles go to any replica for redaction and filtering. Dashboards read all stores, alert rules mostly metrics; an independent watcher checks the gateways and the alert path.
| Component | Responsibility | Main risk | Control |
|---|---|---|---|
| SDKs | Emit signals with semantic conventions; service.name is required[37] |
Missing names; unbounded attributes; lost context | Templates, CI checks |
| Agent | Host signals, container logs, Kubernetes metadata; route spans | Memory pressure on nodes | memory_limiter first;[38] resource limits |
| Gateway | Central credentials and policy | One more thing that can fail; latency; cost[39] | Two or more replicas; sending queues; persistent queue on critical paths[40] |
| Tail sampling | Keep errors and slow traces, sample the rest | Stateful; all spans of a trace must reach one instance[41] | load_balancing exporter with routing_key: traceID[42] |
| Span metrics | RED metrics from every span, before sampling | Several replicas writing the same series[39][43] | load_balancing exporter with routing_key: service[42] |
| Back ends | Store and query | Cardinality; retention cost; local Prometheus is “not clustered or replicated”[44] | Limits, long-term store, tiers |
| Alerting | Evaluate rules, route, page | Noise; silent failure | Symptom-based rules; meta-monitoring |
Alternatives. Sending from SDKs straight to a back end is simplest but leaves no place for central policy; a single Collector tier, as at Mastodon (Case 1), avoids routing altogether. A gateway tier buys central credentials and policy at the price of “one more thing to maintain and that can fail”, latency and resources. It must also respect the single-writer rule: every metric stream needs “a single writer and a globally unique identity”, or the series shows gaps, jumps or out-of-order errors.[39] That rule is why span metrics are routed by service.
Trust boundaries. Anything that can reach an OTLP endpoint can create series and cost. The Collector binds to localhost by
default; documentation examples use 0.0.0.0 only for convenience.[25] Artefact 1 states who can reach each endpoint.
Logs and traces can contain personal data, so query access needs roles and tenancy.
End-to-end workflow: onboarding a service
Link to section “End-to-end workflow: onboarding a service”Diagram source (Mermaid)
sequenceDiagram
participant BO as Budget owner
participant ST as Team
participant OB as ObsOps
participant CI as CI
ST->>OB: Request: signals, size
OB->>OB: Check standard, PII, budget
alt Within budget
OB->>CI: Merge routes, limits
CI->>CI: Validate, test rules
CI-->>OB: Deployed
OB-->>ST: Dashboard, alerts live
else Over budget or risky
OB-->>ST: Redesign or seek exception
ST->>BO: Cost and expiry date
BO-->>ST: Approve or reject
ST->>OB: Approved exception
OB->>CI: Merge with expiry date
end
OB->>ST: Review after 30 days
Text equivalent: the service team (Team) asks the platform team (ObsOps) to onboard signals of a given size. ObsOps checks the standard, personal-data rules and budget. On the happy path ObsOps merges routes and limits, CI validates them and tests the alert rules, and the team gets a template dashboard and alerts. On the exception path the team redesigns or asks the budget owner for a time-limited exception, which is merged with its expiry date. ObsOps reviews usage and noise after 30 days.
Acceptance criteria for “onboarded”: telemetry arrives under the right service.name; the standard dashboard renders; at least
one symptom alert has a runbook and an owner; series and log volume are inside budget; a test request yields one unbroken trace;
nothing in the debug output matches the redaction patterns.
Practices, frameworks and tools
Link to section “Practices, frameworks and tools”Core practices and playbooks
Link to section “Core practices and playbooks”| Practice | Goal | Owner | Verification |
|---|---|---|---|
| Instrumentation standard | Same names everywhere | Platform team | Share of telemetry under unknown_service near zero |
| Cardinality control | Stable, affordable metrics | Platform and service teams | Series per team within budget |
| Sampling | Keep the informative traces | Platform team | No dropped-too-early traces; RED metrics unaffected |
| Context propagation | Whole traces across every hop | Platform team sets it; service teams apply it | A test request through every hop yields one trace |
| Log pipeline | Parsed, correlated, affordable logs | Platform team | Share of log records with severity and trace_id set |
| Retention tiers | Pay for age only when needed | Platform team with data protection | Storage cost per tier; policy met |
| Dashboard and alert design | Fast diagnosis, few pages | Service teams | Page actionability trend |
| Meta-monitoring | Know when the platform is blind | Platform team | Alert-path heartbeat never silently stops |
Instrumentation standard. Use OpenTelemetry SDKs and semantic conventions. service.name is required; when it is missing, SDKs
fall back to unknown_service: plus the executable name,[37] so its share is a cheap hygiene metric. Record HTTP routes as templates: the
conventions say “the URI path can NOT substitute” http.route.[45] Emit events as logs correlated with the current span;
OpenTelemetry is deprecating the Span Event API.[46]
Cardinality control. Prometheus advises that “every unique combination of key-value label pairs represents a new time series” and
that labels must not hold “user IDs, email addresses, or other unbounded sets of values”.[29] Keep a metric’s cardinality
below about 10 and investigate anything above 100.[47] Loki asks the same of log stream labels: dynamic labels “ideally limited
to tens of values”, and no trace or order IDs.[48] Scrape limits (sample_limit, label_limit) fail the whole scrape when
exceeded,[49] so watch scrape_series_added and scrape_samples_post_metric_relabeling.[50] High-cardinality context is
not bad in itself, only in metric labels: event stores treat user or order IDs as the key to isolating a problem.[51]
Sampling. OpenTelemetry suggests sampling from about 1,000 traces per second, and avoiding it at tens of small traces per second
or where regulation forbids dropping data. Head sampling is cheap but cannot guarantee that every error trace is kept; tail sampling
can, but it is “difficult to operate”.[26] The Collector’s
tail sampler needs every span of a trace on one instance, and its sampling_trace_dropped_too_early metric shows traces lost before
a decision.[41] Derive request rates from all spans before the sampler drops any, since rates counted from sampled spans fall by
the sampling factor (Artefact 1). Mastodon’s published configuration uses the same order: trace statistics from every span
first, then tail sampling.[32]
Context propagation. A trace survives only if every hop passes its context on. OpenTelemetry SDKs default to the W3C
tracecontext and baggage propagators (OTEL_PROPAGATORS); B3 is an option.[52] Fix the list in the standard, and while
some services still use B3 configure both (tracecontext,baggage,b3multi). Traces break in three places:
- Queues and batches. A span has one parent, so OpenTelemetry’s messaging conventions (still in development) link consumer spans to each message’s creation context with span links.[53] The queue client must carry context in message headers.
- Intermediaries. W3C Trace Context requires a vendor that receives
traceparentandtracestateto send them on;[17] a proxy, API gateway or mesh that drops them starts a new trace behind it. - Mixed instrumentation. A service on another propagator produces fragments that look like separate traces.
Trust and baggage. OpenTelemetry advises caution with incoming context from external sources (forged headers can manipulate
tracing data) and with sending context to external endpoints (IDs and baggage reveal internal details).[27] Instrumentation
adds baggage to most outgoing requests, so it can reach third-party APIs, and nothing checks its integrity.[54] Keep
credentials, API keys and personal data out of it,[27] although OpenTelemetry’s baggage page lists user IDs among typical
uses:[54] allow-list baggage keys. A workable policy starts a new trace at the public edge, which W3C Trace Context
permits,[17] and strips baggage at egress.
Log pipelines. Four decisions keep logs useful and affordable.
- Structure. OpenTelemetry defines a structured log by its stable schema, not by valid JSON alone; its log records carry
TraceId,SpanIdand severity as top-level fields.[55] Require them in every log line: SDKs can inject the IDs;[27] services that print to stdout must write them as fields. - Collection. Kubernetes describes a node-level logging agent, run as a DaemonSet, that reads container log files (by default
under
/var/log/pods).[56] For the Collector, OpenTelemetry calls thefile_logreceiver “the de facto solution”; itscontainerparser reads docker, CRI-O and containerd formats and adds pod metadata.[57][58] Fluent Bit is an alternative agent.[59] Without a storage extension, offsets live only in memory and a restarted agent cannot resume.[60] - Parsing and multiline. Parse JSON lines into severity and trace fields at the agent (Artefact 1). Join stack traces with the
recombineoperator after the container parser,[61] or better, log each exception as one record. - Routing by level. The filter processor drops records matching a condition such as
log.severity_number < SEVERITY_NUMBER_WARN.[62] Records whose severity was never parsed have severity number 0 (SEVERITY_NUMBER_UNSPECIFIED) and match it too,[63] so exclude 0 or parse severity first.
Retention tiers. Prometheus keeps 15 days by default on single-node local storage.[44] A typical design keeps raw data hot briefly, downsampled metrics warm for months and compressed logs in object storage as policy requires; security retention belongs to SecDataOps.
Dashboards and alerts. Give every request-driven service a RED view and every resource a USE view, as Wilkie recommends.[14][13] Design the drill-down: alert, dashboard, exemplar trace, that trace’s logs.[12] Page on symptoms, use tickets for slow problems, and test each page against the SRE book’s questions (urgent, actionable, user-visible).[9] Burn-rate thresholds belong to the SLO owner (SRE).[64]
Meta-monitoring. Prometheus recommends alerts that the monitoring works, and notes that one black-box test of the alert path “is better than individual alerts on each” component.[30] The usual implementation is a dead man’s switch: an always-firing alert whose absence pages through another channel.
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”| Name | Kind | Status (October 2026) | Use it for |
|---|---|---|---|
| OpenTelemetry specification, OTLP, semantic conventions | Open standard (CNCF graduated) | Varies by language and signal; logs still “Development” in the Python and JavaScript SDKs[65] | Instrumentation and transport |
| W3C Trace Context | W3C Recommendation (2020; edited 2021)[17] | Stable; Level 2 in progress[66] | Trace propagation across services |
| Prometheus exposition format and PromQL | De facto open standard | Prometheus 3.x; native histograms stable since v3.8.0[67] | Metrics |
| Golden signals, RED, USE | Methods | Stable | Deciding what to measure |
| Google SRE monitoring and SLO-alerting guidance | Industry recommendation | 2016 book, 2018 workbook | Alert design |
| CNCF TAG Observability whitepaper | Community guidance | v1.0 (2023) | Concepts and signal trade-offs |
None is mandatory; what binds comes from contracts and law, such as privacy rules on personal data in logs.
Tooling landscape (as of October 2026)
Link to section “Tooling landscape (as of October 2026)”| Job | Representative open-source options (latest release checked 2026-10-16) | Notes |
|---|---|---|
| Collect and process | OpenTelemetry Collector v0.163.x (v0.163.1, 2026-10-16);[68] Grafana Alloy, a Collector distribution;[69] Fluent Bit (CNCF graduated, under Fluentd);[59] Vector[70] | Pick one primary pipeline; the Collector is the vendor-neutral default |
| Metrics | Prometheus v3.15.0 (CNCF graduated 2018);[71][15] Thanos (CNCF incubating) for long-term storage[72] | Local Prometheus storage is single-node[44] |
| Logs | Grafana Loki v3.7.8;[73] Elasticsearch v9.5.5[74] | Loki needs low-cardinality labels; put high-cardinality fields in structured metadata[48] |
| Traces | Jaeger v2.22.0, rebuilt on the Collector;[75][20] Grafana Tempo v3.1.0[76] | Jaeger recommends the standard Collector as agent[77] |
| Profiles | OpenTelemetry Profiles (public alpha since March 2026)[21] | Early; expect format changes |
| Visualise and alert | Grafana v13.2.4;[78] Prometheus Alertmanager; pint for rule linting[36] | Keep rules and dashboards in Git |
| Commercial back ends | Many accept OTLP natively (non-exhaustive list)[79] | Compare pricing on your own data |
Selection criteria. OTLP at the edge, so you can switch back ends; the cost model (per series, per GB, per host); query
languages your engineers can learn; licence (Grafana, Loki and Tempo moved from Apache 2.0 to AGPLv3 in 2021;[80] Elastic added AGPL next to ELv2 and SSPL in 2024[81]); and the burden of running stateful databases.
Version churn is real: the Collector renamed otlp to otlp_grpc (v0.144.0) and many contrib components to
snake_case,[33][34] and native histograms went from experimental in Prometheus 3.0 to stable in
3.8.[19][67] Pin versions. Defaults move too: current Collector documentation batches in the exporter’s sending queue (sending_queue →
batch), not in the beta batch processor, which holds data in memory outside any persistent queue.[25][82]
Automation and AI
Link to section “Automation and AI”Automate governance first: configuration validation, rule unit tests, cardinality checks in CI, budget reports and lists of unused signals. Anomaly detection and event correlation, and when simple rules beat them, are the subject of AIOps. Natural-language query assistants need their generated queries checked, and anything that reads telemetry follows the same redaction rules. Observability of LLM applications is covered in LLMOps.
ObservabilityOps in practice
Link to section “ObservabilityOps in practice”Reference implementation
Link to section “Reference implementation”Assumptions: services on Kubernetes, instrumented with OpenTelemetry; Collector contrib 0.162 or later; Prometheus 3.x; an
OTLP-capable back end at the placeholder backend.example.internal; certificates from a platform CA. Three artefacts follow; Lab 1 runs the third.
Artefact 1: agent and gateway Collector configuration
Link to section “Artefact 1: agent and gateway Collector configuration”Two files implement the reference architecture. Agents send each span twice: by trace ID to the tail
sampler and by service to a second receiver for span metrics;[39][42] metrics and logs go to any replica. The agent also
collects container logs, parses JSON lines and drops DEBUG and TRACE records. Names follow the 2026 snake_case
renames.[34] span_metrics and filter are alpha, and the span-metrics duration unit is changing from milliseconds to seconds;[43][62]
redaction is beta for traces, alpha for logs and metrics.[83] MY_POD_IP comes from the downward API. The DaemonSet
mounts /var/log/pods read-only[57] and a writable hostPath for file_storage, so log offsets survive a replaced
pod.[84]
Example status: YAML syntax-checked; not executed (requires Kubernetes, certificates from a platform CA and a back end).
# agent.yaml: DaemonSet, one Collector per node
receivers:
otlp:
protocols:
grpc:
endpoint: ${env:MY_POD_IP}:4317 # hostPort; no TLS or auth (see below)
file_log: # container logs on this node
include: [/var/log/pods/*/*/*.log]
exclude: [/var/log/pods/*/otel-agent/*.log] # not the agent's own logs
start_at: end
include_file_path: true
storage: file_storage # resume from saved offsets after a restart
operators:
- type: container # docker, CRI-O, containerd; adds pod metadata
- type: json_parser # services log JSON with level and trace IDs
if: 'body startsWith "{"'
severity: {parse_from: attributes.level}
trace:
trace_id: {parse_from: attributes.trace_id}
span_id: {parse_from: attributes.span_id}
extensions:
file_storage:
directory: /var/lib/otelcol/file_storage # a hostPath: offsets outlive the pod
create_directory: true
processors:
memory_limiter: {check_interval: 1s, limit_mib: 400, spike_limit_mib: 100}
filter/drop_debug: # drop DEBUG and TRACE; keep unparsed (0)
log_conditions:
- >-
log.severity_number > SEVERITY_NUMBER_UNSPECIFIED and
log.severity_number < SEVERITY_NUMBER_INFO
exporters:
load_balancing/by_trace: # tail sampling: whole traces on one replica
routing_key: traceID
protocol:
otlp:
tls: # endpoints are pod IPs: verify the DNS name
ca_file: /tls/ca.crt
cert_file: /tls/tls.crt
key_file: /tls/tls.key
server_name_override: otel-gateway.observability.svc.cluster.local
resolver:
dns: {hostname: otel-gateway-headless.observability.svc.cluster.local, port: 4317}
load_balancing/by_service: # span metrics: each service on one replica
routing_key: service
protocol:
otlp:
tls:
ca_file: /tls/ca.crt
cert_file: /tls/tls.crt
key_file: /tls/tls.key
server_name_override: otel-gateway.observability.svc.cluster.local
resolver:
dns: {hostname: otel-gateway-headless.observability.svc.cluster.local, port: 4319}
otlp_grpc/gateway: # metrics and logs: any replica
endpoint: otel-gateway.observability.svc.cluster.local:4317
tls: {ca_file: /tls/ca.crt, cert_file: /tls/tls.crt, key_file: /tls/tls.key}
sending_queue: {batch: {}}
service:
extensions: [file_storage]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter]
exporters: [load_balancing/by_trace, load_balancing/by_service]
metrics:
receivers: [otlp]
processors: [memory_limiter]
exporters: [otlp_grpc/gateway]
logs:
receivers: [otlp, file_log]
processors: [memory_limiter, filter/drop_debug]
exporters: [otlp_grpc/gateway]
# gateway.yaml: Deployment with two or more replicas behind a headless Service
receivers:
otlp: # spans routed by trace ID; metrics; logs
protocols:
grpc:
endpoint: ${env:MY_POD_IP}:4317
tls: # mutual TLS: clients need a platform-CA cert
cert_file: /tls/tls.crt
key_file: /tls/tls.key
client_ca_file: /tls/ca.crt
otlp/by_service: # spans routed by service, for span metrics only
protocols:
grpc:
endpoint: ${env:MY_POD_IP}:4319
tls:
cert_file: /tls/tls.crt
key_file: /tls/tls.key
client_ca_file: /tls/ca.crt
processors:
memory_limiter: {check_interval: 1s, limit_mib: 1500, spike_limit_mib: 300}
redaction:
allow_all_keys: true # keep keys, but mask risky values
blocked_key_patterns: [".*token.*", ".*password.*", ".*secret.*"]
# e-mail addresses, masked in attributes and in string log bodies
blocked_values: ['[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}']
summary: info
tail_sampling:
decision_wait: 10s
num_traces: 100000
policies:
- {name: errors, type: status_code, status_code: {status_codes: [ERROR]}}
- {name: slow, type: latency, latency: {threshold_ms: 2000}}
- {name: baseline, type: probabilistic, probabilistic: {sampling_percentage: 1}}
connectors:
span_metrics:
metrics_expiration: 5m # stop a service's series after it moves away
exporters:
otlp_grpc/backend:
endpoint: backend.example.internal:4317
headers:
authorization: "Bearer ${env:BACKEND_TOKEN}" # injected from a secret
sending_queue:
queue_size: 5000
batch: {} # batches after sampling, inside the exporter
retry_on_failure:
max_elapsed_time: 10m
service:
telemetry:
metrics:
readers:
- pull:
exporter:
prometheus:
host: ${env:MY_POD_IP} # scraped only by the platform Prometheus
port: 8888
without_type_suffix: true
without_units: true
pipelines:
traces/sampled:
receivers: [otlp]
processors: [memory_limiter, redaction, tail_sampling]
exporters: [otlp_grpc/backend]
traces/span_metrics:
receivers: [otlp/by_service]
processors: [memory_limiter]
exporters: [span_metrics]
metrics:
receivers: [otlp, span_metrics]
processors: [memory_limiter]
exporters: [otlp_grpc/backend]
logs:
receivers: [otlp]
processors: [memory_limiter, redaction]
exporters: [otlp_grpc/backend]
What the routing costs. Each span crosses the network twice. The alternative, span metrics on the trace-ID-routed replicas,
works only because the connector adds a collector.instance.id dimension (a UUID per instance) by default, so each replica writes
its own series,[43] multiplying series by the replica count. Never exclude that dimension while several replicas compute
span metrics. Service routing keeps the dimension and some churn: by default the UUID is new each time a gateway
starts,[85][86] so every restart or rollout starts new span-metric series, and when the N replicas change about
1/N of services move too.[42] metrics_expiration retires the old series; dashboards still need
sum without (collector_instance_id).
Who can reach what. The agent’s receiver has no TLS or authentication and its hostPort opens it on every node, so anything
that reaches port 4317 on a node can send data. Keep that port inside the cluster (host firewall, security group) or give the agent
TLS and an authenticator too. Only clients with a platform-CA certificate reach the gateways. Agents dial gateway pod IPs;
server_name_override makes them verify the Service’s DNS name instead, so issue gateway certificates for that
name.[42][87] Port 8888 should accept only the platform’s Prometheus (a network policy, not shown). Since contrib
v0.126.0 redaction also covers log bodies: blocked_values inside string bodies, key rules in map
bodies.[83][34] Key rules cannot see a free-text password=… in a string, so code that never logs secrets remains the safer control.[31]
Artefact 2: Prometheus guardrails
Link to section “Artefact 2: Prometheus guardrails”Example status: YAML syntax-checked; not executed.
scrape_configs:
- job_name: checkout
sample_limit: 20000 # whole scrape fails above this, so alert well before it
label_limit: 30
label_value_length_limit: 200
extra_scrape_metrics: true # exposes scrape_sample_limit
static_configs:
- targets: ["checkout.example.internal:9464"]
metric_relabel_configs: # emergency containment: drop one offending metric
- source_labels: [__name__]
regex: checkout_orders_total
action: drop
The pipeline alerts and their tests are in Lab 3 of the hands-on labs.
Artefact 3: telemetry budget estimator
Link to section “Artefact 3: telemetry budget estimator”It computes worst-case series (instances × product of label values × series per label set; a classic histogram adds +Inf, _sum
and _count to its buckets), applies the Prometheus disk formula at 1–2 bytes per sample,[44] and flags labels with more
than 100 values, where Prometheus suggests looking for alternatives.[47]
Example status: executed with Python 3.8.10 on 2026-10-16; output below is real.
#!/usr/bin/env python3
"""Estimate worst-case Prometheus-style active series and disk for a metric inventory,
and flag labels and teams that break the telemetry budget. Python 3.8+, standard library only."""
import json
import sys
from math import prod
SERIES_PER_LABELSET = {"counter": 1, "gauge": 1}
def series_for(metric):
combos = prod(metric.get("labels", {}).values()) if metric.get("labels") else 1
if metric["type"] == "histogram": # classic histogram: buckets + "+Inf" + _sum + _count
per_set = metric["buckets"] + 3
else:
per_set = SERIES_PER_LABELSET[metric["type"]]
return metric["instances"] * combos * per_set
def main(path):
inv = json.load(open(path))
b = inv["budget"]
problems = 0
per_team = {}
print("metric team series (upper bound)")
for m in inv["metrics"]:
s = series_for(m)
per_team[m["team"]] = per_team.get(m["team"], 0) + s
print(f"{m['name']:<37} {m['team']:<12} {s:>12,}")
for label, values in m.get("labels", {}).items():
if values > b["max_label_values"]:
problems += 1
print(f" WARNING label '{label}' has {values:,} values (> {b['max_label_values']}): "
"move it to a span or log attribute")
for team, s in sorted(per_team.items()):
status = "OK" if s <= b["active_series_per_team"] else "OVER BUDGET"
if status != "OK":
problems += 1
print(f"team {team:<12} {s:>12,} of {b['active_series_per_team']:,} {status}")
total = sum(per_team.values())
sps = total / b["scrape_interval_s"]
seconds = b["retention_days"] * 86400
low, high = seconds * sps * 1 / 1e9, seconds * sps * 2 / 1e9
print(f"total series {total:,}; samples/s {sps:,.1f}; "
f"disk for {b['retention_days']} d: {low:.1f}-{high:.1f} GB at 1-2 bytes/sample")
return 1 if problems else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1]))
{
"budget": {"active_series_per_team": 200000, "max_label_values": 100,
"scrape_interval_s": 30, "retention_days": 15},
"metrics": [
{"name": "http_server_request_duration_seconds", "team": "checkout", "type": "histogram",
"buckets": 14, "instances": 12,
"labels": {"http_request_method": 4, "http_route": 30, "http_response_status_code": 6}},
{"name": "checkout_orders_total", "team": "checkout", "type": "counter", "instances": 12,
"labels": {"payment_provider": 3, "customer_id": 50000}},
{"name": "queue_depth", "team": "fulfilment", "type": "gauge", "instances": 6,
"labels": {"queue": 8}}
]
}
The 14 buckets match the boundaries OpenTelemetry advises for http.server.request.duration.[45] The label counts are
illustrative.
Hands-on labs
Link to section “Hands-on labs”Lab 1 was executed for this page. Labs 2 and 3 were not executed (no container runtime or promtool on the authoring host);
their expected output is derived from current documentation.
Lab 1 (beginner): budget a metric inventory
Link to section “Lab 1 (beginner): budget a metric inventory”Objective: find the label that breaks a budget before production. Prerequisites: Python 3.8 or later; no cost or risk; 30 minutes.
- Save Artefact 3 as
telemetry_budget.pyand its JSON asinventory.json. - Run
python3 telemetry_budget.py inventory.json; echo "exit=$?". - Delete the
customer_identry fromcheckout_orders_totaland run again. - Replace the inventory with three real metrics from your own services.
Expected output (step 2, verbatim):
metric team series (upper bound)
http_server_request_duration_seconds checkout 146,880
checkout_orders_total checkout 1,800,000
WARNING label 'customer_id' has 50,000 values (> 100): move it to a span or log attribute
queue_depth fulfilment 48
team checkout 1,946,880 of 200,000 OVER BUDGET
team fulfilment 48 of 200,000 OK
total series 1,946,928; samples/s 64,897.6; disk for 15 d: 84.1-168.2 GB at 1-2 bytes/sample
exit=1
Validation: after step 3, checkout totals 146,916 series, the run prints total series 146,964; samples/s 4,898.8; disk for 15 d: 6.3-12.7 GB and exits 0. Troubleshooting: ImportError: cannot import name 'prod' means Python older than 3.8;
KeyError: 'buckets' means a histogram lacks a bucket count. The figures are upper bounds, because not every label combination
occurs.[8] Teardown: delete the two files.
Lab 2 (intermediate): redact and inspect a Collector pipeline
Link to section “Lab 2 (intermediate): redact and inspect a Collector pipeline”Objective: prove that the pipeline masks secrets and personal data, and read its internal metrics. Prerequisites: Docker on Linux, about 1 GB of free memory, 45 minutes; local, no cost. Status: not executed.
- Save this as
lab2.yaml.
Example status: YAML syntax-checked; not executed.
receivers:
otlp:
protocols:
grpc:
endpoint: 127.0.0.1:4317
processors:
memory_limiter: {check_interval: 1s, limit_mib: 400, spike_limit_mib: 100}
redaction:
allow_all_keys: true
blocked_key_patterns: [".*token.*"]
blocked_values: ['[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}']
summary: info
exporters:
debug: {verbosity: detailed}
service:
telemetry:
metrics:
readers:
- pull:
exporter:
prometheus:
host: 127.0.0.1
port: 8888
without_type_suffix: true
without_units: true
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, redaction]
exporters: [debug]
Example status: syntax-checked with bash -n; not executed (requires Docker).
#!/usr/bin/env bash
set -euo pipefail
REG=ghcr.io/open-telemetry
IMG=$REG/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.162.0
GEN=$REG/opentelemetry-collector-contrib/telemetrygen:v0.162.0
CFG=/etc/otelcol-contrib/config.yaml
docker run --rm -v "$PWD/lab2.yaml:$CFG:ro" "$IMG" validate --config=$CFG
docker run -d --name otelcol-lab --network host -v "$PWD/lab2.yaml:$CFG:ro" "$IMG"
sleep 5
docker run --rm --network host "$GEN" traces --otlp-insecure \
--otlp-endpoint 127.0.0.1:4317 --traces 5 --service lab-checkout \
--telemetry-attributes 'user.email="jane@example.com"' \
--telemetry-attributes 'session_token="abc123"'
sleep 3
docker logs otelcol-lab 2>&1 | grep -E "user.email|session_token|redaction" | head
curl -s 127.0.0.1:8888/metrics | grep -E "otelcol_(receiver_accepted|exporter_sent)_spans"
Expected output and validation: validate exits 0. The debug log shows user.email and session_token masked with asterisks and a
redaction.masked.count attribute; neither jane@example.com nor abc123 appears. The span counters on port 8888 are non-zero. Troubleshooting: “unknown type” errors mean an image older than the
snake_case renames;[34] on macOS or Windows, publish ports instead of using host networking. Image tags were checked
in the registry on 2026-10-16. Teardown: docker rm -f otelcol-lab.
Lab 3 (advanced): test alerts for the telemetry pipeline
Link to section “Lab 3 (advanced): test alerts for the telemetry pipeline”Objective: alert on the three ways a Collector loses data (failures after retries, a full queue, refused input) and on targets
near their sample limit, and unit-test the rules. Prerequisites: promtool from Prometheus 3.x; 45 minutes. Status: not
executed; expected results reasoned from the test-file documentation.[35]
Example status: YAML syntax-checked; not executed (requires promtool).
# pipeline-alerts.yml
groups:
- name: telemetry-pipeline
rules:
- alert: TelemetryExportFailing
# "or ... * 0" keeps the ratio defined when sent_spans has never been incremented
expr: |
sum by (job, instance) (rate(otelcol_exporter_send_failed_spans[5m]))
/
(sum by (job, instance) (rate(otelcol_exporter_send_failed_spans[5m]))
+ (sum by (job, instance) (rate(otelcol_exporter_sent_spans[5m]))
or sum by (job, instance) (rate(otelcol_exporter_send_failed_spans[5m])) * 0))
> 0.01
for: 10m
labels: {severity: page}
annotations:
summary: "{{ $labels.job }}/{{ $labels.instance }}: over 1% of spans fail to export"
- alert: TelemetryQueueFull
expr: sum by (job, instance) (rate(otelcol_exporter_enqueue_failed_spans[5m])) > 0
for: 5m
labels: {severity: page}
annotations:
summary: "{{ $labels.job }}/{{ $labels.instance }}: sending queue full, spans dropped"
- alert: TelemetryReceiverRefusing
expr: sum by (job, instance) (rate(otelcol_receiver_refused_spans[5m])) > 0
for: 15m
labels: {severity: ticket}
annotations:
summary: "{{ $labels.job }}/{{ $labels.instance }}: refusing spans (back-pressure)"
- alert: ScrapeNearSampleLimit
expr: scrape_samples_post_metric_relabeling / (scrape_sample_limit > 0) > 0.8
for: 15m
labels: {severity: ticket}
annotations:
summary: "{{ $labels.job }}/{{ $labels.instance }}: over 80% of sample_limit"
- alert: Watchdog
expr: vector(1)
labels: {severity: none}
annotations:
summary: "Always firing; page through another channel if it stops"
# pipeline-alerts.test.yml
rule_files: [pipeline-alerts.yml]
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: 'otelcol_exporter_sent_spans{job="otel-gw", instance="gw-1"}'
values: '0+1000x30'
- series: 'otelcol_exporter_send_failed_spans{job="otel-gw", instance="gw-1"}'
values: '0+60x30'
- series: 'otelcol_exporter_send_failed_spans{job="otel-gw", instance="gw-2"}'
values: '0+100x30' # back end down since gw-2 restarted: nothing ever sent
- series: 'otelcol_exporter_enqueue_failed_spans{job="otel-gw", instance="gw-1"}'
values: '0+50x30'
- series: 'otelcol_receiver_refused_spans{job="otel-gw", instance="gw-1"}'
values: '0+10x30'
- series: 'scrape_samples_post_metric_relabeling{job="checkout", instance="app-7"}'
values: '850x30'
- series: 'scrape_sample_limit{job="checkout", instance="app-7"}'
values: '1000x30'
alert_rule_test:
- eval_time: 5m
alertname: TelemetryExportFailing
exp_alerts: []
- eval_time: 15m
alertname: TelemetryExportFailing
exp_alerts:
- exp_labels: {severity: page, job: otel-gw, instance: gw-1}
exp_annotations: {summary: "otel-gw/gw-1: over 1% of spans fail to export"}
- exp_labels: {severity: page, job: otel-gw, instance: gw-2}
exp_annotations: {summary: "otel-gw/gw-2: over 1% of spans fail to export"}
- eval_time: 10m
alertname: TelemetryQueueFull
exp_alerts:
- exp_labels: {severity: page, job: otel-gw, instance: gw-1}
exp_annotations: {summary: "otel-gw/gw-1: sending queue full, spans dropped"}
- eval_time: 20m
alertname: TelemetryReceiverRefusing
exp_alerts:
- exp_labels: {severity: ticket, job: otel-gw, instance: gw-1}
exp_annotations: {summary: "otel-gw/gw-1: refusing spans (back-pressure)"}
- eval_time: 20m
alertname: ScrapeNearSampleLimit
exp_alerts:
- exp_labels: {severity: ticket, job: checkout, instance: app-7}
exp_annotations: {summary: "checkout/app-7: over 80% of sample_limit"}
- eval_time: 1m
alertname: Watchdog
exp_alerts:
- exp_labels: {severity: none}
exp_annotations: {summary: "Always firing; page through another channel if it stops"}
- Save both files; run
promtool check rules pipeline-alerts.yml, thenpromtool test rules pipeline-alerts.test.yml. - Change the
gw-1failed-span series to'0+5x30'(0.5% failures) and rerun; the 15-minute case should now fail.
Expected output and validation: step 1 reports valid rules and passed tests. At 5 minutes the export alert is pending
(for: 10m). At 15 minutes gw-1 fails 60 / 1,060 ≈ 5.7% of spans and gw-2, which has sent nothing since it restarted, 100%;
without the or … * 0 term the gw-2 ratio would be empty and never page. Step 2 fails because 0.5% is under the threshold.
Reading the counters: the Collector documentation warns that send failures “do not inherently imply data loss since there could be
retries”;[86] in the current exporter helper, retries run inside the step that counts results, so send_failed counts
items that failed after their retries.[89] Spans refused by a full queue never reach the retries and count as
enqueue_failed.[82] Troubleshooting: a label mismatch usually means different metric names or labels (the _total
suffix depends on telemetry settings); check /metrics first.[86] Teardown: none; commit the files. Route Watchdog
to a dead man’s switch service, not to a person.
Use cases and scenarios
Link to section “Use cases and scenarios”Case 1 (sourced): one engineer, global scale. Mastodon, about 20 people, runs mastodon.social (about 10 million requests per minute) with one engineer managing observability infrastructure: one Collector per Kubernetes namespace, the OpenTelemetry Operator and Argo CD, Datadog as the back end, and tail sampling that keeps roughly 0.1% of successful traces and every error trace. Tim Campbell, a Mastodon software engineer, said he had not run into a single Collector issue in about two years; the post itself names getting started as the hardest part.[32] Lesson: one Collector tier is enough when a SaaS back end carries storage and sampling controls volume. Pitfall: Mastodon sets no strict CPU or memory limits on Collector pods, which suits its load but is no general default.
Case 2 (sourced): cardinality governance at scale. In March 2023 Cloudflare ran 916 Prometheus instances holding about 4.9 billion
series. Because a breached sample_limit fails the whole scrape, Cloudflare patched Prometheus to cap series per server and keep
existing series beyond the limit, alerting the owning team. The result is self-service: “if CI checks are passing then we have the
capacity you need”.[8] Lesson: limits plus CI make self-service safe. Pitfall: these
are Cloudflare’s patches, not upstream behaviour.
Case 3 (sourced): observability as an internal service. The UK Ministry of Justice’s Observability Platform gives teams on its Modernisation Platform logs, metrics and alerting to PagerDuty or Slack on Amazon Managed Grafana; its team “maintains the infrastructure, allowing teams to focus on running their applications”. The offering is to be retired, date not announced.[4] Lesson: a platform needs a product’s lifecycle, including retirement.
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”Measure the platform as a product: is the data delivered, used and affordable, and do alerts help? Targets are starting points, not industry benchmarks.
| KPI | Definition or formula | Data source | Pitfall |
|---|---|---|---|
| Export success ratio (leading) | sent ÷ (sent + send failed + enqueue failed), per signal | Collector internal metrics[86] | Blind to loss before the Collector; confirm with the canary |
| Freshness, p95 | query time − emit time of a synthetic canary | Canary job | One canary per path, not per platform |
| Alert-path availability | minutes the Watchdog heartbeat arrived ÷ minutes in period |
Dead man’s switch | Must use a separate channel |
| Page actionability (lagging) | pages labelled actionable ÷ all pages | Monthly alert review | Self-reported; sample a few to check |
| Unused-signal ratio | series used by no dashboard or rule in 90 days ÷ active series | TSDB statistics[90] plus a rule and dashboard inventory | Ad hoc queries are invisible; ask first |
| Naming hygiene | items under unknown_service ÷ all items[37] |
Back-end query | Only near zero is good |
| Budget adherence | teams within budget ÷ teams | Budget report | Budgets that never change are ignored |
| Telemetry unit cost | observability spend ÷ business units (for example per million requests) | Bills and traffic | Compare trends, not companies |
Report per team: a single platform-wide figure hides who generates the cost and noise.
Quality, security, privacy and compliance
Link to section “Quality, security, privacy and compliance”| Risk | Signal | Mitigation |
|---|---|---|
| Secrets or personal data in attributes and logs | Pattern scans; redaction counters | Data minimisation and review of what libraries emit (the implementer’s responsibility, says OpenTelemetry);[31] redaction, including log bodies[83] |
| Baggage and trace headers leaving the network | Outbound third-party requests with baggage or traceparent |
Baggage allow-list without credentials or personal data; stop propagation at egress; new trace for untrusted callers[27][54] |
| Open ingest endpoints | Unknown sources; sudden series growth | Network policy, mutual TLS, per-tenant limits; random request paths in labels are an attack as well as a bug[8] |
| Over-broad query access | Audit logs of searches | Role-based access and tenancy; restricted views of sensitive services |
| Keeping data too long | Retention report | Retention per data class; EU law, for example, requires personal data to be kept “no longer than is necessary” (GDPR Article 5, unofficial text)[91] |
Obligations differ by jurisdiction and sector: take legal advice, and coordinate with SecDataOps, PrivacyOps and ComplianceOps.
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”Drivers: active series (memory), samples per second, log bytes ingested and indexed, spans kept, query load, retention and
cross-region transfer. Capacity: Prometheus disk ≈ retention seconds × samples per second × 1–2 bytes;[44] for memory,
Cloudflare divides go_memstats_alloc_bytes by prometheus_tsdb_head_series as a rough bytes-per-series figure.[8] Levers,
cheapest first: delete unused signals; fix unbounded labels; sample traces; tier and shorten retention; downsample; raise scrape
intervals last, because cutting series is more effective.[44] Allocation: tag telemetry by team or service.namespace
(FinOps). Sustainability: less data stored means less compute and storage (GreenOps).
Assurance: watching the watchers
Link to section “Assurance: watching the watchers”The observability system can fail silently, so it needs its own loops: an independent watcher (a small separate Prometheus or an
external probe); the Watchdog heartbeat; a freshness canary; a weekly review of dropped data (…send_failed_*,
…enqueue_failed_*, otelcol_receiver_refused_*, tail-sampling drops);[86][41] and Git history plus CI results as
the configuration audit trail. Early warnings: queues above 80% of capacity, rising scrape_series_added, series growing faster than
traffic.
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnosis | Remediation and prevention |
|---|---|---|---|
| Metrics database memory climbs; restarts | Cardinality explosion | TSDB statistics; scrape_series_added |
Runbook below; budget checks in CI |
| Traces missing spans | Tail sampler overloaded, or spans split across instances | …_sampling_trace_dropped_too_early[41] |
Raise num_traces or shorten decision_wait; route by trace ID |
| Export failures; queue near capacity | Back end down or throttling | otelcol_exporter_queue_size versus _capacity[40] |
Scale or fix the back end; persistent queue for critical data |
| One request shows up as several traces | Context lost at a proxy or queue; mixed B3 and W3C | Follow a test request hop by hop | One propagator set; header pass-through; span links for queues[52][53] |
| Gaps, jumps, “out-of-order samples” | Two writers for one metric stream | Back-end errors[39] | Unique resource identity per writer |
| Span-metric series multiply or jump after scaling | Span metrics on trace-ID-routed replicas, or collector.instance.id excluded |
Series per collector_instance_id[43] |
Route by service; sum without (collector_instance_id) |
| Logs not linked to traces | No trace IDs in log lines, or not parsed | Share of records with empty trace_id |
Emit IDs; parse them at the agent[55] |
Telemetry under unknown_service |
SDK resource not configured | Count by service.name[37] |
Set the service name in templates |
| Pages nobody acts on | Cause-based or over-sensitive rules | Alert review | Delete, merge or convert to tickets[9] |
| No alerts during an outage | Broken alert path | Watchdog heartbeat missing |
Fix routing; test the path end to end[30] |
| Secrets in stored logs | Logging in code; no redaction | Pattern scan | Rotate the secret; purge; fix the code |
Worked runbook: a cardinality explosion
Link to section “Worked runbook: a cardinality explosion”Diagram source (Mermaid)
flowchart TB
A["Alert: head series or memory rising fast"]
B["Confirm: series growth by job"]
C["Find: top metrics and labels in TSDB stats"]
D{"Memory at risk?"}
E["Contain: drop the offending metric for that job"]
F["Fix at source: bounded label values, IDs as span attributes"]
G["Recover: wait for head garbage collection"]
H["Prevent: CI budget check, scrape limits, growth alert"]
A --> B
B --> C
C --> D
D -- "yes" --> E
D -- "no" --> F
E --> F
F --> G
G --> H
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
class A,E risk
class D decision
class B,C,F,G,H core
Text equivalent: an alert on fast-rising head series or memory starts the runbook. Confirm which job adds series and find the top metrics and labels in the TSDB statistics. If memory is at risk, drop the offending metric first; then fix the source, wait for head garbage collection, and prevent recurrence with CI budget checks, scrape limits and a growth alert.
- Confirm. Which jobs are adding series?
sort_desc(sum by (job) (sum_over_time(scrape_series_added[1h])))
- Find. Ask the TSDB which metrics and labels hold the most series.[90]
Example status: syntax-checked with bash -n; not executed (requires a Prometheus server).
#!/usr/bin/env bash
set -euo pipefail
PROM="http://prometheus.example.internal:9090"
curl -s "$PROM/api/v1/status/tsdb?limit=5" \
| jq '.data.headStats.numSeries, .data.seriesCountByMetricName, .data.labelValueCountByLabelName'
- Contain if memory is at risk: drop the metric for that job with
metric_relabel_configs(Artefact 2) and reload. Avoidlabeldrop, because metrics must stay uniquely labelled, andsample_limit, which fails the whole scrape.[49] - Fix at source. Replace
customer_idwith a bounded value (customer tier) and record the ID as a span attribute. - Recover. Series scraped even once stay in memory for one to three hours, until head garbage collection,[8] so memory falls slowly; restart only if the server keeps crashing.
- Prevent. Run the Lab 1 check in CI, set scrape limits, and alert on
scrape_series_added.
Done when head series are back to the pre-release level and the inventory passes the budget check.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- “Collect everything; decide later.” Cost grows without value, and the CNCF whitepaper warns against it.[12] Instead: name a consumer for every signal.
- “Three pillars means three tools.” The whitepaper prefers “primary signals” and notes that one or two signals can be a valid trade-off.[12] Instead: choose signals per question and link them.
- “The observability team owns everyone’s alerts.” It becomes a bottleneck and works against DORA’s aim of making every developer proficient with monitoring.[2] Instead: templates and reviews; service teams own their alerts.
- “Self-hosting is free.” You run stateful databases, upgrades and on-call yourself.
- “Tracing works once the SDK is installed.” Traces break wherever context is not propagated.
- “AI will find it.” Correlation needs clean, consistent data first (AIOps).
Deciding and adopting
Link to section “Deciding and adopting”When to use and when not to use
Link to section “When to use and when not to use”Invest in a deliberate ObservabilityOps function when several teams share telemetry tools; cost or noise is a recurring complaint; incidents are slowed by missing or untrusted data; telemetry may hold regulated data; or you self-host stateful back ends. Do not create a team yet when one or two teams use a managed tool with sensible defaults, volumes are small and nobody complains about cost or noise. One practitioner notes that a few people part-time “with a few high leverage SaaS investments” may be enough.[23] Hidden responsibilities either way: on-call for the platform, upgrades, vendor management, access reviews and retention enforcement.
Decision framework
Link to section “Decision framework”Diagram source (Mermaid)
flowchart TB
S("`How many teams share
telemetry tools?`")
H1("`Self-hosted back ends
or regulated data?`")
O1["`Named owner
on call`"]
N["`Part-time owner,
vendor defaults`"]
C("`Cost, noise or gaps
a top problem?`")
H("`Self-host stateful
back ends?`")
G["`Guild and
standards`"]
P["`Platform team
with on-call`"]
Q["`Small function:
standards, budgets`"]
S -- "one or two" --> H1
S -- "three or more" --> C
H1 -- "yes" --> O1
H1 -- "no" --> N
C -- "yes" --> H
C -- "no" --> G
H -- "yes" --> P
H -- "no" --> Q
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
class S,H1,C,H decision
class O1,P,Q core
class N,G neutral
Text equivalent: the team-count thresholds are this Atlas’s rule of thumb. With one or two teams: if you self-host stateful back ends or handle regulated data, name an owner on call for the stack; otherwise a part-time owner and vendor defaults suffice. With three or more teams: if cost, noise or missing data is not a top problem, a guild with shared standards is enough; if it is, self-hosting stateful back ends calls for a platform team with on-call, and otherwise a small function for standards and budgets.
For the hosting choice, weight the criteria for your situation. Scores are this page’s illustrative judgement (1 = weak, 5 = strong).
| Criterion | Weight | Self-hosted open source | SaaS back end | Hybrid: own pipeline, SaaS back end |
|---|---|---|---|---|
| Control of data and residency | 3 | 5 | 2 | 4 |
| Operating effort (higher = less effort) | 3 | 1 | 5 | 3 |
| Cost predictability at your volume | 2 | 3 | 2 | 4 |
| Freedom to switch back ends | 2 | 4 | 2 | 5 |
| Weighted total | 32 | 29 | 39 |
A hybrid scores highest under these weights because a pipeline you own keeps sampling, redaction and routing portable; a team of two might weight operating effort at 5 and get a different answer.
Maturity model (this Atlas’s model)
Link to section “Maturity model (this Atlas’s model)”Not an official or industry model; Grafana Labs’ survey splits practices only into centralised and siloed.[7]
| Level | Observable behaviour | Exit criteria |
|---|---|---|
| 0 Ad hoc | Each team runs its own tools; nobody owns the whole | Inventory of tools, volumes and spend |
| 1 Centralised | Shared stack and a named owner; basic dashboards | Platform SLIs measured; Watchdog in place |
| 2 Standardised | OpenTelemetry, conventions and one propagator set; templates; limits | Most services onboarded through templates; unknown_service near zero |
| 3 Governed product | Showback budgets; alert reviews; platform SLOs; tested configuration in Git | Budget adherence and page actionability tracked for two quarters |
| 4 Optimised | Sampling and retention set by value; unused signals pruned automatically | Unit cost falls or holds while coverage holds or rises |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Window | Goals | Deliverables | Measures |
|---|---|---|---|
| Days 1–30 | Baseline and safety | Named owner; inventory of tools, volumes and spend; Watchdog and an external probe; worst three labels fixed |
Baseline numbers exist |
| Days 31–60 | Standards and guardrails | Instrumentation and log standard; gateways with limits and redaction; scrape limits; showback; first alert review | Export success measured; first deletions |
| Days 61–90 | Run it as a product | Platform SLOs; tested configuration in Git; first pruning; tail sampling for the largest producer; first unit-cost report | Budget adherence, page actionability, unit cost |
Quick wins: delete unused dashboards and alerts, add the dead man’s switch, fix the worst label. Dependencies: a service and owner list, and someone with authority over the budget.
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Quick start
- One named owner for the observability platform
- Inventory: tools, series, log volume, spend, pages per week
-
Watchdogrouted to a dead man’s switch, plus one external probe
Service onboarding (preflight)
-
service.nameand owner set; routes recorded as templates - Expected series and log volume inside the team budget
- Standard RED dashboard; at least one symptom alert with a runbook
- No personal data or secrets in a sample of debug output
Pipeline change
- Configuration validated and alert rules unit-tested in CI
- Component names, stability levels and defaults checked against the pinned version’s release notes; rollback is a Git revert
Monthly review
- Platform SLIs, dropped data and queue use
- Budget report per team; expired exceptions removed
- Alert review with actionability labels
Security and privacy audit (quarterly)
- Gateways require mutual TLS; agent ports closed to outside the cluster; baggage keys match the allow-list
- Query access matches roles; searches of sensitive logs are audited
- Retention per data class enforced; redaction rules tested
Platform incident readiness
- Independent watcher alive; runbooks for back end down, queue full, cardinality explosion
Templates
Link to section “Templates”Template status: illustrative; adapt the fields.
ONE-PAGE OBSERVABILITY PLATFORM CHARTER
Mission: Every engineer can answer a new question about production within
minutes, from trustworthy data, at an agreed cost.
Users: <service teams, SRE, incident commanders, security (read-only)>
Services: Ingest (OTLP, scrape, container logs), storage tiers, dashboards,
alert routing, budget reports
Platform SLOs: Export success >= <99.9%>/30 d; freshness p95 <= <60 s>;
alert-path heartbeat 100%
Budgets: Active series and log GB/day per team; exceptions expire after <90> d
Standards: Propagators <tracecontext,baggage>; baggage keys on an allow-list;
JSON logs with severity, trace_id, span_id
Not our job: Other teams' SLO targets, their alerts' content, their on-call
Reviews: Weekly health; monthly budget and alerts; quarterly pruning, standards
TELEMETRY CHANGE REQUEST
Team / service: <name> Owner on call: <name>
Signal: metric | log stream | trace attribute | new source
Question it answers: <who uses it, in which dashboard or alert>
Labels and value counts: <label: values, ...> Expected series or GB/day: <n>
Personal or secret data? no | yes -> masking rule: <rule>
Retention class: hot <d> / warm <d> / archive <d>
Review date: <date>; retire if unused for 90 days
Decision: approved | redesign | exception until <date>
Approved by: <budget owner>
ALERT REVIEW (per page, monthly)
Alert | Pages | Actionable? (y/n) | Action taken | User-visible? (y/n)
| Decision: keep / tune / ticket / delete | Owner
ARCHITECTURE DECISION RECORD: trace sampling
Context: <traces per second, cost, current back end, regulatory constraints>
Decision: Tail sampling at gateways: keep all errors and traces > <2 s>;
<1>% of the rest
Consequences: Spans routed by trace ID to the samplers and by service to span
metrics (one writer per series); agent-to-gateway trace traffic doubles;
new span-metric series after each gateway restart (aggregate them);
monitor dropped-too-early; rare successful paths missing from samples
Alternatives: Span metrics on the sampling replicas (series x replicas, keep
collector.instance.id); head sampling in SDKs (cheaper, loses the
error guarantee); no sampling (cost)
Review: <date>
SERVICE OBSERVABILITY SCORECARD (0-2 each, 14 max)
Naming and ownership | RED dashboard | Symptom alert with runbook
Unbroken traces | Logs with trace IDs | Budget adherence
No sensitive data in samples or baggage
Deep comparisons
Link to section “Deep comparisons”| Head sampling | Tail sampling | |
|---|---|---|
| Decision point | Trace start, from the trace ID | After decision_wait, seeing the trace |
| Keeps every error? | No | Yes, with a status policy |
| Cost to run | Negligible | Stateful tier; all spans of a trace to one instance[41] |
| Best for | Uniform, high-volume traffic | Mixed traffic where rare failures matter |
Is ObservabilityOps a job title I should hire for? Rarely under that name: look for observability or platform engineers.
We use one SaaS vendor. Do we still need OpenTelemetry? It keeps instrumentation and pipeline policy portable, and many back ends accept OTLP.[79]
Which signal should we start with? Metrics for RED and USE views and symptom alerts; then traces across services; logs for detail. The CNCF whitepaper suggests starting with one or a few signals.[12]
Who pays for telemetry? The teams that generate it: showback first, chargeback only if you charge back other costs (FinOps).
How long should we keep data? As long as a named question or obligation needs it, per data class. Metrics downsample well; traces do not, so keep them only as long as investigations need them.
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”| Topic | Status (October 2026) | What it changes |
|---|---|---|
| Continuous profiling | Established technique; OpenTelemetry Profiles public alpha since March 2026[21] | Cost per code path, linked to traces |
| eBPF zero-code instrumentation | OpenTelemetry eBPF Instrumentation (OBI, formerly Grafana Beyla), first alpha November 2025[92] | Protocol-level telemetry without code changes; less detail |
| Consistent probability sampling | Specification work, with W3C Trace Context Level 2[66] | Different sampling rates per service without broken traces |
| Events as logs | Span Event API being deprecated (March 2026)[46] | One way to emit events, correlated with spans |
| Native histograms | Stable in Prometheus since v3.8.0[67] | Cheaper, more accurate latency distributions |
Treat “AI observability” and “observability pipeline” product claims as marketing until tested on your own data.
Learning path
Link to section “Learning path”- Prerequisites: Linux and HTTP basics, one programming language, DevOps and SRE vocabulary.
- One day: read the CNCF whitepaper’s signals section[12] and the SRE book’s monitoring chapter;[9] run Lab 1.
- One week: instrument a sample service, run Lab 2, build a RED dashboard and one symptom alert.
- 30 days: run Lab 3 in CI, route traces to a sampling tier, and produce a budget report for real services.
- Specialise: time-series storage, tracing, log pipelines, or cost and governance.
- Portfolio project: a repository with Collector configuration, tested rules, a budget checker and a one-page charter.
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”| Decide | Practise | Measure | Watch out for |
|---|---|---|---|
| Who owns the platform; hosting model; budgets per team | OpenTelemetry and conventions; bounded labels; tail sampling with RED computed first; retention tiers | Export success, freshness, alert-path heartbeat, page actionability, unit cost | Cardinality explosions; silent data loss; secrets in logs; component renames on upgrade |
A judgement call: cut traces or cut logs?
Link to section “A judgement call: cut traces or cut logs?”Assumptions: about 2,000 traces per second, mostly healthy; engineers start investigations from traces and rarely read debug logs; one regulated service must keep its logs. Why A looks attractive: one configuration change gives the biggest saving, and OpenTelemetry suggests sampling at this volume.[26] Why A alone can be wrong: dashboards that count stored spans undercount once sampling starts; the stateful sampling tier must be sized and monitored;[41] and rare successful paths you may want to compare against disappear. Why B alone can be wrong: it may not reach 30%, and three-day traces break investigations of slow-burning problems. A defensible answer: A for high-volume services, with span metrics computed before sampling (Artefact 1); 100% for low-volume services; and the cheap half of B (archive debug logs, keep error logs hot). Track unit cost and “data missing” findings for a quarter; the open question is how often engineers need a successful trace, so check five incident reviews before going below 1%.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”After this page you can explain the two readings of ObservabilityOps; design a pipeline with routing, limits, redaction and sampling; find where a trace breaks; estimate series and disk; test alerts for the platform; and choose an organisational model.
Why does adding a customer_id label to a counter create a cost problem, and where should the ID go instead?
Every label combination is a new series, so the label multiplies series by the number of customers.[29] Put the ID on spans or logs.
A metric has 4 methods, 30 routes and 6 status codes on 12 instances, as a classic histogram with 14 buckets. How many series?
4 × 30 × 6 × 12 × (14 + 3) = 146,880 as an upper bound.
Why are spans routed by trace ID to tail-sampling Collectors but by service to span-metrics Collectors?
A checkout trace shows the API span, but the payment worker’s spans appear as a separate trace. Name likely causes.
A team asks the platform team to own its alerts. What do you answer?
No: provide templates and reviews; the service team owns its alerts because it can act on them, and DORA wants every developer to be proficient with monitoring.[2]
When should a small company not build an ObservabilityOps function?
When one or two teams use a managed tool with sensible defaults and cost and noise are not problems: a part-time owner is enough, unless back ends are self-hosted or data is regulated.
Conclusion
Link to section “Conclusion”Behind the ObservabilityOps label is real work that every organisation with more than a few services ends up doing, well or badly: deciding what telemetry to produce, moving it safely, keeping it affordable, and making sure the alerts mean something. OpenTelemetry makes the pipeline portable; the decisions stay human: which questions matter, how many series a team may spend, which data is too sensitive to keep, which page should wake someone. Run the system that answers them like a product, with users, SLOs, a budget and the discipline to delete what nobody uses.
Sources and further reading
Link to section “Sources and further reading”Each footnote gives its check date. Versions, statuses, defaults and survey figures are time-sensitive: re-check them against
the release notes and component READMEs below at each Collector minor release and at least quarterly. The evidence ledger is
research/observabilityops-ledger.md.
Further reading: The Site Reliability Workbook: Monitoring (Google, 2018); Observability Engineering (first edition, O’Reilly, 2022).
Changelog. 2026-10-16: first version (research, ledger, three artefacts, three labs of which Lab 1 was executed, six diagrams);
term status confirmed as ambiguous; awaiting independent review.
2026-10-16 (r1 revision after independent review): span metrics routed by service (Artefact 1, figure 3, ADR); context propagation,
baggage and log pipelines added; log-body redaction, Trace Context date, Prometheus OTLP history and Mastodon attribution corrected;
loss alerts and least-privilege bindings added; four diagrams redrawn; trimmed.
2026-10-17, post-score polish: span-metric churn under service routing; server_name_override, agent exposure and hostPath
offsets in Artefact 1; exact survey label and Mastodon attribution; re-check cadence.
What is OpenTelemetry? — OpenTelemetry, updated 2026-04-06. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Capabilities: Monitoring and observability — DORA, undated. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Observability Team — The GitLab Handbook, living page. Checked 2026-10-16. ↩︎ ↩︎
What is the Observability Platform? — UK Ministry of Justice, reviewed 2026-10-01. Checked 2026-10-16. ↩︎ ↩︎
Observability Center of Excellence — New Relic Documentation. Checked 2026-10-16. ↩︎ ↩︎
Observability Survey Report 2025 — Grafana Labs, 2025 (fieldwork 2024-09-18 to 2025-01-02). Checked 2026-10-16. ↩︎
Observability Survey Report 2026 — Grafana Labs, 2026 (fieldwork 2025-10-01 to 2026-01-10). Checked 2026-10-17. ↩︎ ↩︎ ↩︎
How Cloudflare runs Prometheus at scale — Łukasz Mierzwa, Cloudflare, 2023-03-03. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Monitoring Distributed Systems — Rob Ewaschuk, Site Reliability Engineering (Google, O’Reilly, 2016). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Observability Engineering, 2nd Edition — Honeycomb book page (Majors, Fong-Jones, Miranda, Parker; O’Reilly; no publication date shown). Checked 2026-10-16. ↩︎
ObserveOps — Motadata, product page. Checked 2026-10-16. ↩︎
Observability Whitepaper — CNCF TAG Observability, v1.0 (October 2023). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
The USE Method — Brendan Gregg, 2012. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
The RED Method: How to Instrument Your Services — Julie Dam on Tom Wilkie’s talk, Grafana Labs, 2018. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Prometheus — CNCF project page. Checked 2026-10-16. ↩︎ ↩︎
OpenTelemetry — CNCF project page. Checked 2026-10-16. ↩︎ ↩︎
Trace Context — W3C Recommendation, 2021-11-23 (edits the 2020-02-06 Recommendation). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Prometheus CHANGELOG — Prometheus (entry “2.47.0 / 2023-09-06”). Checked 2026-10-16. ↩︎
Announcing Prometheus 3.0 — Prometheus Team, 2024-11-14. Checked 2026-10-16. ↩︎ ↩︎
Jaeger v2 released: OpenTelemetry in the core! — CNCF blog, 2024-11-12. Checked 2026-10-16. ↩︎ ↩︎
OpenTelemetry Profiles Enters Public Alpha — OpenTelemetry blog, 2026-03-26. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
OpenTelemetry Graduates — OpenTelemetry blog, 2026-05-21. Checked 2026-10-16. ↩︎
Notes on an Observability Team — Eric Mustin, 2022-04-20. Checked 2026-10-16. ↩︎ ↩︎
OpenTelemetry semantic conventions 1.44.0 — OpenTelemetry. Checked 2026-10-16. ↩︎
Configuration — OpenTelemetry Collector docs, updated 2026-09-10. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Sampling — OpenTelemetry, updated 2025-10-16. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Context propagation — OpenTelemetry, updated 2026-08-10. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
CNCF Platforms White Paper — CNCF TAG App Delivery. Checked 2026-10-16. ↩︎
Metric and label naming — Prometheus best practices. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Alerting — Prometheus best practices. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Handling sensitive data — OpenTelemetry, updated 2026-01-14. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
How Mastodon Runs OpenTelemetry Collectors in Production — OpenTelemetry blog, 2026-03-18. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
opentelemetry-collector CHANGELOG — OpenTelemetry. Checked 2026-10-16. ↩︎ ↩︎
opentelemetry-collector-contrib CHANGELOG — OpenTelemetry. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Unit testing for rules — Prometheus docs. Checked 2026-10-16. ↩︎ ↩︎
pint, a Prometheus rule linter — Cloudflare (v0.89.2, 2026-10-08). Checked 2026-10-16. ↩︎ ↩︎
Service resource conventions — OpenTelemetry. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Memory Limiter Processor — OpenTelemetry Collector. Checked 2026-10-16. ↩︎
Gateway deployment pattern — OpenTelemetry, updated 2026-09-10. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Resiliency — OpenTelemetry Collector docs. Checked 2026-10-16. ↩︎ ↩︎
Tail Sampling Processor — OpenTelemetry contrib. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Load Balancing Exporter — OpenTelemetry contrib. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Span Metrics Connector — OpenTelemetry contrib. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎
Storage — Prometheus docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Semantic conventions for HTTP metrics — OpenTelemetry. Checked 2026-10-16. ↩︎ ↩︎
Deprecating Span Events API — OpenTelemetry blog, 2026-03-17. Checked 2026-10-16. ↩︎ ↩︎
Instrumentation — Prometheus best practices. Checked 2026-10-16. ↩︎ ↩︎
Label best practices — Grafana Loki docs. Checked 2026-10-16. ↩︎ ↩︎
Configuration — Prometheus docs. Checked 2026-10-16. ↩︎ ↩︎
Jobs and instances — Prometheus docs. Checked 2026-10-16. ↩︎
High Cardinality — Honeycomb docs. Checked 2026-10-16. ↩︎
Environment Variable Specification — OpenTelemetry specification 1.61.0. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Semantic conventions for messaging spans — OpenTelemetry semantic conventions 1.44.0 (status: Development). Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Baggage — OpenTelemetry, updated 2026-02-22. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Logs — OpenTelemetry, updated 2026-08-07. Checked 2026-10-16. ↩︎ ↩︎
Logging Architecture — Kubernetes documentation, updated 2024-10-17. Checked 2026-10-16. ↩︎
Important Components for Kubernetes — OpenTelemetry, updated 2026-09-10. Checked 2026-10-17. ↩︎ ↩︎
container operator — OpenTelemetry contrib. Checked 2026-10-16. ↩︎
What is Fluent Bit? — Fluent Bit docs. Checked 2026-10-16. ↩︎ ↩︎
Filelog Receiver — OpenTelemetry contrib. Checked 2026-10-17. ↩︎
recombine operator — OpenTelemetry contrib. Checked 2026-10-16. ↩︎
Filter Processor — OpenTelemetry contrib. Checked 2026-10-16. ↩︎ ↩︎
OTTL log context — OpenTelemetry contrib. Checked 2026-10-16. ↩︎
Alerting on SLOs — The Site Reliability Workbook (Google, O’Reilly, 2018). Checked 2026-10-16. ↩︎
OpenTelemetry Sampling update — OpenTelemetry blog, 2025-10-15. Checked 2026-10-16. ↩︎ ↩︎
Native Histograms — Prometheus specification. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
opentelemetry-collector-releases v0.163.1 — GitHub, 2026-10-16. Checked 2026-10-16. ↩︎
Introduction to Grafana Alloy — Grafana Labs. Checked 2026-10-16. ↩︎
Prometheus v3.15.0 — GitHub, 2026-09-25. Checked 2026-10-16. ↩︎
Loki v3.7.8 — GitHub, 2026-09-17. Checked 2026-10-16. ↩︎
Elasticsearch v9.5.5 — GitHub, 2026-10-06. Checked 2026-10-16. ↩︎
Jaeger v2.22.0 — GitHub, 2026-10-06. Checked 2026-10-16. ↩︎
Tempo v3.1.0 — GitHub, 2026-09-29. Checked 2026-10-16. ↩︎
Architecture — Jaeger docs (v2.22), updated 2026-10-08. Checked 2026-10-16. ↩︎
Grafana v13.2.4 — GitHub, 2026-10-13. Checked 2026-10-16. ↩︎
Vendors — OpenTelemetry, updated 2026-10-09. Checked 2026-10-16. ↩︎ ↩︎
Grafana, Loki, and Tempo will be relicensed to AGPLv3 — Raj Dutt, Grafana Labs, 2021. Checked 2026-10-16. ↩︎
Elasticsearch is Open Source, Again — Shay Banon, Elastic, 2024-08-29. Checked 2026-10-16. ↩︎
Exporter Helper — OpenTelemetry Collector. Checked 2026-10-16. ↩︎ ↩︎
Redaction Processor — OpenTelemetry contrib. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
File Storage Extension — OpenTelemetry contrib. Checked 2026-10-17. ↩︎
Span metrics connector factory.go — OpenTelemetry contrib source. Checked 2026-10-17. ↩︎
Internal telemetry — OpenTelemetry Collector docs. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
TLS Configuration Settings — OpenTelemetry Collector. Checked 2026-10-17. ↩︎
telemetrygen — OpenTelemetry contrib. Checked 2026-10-16. ↩︎
base_exporter.go — OpenTelemetry Collector source. Checked 2026-10-16. ↩︎
Art. 5 GDPR: Principles relating to processing of personal data — unofficial consolidated text, intersoft consulting (the official EUR-Lex text could not be fetched automatically). Checked 2026-10-16. ↩︎
OpenTelemetry eBPF Instrumentation Marks the First Release — OpenTelemetry blog, 2025-11-03. Checked 2026-10-16. ↩︎