With a destination configured, Elide records these without code changes:
| Area | Spans | Metrics | Setting |
|---|---|---|---|
| Served HTTP requests | SERVER span per request | http., http., elide. | runtime.traces, runtime.metrics |
fetch | CLIENT span per request | http. | runtime.traces, runtime.metrics |
| Process | — | CPU time, memory, threads, file descriptors, uptime | runtime.metrics |
| Errors | exception events | elide.errors | follows logs, traces, metrics |
elide build, elide test, elide install | Command, task, and test spans | — | runtime.traces |
| Elide’s own logs | — | — | runtime.logs |
Names, units, and attributes are listed in Emitted telemetry.
Serving
Every request served by elide serve, an exported fetch handler, or a node:http server gets a server span and
the HTTP server metrics. The handler runs inside the span, so spans your code starts there are its children:
// server.ts
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("orders");
export default {
async fetch(request: Request): Promise<Response> {
return tracer.startActiveSpan("load order", async (span) => {
span.setAttribute("order.id", new URL(request.url).searchParams.get("id") ?? "");
span.end();
return new Response("ok\n");
});
},
};
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 elide serve server.ts
A request carrying a valid traceparent continues the caller’s trace. A 5xx response, a handler that throws, an
abandoned response, or a reset connection marks the server span ERROR.
Request rate comes from the count of http.server.request.duration: divide the increase in count by
the elapsed seconds. These counts include unsampled requests, so changing trace sampling does not
change the reported throughput. http.server.active_requests tracks requests still in flight.
http.server.request.body.size and http.server.response.body.size record body sizes in bytes.
Known empty bodies contribute zero; unknown response sizes are omitted. These histograms share the
duration metric’s method, scheme, protocol, status, error, and known route dimensions. Raw paths,
query strings, user agents, and baggage are not automatic metric dimensions. Body-size metric names
follow OpenTelemetry’s development semantic conventions.
Routes
Server spans are named after the method: GET. A node:http server can name the matched route, which sets
http.route and renames the span GET /items/:id:
// routes.ts
import http from "node:http";
import { trace } from "@opentelemetry/api";
http
.createServer((req, res) => {
trace.getActiveSpan()?.setAttribute("http.route", "/items/:id");
res.end("ok\n");
})
.listen(8091);
A router that sets req.route (Express and compatible routers) supplies the route without that line; req.baseUrl
is prefixed. Inside a node:http handler or an exported fetch handler the active span stands for the server
span, and only http.route set on it reaches the exported span.
fetch
Each fetch gets a client span, parented to the span active where fetch was called, and sends traceparent and
tracestate so the next service continues the trace. A traceparent header you set yourself is kept.
import { trace } from "@opentelemetry/api";
await trace.getTracer("sync").startActiveSpan("sync inventory", async (span) => {
await fetch("https://inventory.example.com/items"); // child CLIENT span
span.end();
});
Process metrics
process.cpu.time, process.memory.usage, process.thread.count, process.unix.file_descriptor.count, and
process.uptime, collected at each metric export on Linux and macOS.
Builds and tests in CI
Enable build reports
Build-report export is opt-in: configure an OTLP destination with CLI flags, elide.pkl, or
environment variables. With no destination configured,
Elide exports no build or test telemetry. Once a destination accepts traces, runtime.traces enables the command,
task, execution-unit, and individual-test spans together; it defaults to true. There is no separate build-report flag.
For one invocation, point Elide at a running OTLP/HTTP collector. Place these global flags before the command:
elide --otel-endpoint=http://localhost:4318 --otel-protocol=http/protobuf build
elide --otel-endpoint=http://localhost:4318 --otel-protocol=http/protobuf test
For a project, add this configuration to elide.pkl (merge the block into an existing manifest):
amends "elide:project.pkl"
name = "my-project"
opentelemetry {
service { name = "my-project-builds" }
destinations {
["collector"] {
endpoint = "http://localhost:4318"
protocol = "http/protobuf"
signals { "traces" }
}
}
runtime { traces = true }
}
Then run elide build or elide test normally. Replace the endpoint with your collector’s base URL;
Elide appends /v1/traces for HTTP export. For gRPC, use port 4317 and protocol grpc.
The destination above exports only traces. Configure authentication through
environment-backed headers.
To suppress telemetry for one invocation, even when the project or environment enables it:
elide --no-otel build
elide --no-otel test
In the manifest, opentelemetry { enabled = false } disables all telemetry. To keep application telemetry but
suppress automatic build/test spans, set opentelemetry { runtime { traces = false } }; this also disables Elide’s
automatic HTTP spans. Setting only traces { enabled = false } controls application spans and does not disable
runtime spans. OTEL_TRACES_EXPORTER=none disables both kinds of trace export.
To verify export, find elide.build or elide.test in your collector under the configured service name, then group
its descendants by elide.invocation.id. Task, execution, and test spans appear when that work actually runs.
Sampling still applies: an unsampled upstream TRACEPARENT suppresses spans with the default parent-based sampler.
For a local diagnostic run, OTEL_TRACES_SAMPLER=always_on records every span; it does not guarantee delivery if
an exporter queue fills. See Troubleshooting if no spans arrive.
ELIDE_BUILD_TELEMETRY is a separate local timing-profile switch. It is not required for OTLP build reports and
does not enable OTLP export.
Correlate builds and tests
elide build, elide test, and elide install record a root span for the command (elide.build, elide.test,
elide.install). Build stages and tasks become child spans with outcome and cache-hit attributes, and each test case
nests under its execution unit (a JS file or JVM launcher fork), which nests under the owning task. The root span
continues a W3C TRACEPARENT from the environment, so a CI job’s own trace can include the build:
TRACEPARENT=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 \
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com \
elide test
The command span carries elide.ci.* attributes from GitHub Actions, GitLab CI, Buildkite, and CircleCI variables
(see Emitted telemetry) and process.exit.code. Elide waits up to 5 seconds
for the export before the command returns.
Build-report spans carry schema version 1, an elide.span.type, and an elide.invocation.id on every span.
The invocation ID combines the command’s trace ID and span ID: two commands can share a CI trace without merging
into one build. Use this ID for a single report, and workspace/project name plus task path and test identity to
compare reports across runs. Scope comparisons to the same repository or service; project names alone are not
globally unique. CI revision and pipeline attributes remain on the command span.
Task spans include their implementation type, workspace-qualified path, prerequisite paths, cacheability, and outcome. Driver operations remain nested under tasks. JS files have zero-based shard indexes and the discovered file count; JVM source-set forks have separate execution IDs, including discovery fallback forks. This describes local execution units; it does not add distributed sharding. Each execution reports result counts and becomes an error on a failed test or unsuccessful execution, even when the runner returned normally.
Individual tests carry a stable, length-prefixed display-path identity and an occurrence number within their
execution. Identical display paths intentionally share a comparison key; occurrences distinguish emitted results,
not retries. JUnit’s text feed cannot yet provide native unique IDs. Test span positions are reconstructed from
result arrival and reported duration, clamped to their execution’s lifetime; elide.test.duration_ns preserves the
reported duration. This is sufficient for result comparison but not an exact concurrent-test timeline.
Execution spans measure their full lifecycle. Filtered tests contribute to execution counts but emit no test span.
Telemetry remains subject to tracing configuration, sampling, and exporter queue limits. It is not a lossless build event archive. Structured JUnit identities/start events, per-task scheduler wait, artifact provenance, and a durable report store are follow-up work; this schema does not claim complete Bazel or Gradle report parity.
In GitHub Actions:
- name: Test
run: elide test
env:
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OTEL_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_HEADERS }}
OTEL_SERVICE_NAME: my-service-ci
Elide’s own logs
Elide’s warnings and errors are exported as log records in the dev.elide.runtime scope, at or above runtime.logs
(warn by default), with the active span’s ids. Set runtime { logs = "off" } to stop them.
Rust instrumentation and library diagnostics also use this runtime scope. Structured log fields and span
parentage are retained. Console output follows ELIDE_LOG and the active console or progress logger;
export independently follows runtime.logs. Application log and trace settings do not enable runtime signals.
Compio’s internal task and poll spans are off by default. Set ELIDE_OTEL_RUNTIME_INTERNAL_TRACES=true
to include them when runtime tracing is enabled and a telemetry destination is configured. This can produce
many spans; library log events still follow runtime.logs. Tokio’s unstable internal tracing is not enabled
in shipped builds. Library events do not automatically become metrics, and exporter diagnostics are excluded
from export to prevent feedback.
Not instrumented yet
| Area | Workaround |
|---|---|
node:http and node:https client requests: no client span, no traceparent | Inject headers with propagation.inject; see Trace context |
Python HTTP clients (urllib, requests) | Inject headers with opentelemetry. |
| Serving from Python | Serve from JavaScript or TypeScript |
| Garbage collection and event-loop delay metrics | — |
| Database, file system, and child process spans | Create spans in your code |
| Java and Kotlin programs | Not supported; see Languages |