The span active where a signal is produced links that signal to its trace. Elide applies this to everything it records, with no code changes.
| Signal | Linked to the active span by |
|---|---|
| Log records | Trace id, span id, and trace flags on the record |
| Errors | An exception log record and span event on the span, and an elide.errors exemplar |
| Metrics | Exemplars on histogram buckets and sums |
| Profiles | A link from each CPU sample to its span |
Request handlers run inside the request’s server span, so anything they log, throw, measure, or call joins the request’s trace.
Logs
Every log record emitted inside a span carries its trace id, span id, and trace flags: records from the
OpenTelemetry logs API, captured console.* and Python logging output, and Elide’s own log records.
// handler.ts
export default {
async fetch(request: Request): Promise<Response> {
console.log("looking up order"); // exported with the request span's ids
return new Response("ok");
},
};Errors
One error produces three records that share a timestamp and the same exception.type, exception.message, and
exception.stacktrace:
| Record | Where |
|---|---|
Log record, severity ERROR, event.name = exception | Linked to the active span |
exception event on the span, and ERROR status when the failure ends the span | The active span |
elide.errors increment, with an exemplar pointing at the span | error.type, elide.error.origin |
With no active span, the log record and counter are still emitted. OTEL_SEMCONV_EXCEPTION_SIGNAL_OPT_IN=logs drops
the span events; the default, logs/dup, keeps both. Uncaught exceptions, unhandled rejections, failing request
handlers, and span.recordException(…) all take this path; recordException never changes span status.
A process that crashes cannot export. The next Elide process on the machine that has telemetry configured exports
the crash report as a FATAL error record: up to 16 reports per run, from the last 7 days, each sent once. See
Emitted telemetry.
Only reports from a process with the same service.name are exported; a crash in a process without telemetry
configured is never exported.
Metrics and exemplars
A counter or histogram measurement recorded inside a sampled span carries an exemplar: the trace id, span id, value, and time. Backends that read exemplars link a latency bucket to an example trace.
OTEL_ | Exemplars from |
|---|---|
trace_based (default) | Measurements inside a sampled span |
always_on | Every measurement |
always_off | None |
Elide keeps the latest exemplar per histogram bucket and per sum in each export. http.server.request.duration and
http.client.request.duration carry exemplars of their own request spans.
Profiles
Each guest CPU sample links to the span active on the sampled thread when the sample was taken, including after
await, in promise callbacks, and across Python asyncio tasks. See Profiles.
Trace context over HTTP
Elide’s HTTP server and clients use W3C Trace Context in both directions:
| Direction | Behavior |
|---|---|
| Inbound | A valid traceparent (with tracestate) on a served request becomes the parent of the server span. An invalid one starts a new trace. |
| Outbound | fetch sends traceparent and tracestate for its client span. A header the caller set is never replaced. |
curl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' http://localhost:8080/traces.propagators (or OTEL_PROPAGATORS) chooses what propagation.inject and propagation.extract do in your
code: tracecontext, baggage, or none. Removing tracecontext also stops Elide’s HTTP clients from sending
traceparent. node:http client requests and Python HTTP clients do not send it yet; inject it with the API:
import { context, propagation } from "@opentelemetry/api";
import http from "node:http";
const headers: Record<string, string> = {};
propagation.inject(context.active(), headers);
http.get("http://localhost:8081/inventory", { headers });Baggage
W3C baggage carries key-value pairs alongside a trace. Your code can always read, set, and send baggage through the
OpenTelemetry API. Elide’s HTTP server and clients pass the baggage header only for directions you turn on, because
baggage from a client is untrusted input:
opentelemetry {
propagation {
baggage {
inbound = true
outbound = true
allow { "tenant"; "region" }
spanAttributes { "tenant" }
}
}
}| Property | Variable | Effect | Default |
|---|---|---|---|
inbound | ELIDE_ | A served request’s baggage header becomes the handler’s baggage. | false |
outbound | ELIDE_ | fetch sends the current baggage, unless the request sets baggage itself. | false |
allow | ELIDE_ | Only these keys pass, in both directions. | all keys |
spanAttributes | — | These inbound keys are recorded on the server span as baggage.<key>; each must be in allow. | none |
- A header over 8,192 bytes or 180 members, or with any malformed member, is dropped whole.
- Baggage received on a request reaches outgoing requests only when both directions are on.
OTEL_PROPAGATORSdoes not turn baggage on for Elide’s HTTP server and clients; it affects only the API in your code.
Read inbound baggage in a handler:
import { context, propagation } from "@opentelemetry/api";
export default {
async fetch(): Promise<Response> {
const tenant = propagation.getBaggage(context.active())?.getEntry("tenant")?.value;
return new Response(`tenant: ${tenant ?? "none"}\n`);
},
};