Instrument code with the standard @opentelemetry/api and
@opentelemetry/api-logs. Elide implements them: there is no SDK, exporter, or collector to add to the
application, and libraries that already use the API work unchanged.
// app.ts
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("checkout");
await tracer.startActiveSpan("charge", async (span) => {
span.setAttribute("order.id", 42);
console.log("charged");
span.end();
});OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 elide run app.tsThe API package
| Your project | import "@opentelemetry/ resolves to |
|---|---|
No @opentelemetry/api installed | Elide’s built-in copy (API 1.9) |
@opentelemetry/api 1.x installed | Your copy, backed by Elide |
The same holds for @opentelemetry/api-logs and @opentelemetry/api/experimental. Install the package as a dev
dependency when you want its TypeScript types:
elide add -D @opentelemetry/apiElide registers its tracer, meter, logger, context manager, and propagator in the API’s global registry before your code runs, so any 1.x copy finds them. When telemetry is not configured, the API returns no-op implementations.
Traces
Tracers, spans, links, events, and status follow the OpenTelemetry API and SDK rules: invalid attribute values are
dropped with a warning, operations on ended spans warn, UNSET status is ignored, and OK is final.
Spans implement Symbol.dispose, so using ends them at the end of the block:
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("jobs");
{
using span = tracer.startSpan("nightly");
span.setAttribute("job.items", 120);
} // span ends hereElide’s tracers also have withSpan(name, [options], [context], fn): it runs fn with a new active span, ends the
span when fn returns or its promise settles, and records a throw or rejection as an ERROR:
const tracer = trace.getTracer("jobs") as any;
await tracer.withSpan("import", async (span) => {
await importRows();
});wrapTracer from @opentelemetry/api/experimental (or elide:telemetry) gives any tracer the
same withSpan and withActiveSpan, typed.
Context follows await, promises, timers, event emitters, and callbacks; it is backed by AsyncLocalStorage.
Request handlers run inside their server span.
Metrics
import { metrics } from "@opentelemetry/api";
const pending: string[] = [];
const meter = metrics.getMeter("checkout");
const orders = meter.createCounter("orders", { unit: "{order}" });
const latency = meter.createHistogram("charge.duration", {
unit: "s",
advice: { explicitBucketBoundaries: [0.01, 0.05, 0.1, 0.5, 1] },
});
const queue = meter.createObservableGauge("queue.depth");
queue.addCallback((result) => result.observe(pending.length));
orders.add(1, { region: "eu" });
latency.record(0.042);All synchronous and observable instruments are supported. Observable callbacks run on the program’s event loop at each collection; a callback may return a promise. Measurements are validated as the SDK does (counters and histograms reject negative values).
Logs
import { logs, SeverityNumber } from "@opentelemetry/api-logs";
logs.getLogger("checkout").emit({
severityNumber: SeverityNumber.WARN,
severityText: "WARN",
body: { event: "retry", attempt: 2 },
});Non-string bodies export as structured values. console.* output is exported too while logs.console is on; see
Logs.
Propagation
propagation.inject and propagation.extract use the propagators from traces.propagators or OTEL_PROPAGATORS
(default tracecontext,baggage). Baggage set with propagation.setBaggage stays in your code’s context; Elide’s HTTP
clients send it only when outbound baggage is on. See Correlation and baggage.
Using an OpenTelemetry SDK
You can still bring the JavaScript SDK (@opentelemetry/sdk-node, sdk-trace-*, sdk-metrics, sdk-logs) and its
exporters. When it registers a provider, it takes that signal over:
| Event | Result |
|---|---|
| SDK registers a tracer, meter, or context manager | Elide steps aside for that signal and prints a warning. Tracers, meters, and observable callbacks already handed out forward to the SDK. |
@opentelemetry/sdk-logs is loaded | Elide frees the logs global; its loggers keep recording until the SDK registers a logger provider, then forward to it. |
SDK calls disable() on a global | The signal returns to Elide. |
| SDK is loaded but registers nothing | Elide stays in place. |
The warning names the signal:
[warn] dev.elide.lang.javascript.telemetry.JsTelemetry An OpenTelemetry SDK
registered its own tracer provider; Elide's JavaScript tracer provider steps
asidestatus().steppedAside then lists trace.
Call release() from elide:telemetry before the SDK starts to hand it every signal without warnings. Elide’s own
server, client, and error telemetry keeps recording through Elide either way, and request handlers run under
whichever context manager is registered.
elide:telemetry
Controls the API does not cover. Types ship in @elide-dev/types; every function is safe to call when telemetry is
not configured.
import { trace } from "@opentelemetry/api";
import { flush, status } from "elide:telemetry";
{
using span = trace.getTracer("jobs").startSpan("nightly");
}
const ok = await flush();
console.log(ok, status().spans);| Export | Type | Description |
|---|---|---|
active | boolean | Whether telemetry is configured for this process. |
flush(timeoutMillis?) | (timeoutMillis?: number) = | Exports everything recorded so far, waiting up to timeoutMillis (default 5000). Resolves with whether every destination accepted it; never rejects. |
status() | () => TelemetryStatus | Approximate, process-wide pipeline counters; see below. |
release() | () => boolean | Stops serving as the global OpenTelemetry implementation for good, so an SDK registered afterwards owns every signal. Elide’s runtime instrumentation keeps recording. Returns false if already released. |
enter(ctx) | (context: Context) = | Makes ctx active for the rest of the current synchronous scope and the async work it starts. exit() or using restores the previous context. Warns and does nothing when an SDK registered its own context manager. |
wrapTracer(tracer) | (tracer: Tracer) = | The SugaredTracer of @opentelemetry/: withActiveSpan and withSpan end the span with the callback and record a throw or rejection. options.onException replaces that recording. |
currentSpanContext() | () = | The active span’s traceId, spanId, traceFlags, and traceparent, or undefined outside a valid span. |
api | typeof import("@opentelemetry/ | The API surface Elide serves. |
logs | typeof import("@opentelemetry/ | The logs API surface Elide serves. |
interface TelemetryStatus {
readonly active: boolean;
readonly released: boolean;
/** Signals an SDK took over: "trace", "metrics", "context", "propagation", "logs". */
readonly steppedAside: readonly string[];
readonly spans: {
readonly started: number;
readonly unsampled: number;
readonly ended: number;
readonly live: number;
readonly exported: number;
readonly dropped: number; // a destination queue was full
readonly tableFull: number; // the open-span table was full
readonly reclaimed: number;
readonly handedOff: number; // sent at exit by the background exporter
readonly exitDropped: number; // produced at exit and dropped
};
readonly logs: {
readonly emitted: number;
readonly filtered: number; // below logs.level
readonly exported: number;
readonly dropped: number;
readonly handedOff: number;
readonly exitDropped: number;
};
readonly metrics: { readonly handedOff: number; readonly exitDropped: number };
readonly exportFailures: number;
readonly exportsAbandoned: number; // in-process exports still running when exit ended them
}Pass a context to code that runs outside the current one, such as a queue consumer:
import { context, trace } from "@opentelemetry/api";
import { currentSpanContext, enter } from "elide:telemetry";
const saved = context.active();
setImmediate(() => {
using _scope = enter(saved);
console.log(currentSpanContext()?.traceparent);
});diagnostics_channel events
Instrumentation libraries that subscribe to Node’s diagnostics channels see the same events as on Node:
| Channel | Published by |
|---|---|
http. | node:http server, before the request handler |
http. | node:http server |
http. | node:http server |
http., http. | node:http client |
http. | node:http client |
http. | node:http client |
undici:request:create | fetch, before dispatch (request.origin, method, path, headers) |