Spans and traces
Everything GreenSlope shows you — the Doctor page, latency charts, error breakdowns, the incident loop — is a view over spans and traces. If you're new to OpenTelemetry, learn these two primitives and the rest follows.
A span is one unit of work
A span represents a single operation with a start time, an end time, and some attributes about what happened. An HTTP request is a span. A database query is a span. A queue publish is a span.
A span has:
- A name —
GET /checkout,SELECT users,stripe.charges.create. - A duration — start timestamp and end timestamp.
- Attributes — key/value pairs like
http.status_code=500ordb.system=postgres. - A status —
ok,error, orunset.
That's it. Spans are cheap; emitting a lot of them is fine.
A trace is a tree of spans
A trace is what you get when you connect related spans: the HTTP request that kicked off a database query that kicked off a call to Stripe. Each span points at its parent, and the whole thing forms a tree.
GET /checkout 320 ms
├── authenticateUser 12 ms
├── loadCart 45 ms
│ └── SELECT cart_items 38 ms
├── stripe.charges.create 245 ms
│ └── POST api.stripe.com/v1/charges 238 ms
└── writeOrder 18 ms
└── INSERT orders 14 msThe trace is the thing you look at when you want to answer "why was that request slow?" or "what code path produced that error?". One trace per user action is the mental model that scales furthest.
Resource attributes describe the whole service
Attributes on a span describe what that span did. Resource attributes describe which service emitted it — and they're set once, at SDK startup, not per-span.
GreenSlope requires three resource attributes on every span:
| Attribute | What it is | Example |
|---|---|---|
service.name | Logical service name | web, api, worker |
service.version | Human-readable version | 2026.4.21, v1.8.0 |
greenslope.release.id | Stable ID of the deployed build | A git SHA |
service.name is how we group spans into services on the dashboard.
greenslope.release.id is how we tie a spike to a commit. See
Release attribution for the full
mechanism.
Sampling: don't send every span
In production, instrumenting every request and sending every span at 100% is wasteful. Sample at 10% for most workloads:
import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base"
new NodeSDK({
sampler: new TraceIdRatioBasedSampler(0.1)
// ...
})The sampling decision is made at the root of the trace, so a sampled trace keeps all its child spans — you never get broken trees.
Where to look in the product
- Live traces — the last few minutes of spans, arriving as they land. Useful during quickstart verification and during incidents.
- Trace view — drill into a single trace to see the full tree.
- Doctor page — shows the health of a service derived from its recent spans. See Alerts.
Related