Observability Beyond Metrics: The Art of System Storytelling
Move past green-light dashboards to observability that narrates system behavior, user journeys, and business impact via distributed tracing.
Every dashboard is green, every metric sits inside its threshold, and customers still report broken checkouts. Aggregate metrics describe components one at a time, so a failure that lives in the path between components stays invisible to each of them.
Distributed tracing closes that gap. A single trace follows one request through every service it touches and records the order in which things happened. The default worth reaching for is one complete user journey instrumented end to end, with business context attached to every span.
What a Single Trace Shows
Consider a failure mode that recurs in checkout flows. Infrastructure dashboards look healthy: CPU well below capacity, memory nominal, per-service response times in the low hundreds of milliseconds. Conversion, meanwhile, falls off a cliff.
A trace explains it in one view. A recommendation service loses its cache and starts issuing dozens of API calls per checkout request instead of two. Each call is individually fast, so no per-service latency chart moves. The checkout page waits for all of them, and the user leaves before it settles. The failure exists only in the aggregate of the path, and the path is what a trace records.
// What the per-service dashboards report
interface TraditionalMetrics {
cpu: "40% average";
memory: "6GB/8GB";
responseTime: "200ms p50";
errorRate: "0.1%";
}
// What one trace of the same request reports
interface TraceView {
userJourney: "checkout_attempt";
totalDuration: "8.3 seconds";
spanCount: 247; // a healthy checkout sits closer to 15
criticalPath: {
service: "recommendation-service",
operation: "get_related_products",
calls: 47, // the cache miss, made visible
totalTime: "6.8 seconds"
};
}
Building Narrative-Driven Observability
The telemetry that pays off describes user interactions across the whole system without breaking them into per-service fragments. Three pieces make that work: journey-scoped spans, business attributes on those spans, and alerting that reads the journey.
The OpenTelemetry Journey Mapper
Here is how services are instrumented to capture complete user journeys:
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
import { BusinessContext } from './business-metrics';
class CheckoutService {
private tracer = trace.getTracer('checkout-service', '1.0.0');
async processCheckout(userId: string, cart: CartData): Promise<CheckoutResult> {
// Start with business context, then add technical detail
const startedAt = Date.now();
const span = this.tracer.startSpan('user.checkout.attempt', {
attributes: {
'user.id': userId,
'user.tier': await this.getUserTier(userId),
'business.cart_value': cart.totalValue,
'business.revenue_impact': cart.totalValue,
'journey.step': 'checkout_initiated',
'journey.entry_point': cart.referrer
}
});
// Propagate context across service boundaries
return context.with(trace.setSpan(context.active(), span), async () => {
try {
// Each step adds to the story
span.addEvent('inventory.validation.started', {
items_to_check: cart.items.length
});
const inventory = await this.validateInventory(cart);
if (!inventory.allAvailable) {
// This tells us WHY the checkout failed
span.setAttributes({
'failure.reason': 'inventory_unavailable',
'failure.items': inventory.unavailableItems.join(','),
'business.impact': 'checkout_abandoned'
});
span.setStatus({
code: SpanStatusCode.ERROR,
message: 'Inventory check failed'
});
return { success: false, reason: 'out_of_stock' };
}
// Continue building the narrative...
span.addEvent('payment.processing.initiated');
const payment = await this.processPayment(cart, userId);
span.setAttributes({
'journey.completed': true,
'business.order_value': payment.amount,
'journey.total_duration_ms': Date.now() - startedAt
});
return { success: true, orderId: payment.orderId };
} catch (error) {
// Capture the failure narrative
span.recordException(error);
span.setAttributes({
'failure.stage': this.getCurrentStage(),
'failure.recovery_attempted': true,
'business.impact': 'revenue_lost'
});
throw error;
} finally {
span.end();
}
});
}
}
From Traces to Business Impact
Connecting a trace to business metrics is what makes it answerable during an incident. The analyzer below turns a trace ID into both readings at once:
class BusinessImpactAnalyzer {
async analyzeTraceImpact(traceId: string): Promise<ImpactReport> {
const trace = await this.getTrace(traceId);
const businessContext = this.extractBusinessContext(trace);
return {
// Technical story
technicalNarrative: {
entryPoint: trace.rootSpan.service,
failurePoint: this.findFailureSpan(trace),
cascadeEffect: this.analyzeCascade(trace),
performanceBottleneck: this.findSlowestPath(trace)
},
// Business story
businessNarrative: {
userIntent: businessContext.journeyType, // "purchase", "browse", etc.
valueAtRisk: businessContext.cartValue || businessContext.subscriptionValue,
userSegment: businessContext.userTier,
conversionStage: this.getConversionStage(trace),
alternativePaths: this.findAlternativeJourneys(businessContext)
},
// The combined story
impact: {
immediateRevenueLoss: this.calculateImmediateLoss(businessContext),
projectedChurnRisk: this.predictChurnImpact(trace, businessContext),
brandDamageScore: this.assessBrandImpact(trace),
recoveryActions: this.generateRecoveryPlan(trace, businessContext)
}
};
}
}
AI-Powered Pattern Recognition
Full-stack instrumentation produces more trace data than anyone reads. A language model is a reasonable first pass over that volume, because the signal it is good at finding is repetition: the same ordering of events before every failure. It is much weaker at being right the first time, and the published research is specific about how much weaker.
The Retrieval Step
Handing a batch of traces to a model with nothing else attached is the weakest configuration anyone has measured. Chen et al. evaluated RCACopilot at EuroSys ‘24 against 653 incidents collected over one year from Microsoft’s Transport service, and reported a micro-F1 of 0.766 for the full pipeline. The same table gives plain GPT-4 prompting, with no similar past incidents retrieved, a micro-F1 of 0.026. Retrieving matched prior incidents is what carries the result. The prompt is the cheap part.
That paper also reports a macro-F1 of 0.533 against the micro-F1 of 0.766, so accuracy falls off sharply on rare root-cause categories. Those are exactly the intermittent failures worth handing to a model, which is why the gap matters.
The retrieval therefore belongs in the code, not in the prompt text:
class TraceHypothesisBuilder {
async proposeCause(traces: DistributedTrace[]): Promise<Hypothesis> {
// The load-bearing step: find past incidents that looked like this one
const fingerprint = this.fingerprintFailures(traces); // service, operation, error class, span ordering
const precedents = await this.incidentStore.findSimilar(fingerprint, { limit: 10 });
if (precedents.length === 0) {
return { status: 'no_precedent', traceIds: fingerprint.traceIds };
}
const answer = await this.llm.analyze({
instruction:
'Propose one root cause. Cite the span IDs that support it. ' +
'Report insufficient evidence when the traces do not support a cause.',
precedents: precedents.map(p => ({
summary: p.summary,
cause: p.rootCause,
fix: p.remediation
})),
failing: this.summarizeSpans(traces) // span names, timings, parent links; not raw JSON
});
return {
status: 'hypothesis',
cause: answer.cause,
citedSpanIds: answer.citedSpanIds, // the only field worth acting on
precedentIds: precedents.map(p => p.id)
};
}
}
There is no confidence score in that return value. A decimal printed next to a root cause reads as calibration, and none of the research cited here supports treating a model’s stated confidence as a calibrated probability over telemetry. The field worth keeping is the list of span IDs, because a span ID can be checked.
Accuracy on Real Incidents
Roy et al. at Microsoft tested LLM agents against 107,000 real production incidents, then had two authors manually grade 100 predictions per method. A retrieval baseline and a chain-of-thought agent were each judged correct 39% of the time, and a ReAct agent 35%. Hallucination rates separate them: among incorrect predictions, the retrieval baseline hallucinated in 49% of cases against 6% for ReAct. The paper’s own summary is that ReAct has the highest precision of the three, at the cost of lower overall accuracy.
RCAEval, a neutral benchmark of 735 failure cases across 11 fault types and 3 microservice systems, is candid about the classical side: “Existing methods mostly obtain moderate results”, with the best average Avg@5 scores of 0.46 and 0.54 across 15 baselines. It carries no LLM baselines at all, so there is no neutral ranking of LLM trace analysis against classical root-cause analysis to appeal to.
Vendors publish a different quantity. Datadog’s engineering write-up on its Bits AI SRE agent reports a reduction in time to resolution of up to 95%, describes an internal benchmark of real incidents with known causes scored by an LLM judge, and gives no accuracy figure. Time to resolution and correctness are not the same measurement, and the write-up reports only the first. The research numbers above do not fill that gap. Hand-graded correctness, F1 and Avg@5 answer different questions and do not combine into one accuracy score.
The pattern worth looking for has a specific shape: a fixed delay between an invalidation event and the failures that follow it, visible only because traces preserve ordering across services. Treat what comes back as a hypothesis rather than a diagnosis, and confirm the mechanism by reading the spans the model cited. Practitioners want the same guarantee: in the Grafana Labs Observability Survey 2026, which polled 1,363 respondents across 76 countries, 95% said it matters that AI shows its reasoning.
The line item is small. Honeycomb published the numbers behind its shipped Query Assistant: a monthly OpenAI bill of about 30 USD, a few hundred dollars per month all in, roughly 5 seconds of average latency and a P99 measured in 30 seconds or higher before tuning. It also published the effect it could measure, which landed on learning rather than on answers. Six weeks in, 26.5% of teams that had used the assistant were still writing queries by hand, against 4.5% of teams that had not.
Context-Aware Alert Reduction
Over-instrumentation ends in alert fatigue, and the field reports it as the dominant blocker. In the Grafana Labs Observability Survey 2026, 30% of respondents named alert fatigue the biggest obstacle to faster incident response, the most common answer by a wide margin.
Story-driven alerting gates on business context before it pages anyone:
class StoryDrivenAlerting {
async evaluateAlert(anomaly: TraceAnomaly): Promise<AlertDecision> {
// Don't alert on technical metrics alone
if (!anomaly.hasBusinessContext()) {
return { shouldAlert: false, reason: "No business impact detected" };
}
// Build the complete story
const story = await this.buildNarrative(anomaly);
// Only alert if the story matters
const impactScore = this.calculateImpactScore({
affectedUsers: story.userCount,
revenueAtRisk: story.potentialLoss,
customerTier: story.primaryUserSegment,
timeOfDay: story.isBusinessHours,
similarIncidents: await this.findSimilarStories(story)
});
if (impactScore < this.alertThreshold) {
// Log it, but don't wake anyone up
await this.logForLaterAnalysis(story);
return { shouldAlert: false, reason: "Below impact threshold" };
}
// Create an alert that tells the whole story
return {
shouldAlert: true,
channel: this.getChannelForImpact(impactScore),
message: this.createNarrativeAlert(story),
suggestedActions: await this.generatePlaybook(story),
autoRemediation: this.canAutoRemediate(story)
};
}
private createNarrativeAlert(story: IncidentStory): string {
// A novel incident has no precedent, so it arrives with trace IDs and no cited spans
const evidence = story.citedSpanIds?.length
? `Spans that support it: ${story.citedSpanIds.join(', ')}`
: `No precedent matched. Traces to read: ${story.traceIds?.join(', ') || 'none recorded'}`;
return `
Incident Story:
What's happening: ${story.summary}
Who's affected: ${story.affectedUsers} users (${story.userSegments})
Business impact: $${story.revenueImpact}/hour potential loss
The journey that's broken:
${story.brokenJourney.map(step => `→ ${step}`).join('\n')}
Root cause hypothesis: ${story.rootCause || 'None proposed'}
${evidence}
Similar incident: ${story.previousIncident?.summary || 'No similar incidents found'}
Suggested actions:
${story.suggestedActions.map((action, i) => `${i+1}. ${action}`).join('\n')}
`;
}
}
What Drives the Cost
Observability spend comes down to three levers, and all three are decisions rather than quoted prices. Retention window sets how much trace storage you pay for. Sampling rate sets how much traffic reaches storage at all. Cardinality sets what the query layer costs, and the business attributes that make traces useful (user tier, campaign ID, cart value) are exactly the high-cardinality fields that drive it up.
Which lever moves the invoice depends on the unit you are billed in, and four common vendors each picked a different one. AWS bills per trace: the CloudWatch pricing page lists X-Ray at 0.000005 USD per trace recorded in US East (N. Virginia), which is 5.00 USD per million, after 100,000 free traces a month. Datadog bills per host plus per indexed span: APM lists at 31 USD per host per month on annual pricing and includes 150 GB of ingested spans and 1 million indexed spans per host per month, with 0.10 USD per GB beyond that. Grafana Cloud bills per GB: for customers starting on or after 13 February 2026 the Application Observability docs list 0.025 USD per host hour and 0.50 USD per GB of traces, logs and profiles, with no included telemetry. Honeycomb bills per event: 20 million events a month on the free plan, with Pro starting at 150 USD for 50 million events.
Run one change through those units and the answers diverge. Ten Datadog APM hosts cost 310 USD a month and carry an allowance of 1,500 GB and 10 million indexed spans, so halving span volume from 1,200 GB to 600 GB saves nothing: both figures sit inside the allowance. Push the same fleet to 2,000 GB and the overage is 500 GB at 0.10 USD per GB, so 50 USD. A halving of the same shape on Grafana Cloud, 600 GB down to 300 GB at 0.50 USD per GB, takes that line from 300 USD to 150 USD on the next invoice. On AWS the arithmetic is per trace instead: 10 million traces recorded leaves 9.9 million billable after the free tier, so 49.50 USD. Grafana’s February 2026 change, which removed included telemetry for new customers, is a reminder that the unit itself can move under a product you already run.
Instrumentation also costs something inside the services themselves: extra spans mean extra allocations and extra traffic to the collector. Tail sampling raises that further, because the components holding traces until a decision can be made “must be stateful systems that can accept and store a large amount of data”, and OpenTelemetry’s documentation warns they can grow to “dozens or even hundreds of compute nodes”. Budget the collector fleet as production infrastructure with its own capacity plan, because it fails the way production infrastructure fails.
Practical Implementation Strategies
Three decisions matter more than which vendor you pick:
Start With One User Journey
Instrumenting everything at once produces broad, shallow coverage that answers nothing. Pick the journey carrying the most value, usually checkout or signup, and instrument it completely:
# Start here, not everywhere
priority_instrumentation:
phase_1:
- user_registration_flow
- checkout_process
- search_to_purchase
phase_2:
- admin_operations
- background_jobs
- third_party_integrations
phase_3:
- internal_tools
- reporting_systems
- everything_else
Sampling Against Your Billing Unit
Sampling is not optional at scale, and aggressive rates lose less than they sound like they should. OpenTelemetry’s sampling documentation states that “for high-volume systems, it is quite common for a sampling rate of 1% or lower to very accurately represent the other 99% of data”. What the reduction saves depends on the billing unit above: under per-trace or per-GB pricing it shows up on the next invoice, under per-host pricing it shows up only once you have crossed the included allowance.
class SmartSampling {
getSampleRate(span: Span): number {
// Always sample errors and high-value transactions
if (span.status === 'ERROR') return 1.0;
if (span.attributes['user.tier'] === 'premium') return 1.0;
if (span.attributes['business.value'] > 1000) return 1.0;
// Sample based on business hours
const hour = new Date().getHours();
if (hour >= 9 && hour <= 17) return 0.1; // 10% during business hours
// Minimal sampling during quiet periods
return 0.01; // 1% overnight
}
}
The Team Training Investment
Technical tools are only half the battle. The other half is building a team that thinks in narratives:
interface TeamTrainingPlan {
week1: "Distributed tracing fundamentals",
week2: "OpenTelemetry instrumentation workshop",
week3: "Reading and interpreting trace narratives",
week4: "Correlating traces with business metrics",
week5: "AI-assisted incident analysis",
week6: "Building custom dashboards that tell stories",
ongoing: {
monthlyReviews: "Analyze interesting incidents together",
documentationDays: "Everyone writes one observability guide",
rotationProgram: "Everyone does one week of incident command",
knowledgeSharing: "Weekly 'trace detective' sessions"
}
}
Common Pitfalls
Dashboards Nobody Opens
Panel sprawl is the default outcome of dashboard-first observability: a board gets added after every incident and almost none get deleted. The ones that survive follow a user from landing page to purchase, with each step annotated by the business metric it moves. A panel that shows a number without showing whose journey the number belongs to gets opened once and then forgotten.
Traces Without Business Context
Collecting more telemetry rarely makes an incident answerable. Linking traces to business events does. Add campaign_id and promo_code as span attributes and a question like “why did conversion drop during the biggest marketing push?” turns from an investigation into a query.
Trusting AI on Dirty Telemetry
AI-assisted analysis inherits the quality of the traces it reads. Inconsistent span names, missing parent links, and attributes that mean different things in different services all produce confident nonsense. Fix the instrumentation conventions first. A naming convention enforced in a shared library is worth more than a better model.
Principles Worth Keeping
Four hold up regardless of vendor:
-
Start with business outcomes. Instrumenting revenue-generating paths first delivers a faster return than broad infrastructure coverage.
-
Invest in trace quality over quantity. Better to have perfect traces for critical paths than mediocre traces for everything.
-
Build team culture before tools. The best observability stack is useless if the team doesn’t know how to read the stories it tells.
-
Plan growth around span count. Each newly instrumented service multiplies spans on paths that already existed, so trace volume climbs faster than traffic. Set retention and sampling against that curve or pay the re-architecture cost later.
Future Directions
The Grafana Labs Observability Survey 2026 asked 1,363 practitioners in 76 countries what they want from the next generation of tooling, and the answers point in three directions.
Predictive Narratives
92% of those respondents see value in AI surfacing issues before they cause downtime. That number describes appetite. Whether prediction works is a separate question, and the root-cause research above answers it unkindly: even with the incident already in hand, the graded predictions were correct 39% of the time at best.
The prediction that does ship is arithmetic, not inference. Error-budget burn rate says when the budget runs out, and the Google SRE Workbook publishes the table. Against a 99.9% availability SLO measured over 30 days, a burn rate of 1 (a 0.1% error rate) exhausts the budget in 30 days, a burn rate of 2 in 15 days, a burn rate of 10 (a 1% error rate) in 3 days, and a burn rate of 1,000 (total failure) in 43 minutes. The Workbook’s recommended starting configuration pages at 14.4 times burn measured over a 1-hour window with a 5-minute short window, which is 2% of the budget consumed; pages at 6 times over 6 hours with a 30-minute short window, which is 5%; and files a ticket at 1 times over 3 days with a 6-hour short window, which is 10%.
Commercial forecasting works on the same scale. Datadog’s forecast monitors let you set alert lead time “between 12 hours and 3 months”, with presets at 24 hours, 1 week and 1 month, and the seasonal algorithm “requires at least two seasons of history and uses up to six seasons for forecasting”. No shipped product forecasts a specific transaction failure seconds ahead. A predictive narrative you can act on says the disk fills next Tuesday, or the error budget is gone by Thursday afternoon.
Business-First Instrumentation
The next generation of observability starts from business KPIs and derives the technical metrics that support them.
Autonomous Remediation
Narrow versions of this already ship: systems that detect an issue, match it to a known incident signature, and apply the recorded remediation (restart a pod, roll back a deploy, drain a node). Appetite for the wider version is real but not settled. The same Grafana survey found 77% see value in AI acting autonomously, while 15% say they do not trust AI to execute at all. Correctness on hand-graded predictions tops out at 39% in the research above, so the defensible boundary is to let automation act only where the action is cheap to undo.
Your Next Steps
A workable order of operations:
- Pick one critical user journey and instrument it completely with OpenTelemetry
- Add business context to every span: user tier, revenue impact, conversion stage
- Create your first story-driven dashboard that shows a complete user journey
- Experiment with AI analysis on top of retrieved past incidents rather than a raw prompt, and grade its answers before trusting them
- Make trace reading a team habit with a regular session where someone walks a recent incident trace end to end
References
- Observability primer - opentelemetry.io - OpenTelemetry’s definition of observability and the relationship between traces, metrics, and logs as the three telemetry signals
- Sampling - opentelemetry.io - How head and tail sampling work, why a 1% rate can still represent the remaining traffic, and what tail sampling demands of the collector fleet
- Observability 2.0 - Honeycomb - Charity Majors’ argument for moving beyond metrics dashboards to high-cardinality event-driven observability
- Alerting on Service-Level Objectives - Google SRE Workbook - The burn-rate tables behind error-budget alerting, including time to exhaustion by burn rate and the recommended multiwindow alert parameters
- Exploring LLM-based Agents for Root Cause Analysis - Roy et al. - Microsoft study grading LLM agents against 107,000 production incidents, with correctness and hallucination rates per method
- Automatic Root Cause Analysis via Large Language Models for Cloud Incidents - Chen et al. - The RCACopilot paper (EuroSys ‘24), showing how much of the result comes from retrieving similar past incidents rather than from the model
- RCAEval: A Benchmark for Root Cause Analysis of Microservice Systems with Telemetry Data - Neutral benchmark of 735 failure cases and 15 baselines, and a candid summary of how well current root-cause methods actually perform
- So We Shipped an AI Product. Did it Work? - Honeycomb - First-party adoption, latency, and cost figures for an LLM query assistant running inside an observability product
- How we built an AI SRE agent - Datadog - Vendor account of an incident-investigation agent, useful for the time-to-resolution claim and for what its benchmark does not report
- Grafana Labs Observability Survey 2026 - Survey of 1,363 practitioners across 76 countries covering alert fatigue, tooling cost, and where the field wants AI applied
- Forecast Monitors - Datadog documentation - Configuration reference for predictive monitors, including the supported alert lead times and the data each algorithm requires
- Amazon CloudWatch pricing - List prices for X-Ray traces and Transaction Search spans, the per-trace billing model referenced in the cost section
- Datadog pricing - APM list prices, the per-host billing model, and the ingested-span and indexed-span allowances included with each host
- Application Observability pricing - Grafana Cloud - Per-GB billing rates for traces, logs, and profiles, and the February 2026 change that removed included telemetry for new customers
- Honeycomb pricing - Per-event billing model with the free and Pro plan event allowances used in the cost comparison
Story-driven observability works best in systems where incidents have business consequences visible in the data (revenue drops, conversion falls, SLO breaches that correlate with user behaviour). It is less useful when a service is purely internal with no business-facing metrics to attach. Where basic alerting is still immature, fix that first: a trace explains an incident nobody was paged about only after the fact.
Related posts
A beginner's guide to OpenTelemetry covering traces, metrics, and logs with practical implementation examples, common pitfalls, and a terminology glossary.
Real-world debugging techniques, monitoring strategies, and lessons learned from notification system failures in high-stakes production environments
Fargate failure modes that green dashboards hide: ENI quota exhaustion, subnet routing breaks, memory leaks, and the checks that find each one.
Instrument AWS Lambda for production: CloudWatch custom metrics, X-Ray tracing, structured logging, and alerts that track business impact.
A blameless postmortem model that fixes the system instead of finding a culprit, with a copy-paste template and where individual accountability still applies.