Skip to main content
GreenSlope

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:

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 ms

The 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:

AttributeWhat it isExample
service.nameLogical service nameweb, api, worker
service.versionHuman-readable version2026.4.21, v1.8.0
greenslope.release.idStable ID of the deployed buildA 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

Related