Zum Inhalt springen

Diese Seite ist noch nicht übersetzt und wird auf Englisch angezeigt.

OpenTelemetry in JavaScript and TypeScript

Use @opentelemetry/api in JavaScript and TypeScript on Elide without an SDK: built-in and installed copies, spans with using, metrics, logs, SDK takeover and handback, the elide:telemetry module, and diagnostics_channel events.

Auf dieser Seite

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.ts

The API package

Your projectimport "@opentelemetry/api" resolves to
No @opentelemetry/api installedElide’s built-in copy (API 1.9)
@opentelemetry/api 1.x installedYour 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/api

Elide 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 here

Elide’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:

EventResult
SDK registers a tracer, meter, or context managerElide 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 loadedElide frees the logs global; its loggers keep recording until the SDK registers a logger provider, then forward to it.
SDK calls disable() on a globalThe signal returns to Elide.
SDK is loaded but registers nothingElide 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
aside

status().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);
ExportTypeDescription
activebooleanWhether telemetry is configured for this process.
flush(timeoutMillis?)(timeoutMillis?: number) => Promise<boolean>Exports everything recorded so far, waiting up to timeoutMillis (default 5000). Resolves with whether every destination accepted it; never rejects.
status()() => TelemetryStatusApproximate, process-wide pipeline counters; see below.
release()() => booleanStops 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) => ContextScopeMakes 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) => SugaredTracerThe SugaredTracer of @opentelemetry/api/experimental: withActiveSpan and withSpan end the span with the callback and record a throw or rejection. options.onException replaces that recording.
currentSpanContext()() => ActiveSpanContext | undefinedThe active span’s traceId, spanId, traceFlags, and traceparent, or undefined outside a valid span.
apitypeof import("@opentelemetry/api")The API surface Elide serves.
logstypeof import("@opentelemetry/api-logs")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:

ChannelPublished by
http.server.request.startnode:http server, before the request handler
http.server.response.creatednode:http server
http.server.response.finishnode:http server
http.client.request.created, http.client.request.startnode:http client
http.client.request.errornode:http client
http.client.response.finishnode:http client
undici:request:createfetch, before dispatch (request.origin, method, path, headers)

See node:diagnostics_channel.

Samples

War diese Seite hilfreich?