Build the safeguards
Eight explained steps, a source example and a checkpoint at each decision.
Read the tutorialOpenTelemetry / Observability / SLOs
Choose a pattern by the failure it addresses. Several safeguards can be necessary at once.
| Approach | Useful when | Decision or limitation |
|---|---|---|
| Health check | Process readiness or dependency probes | Does not measure every user operation |
| Metrics | Rates, distributions and alerting | Aggregation loses individual request detail |
| Traces | Follow work within one request | Sampling and missing instrumentation can hide work |
| Structured logs | Discrete events with diagnostic context | Need correlation, retention and data controls |
| Mean latency | A useful aggregate in some investigations | Can conceal a slow minority |
| Latency SLI | A defined fraction of eligible fast responses | Requires a target, window and eligibility contract |
| Environment | What you can do | Scope |
|---|---|---|
| This browser lab | Click, compare, reset and inspect exact source | The worker creates real OpenTelemetry spans around actual timer waits. The database is a timed fixture. Eight requests teach latency arithmetic, not a production SLO window. A local HTTP service and optional Prometheus/Grafana recipe accompany the download. |
| Independent download | Run the same prebuilt lab with Python’s local server | No package installation or automatic external calls needed for the prebuilt preview |
| Native exercise | Run language tests and the included local adapter or script | Requires the documented local toolchain; does not provision a cloud service |
Both versions return eight successful responses. The safeguarded trace contains the request, service and database spans under one trace ID. A long tail remains visible even though availability is 100%.
Begin with an observable outcome, inspect the decisions that produced it, then test a repair. The companion tutorial explains the mechanism; the how-to guide takes the same source into an independent environment.
Prefect’s platform opening explicitly includes service indicators, error budgets and OpenTelemetry-style monitoring.
Eight explained steps, a source example and a checkpoint at each decision.
Read the tutorialUnderstand where each pattern helps and which tradeoffs remain.
Compare approachesDownload the independent environment, run the native checks and try a specific extension.
Open the how-to guideLab discussion / Trace the Wait Lab
Ask about the code, compare an approach, or describe an edge case. Reader comments are reviewed before publication.
Code walkthrough
These short excerpts connect the lab’s behavior to its implementation. Each example links to the full tutorial step for its context, tradeoffs and checkpoint. Names and dependencies belong to that source; the excerpts are not standalone programs.
The root span describes GET /report, not an internal implementation detail. Its duration represents the operation the visitor experiences in this fixture. A health endpoint checks a different question and cannot replace a user-facing indicator.
const root = tracer.startSpan("GET /report", {}, ROOT_CONTEXT);
The service and database-fixture spans surround work that can explain the request. Their names remain bounded and understandable. Attributes describe the operation without embedding arbitrary customer data. Too many tiny spans can create overhead without helping a diagnosis.
tracer.startSpan("db.query orders", {}, parentContext);
The broken database span uses ROOT_CONTEXT and becomes an independent trace. The safeguarded path places the service span in the supplied context before creating the child. A real network boundary additionally needs compatible injection and extraction of trace headers.
const childContext = trace.setSpan(parentContext, serviceSpan);
The example uses nearest-rank p95 on eight recorded durations. One long sample can dominate the tail while barely moving an average. A tiny fixture makes this arithmetic visible; production percentile estimates depend on aggregation methods, histogram buckets and traffic volume.
sorted[Math.ceil(0.95 * sorted.length) - 1]
A target states the permitted fraction of bad eligible events over a specified window. The lab’s illustrative target is 90% under 100 ms. Real alerts need a window, minimum useful volume and burn-rate reasoning rather than an alarm on every slow request.
budgetFraction = 1 - target;
consumedFraction = badEvents / eligibleEvents;