API
The package publishes one entry point per job rather than a single barrel, so an import says what it reaches for and nothing pulls in the SDK by accident.
| Import | What it is for |
|---|---|
@vercube/telemetry | The Telemetry token and TelemetryPlugin |
@vercube/telemetry/api | The OpenTelemetry API |
@vercube/telemetry/attributes | Span and metric attribute keys |
@vercube/telemetry/instrument | Instrumenting a library of your own |
@vercube/telemetry/sdk | Tracer and meter provider wiring |
@vercube/telemetry/otlp | OTLP serializers and exporters |
@vercube/telemetry/testing | In-memory providers for tests |
This is also the only package in the framework that depends on @opentelemetry/*. @vercube/storage, @vercube/cache, @vercube/queue, @vercube/auth, @vercube/ws and @vercube/devtools all instrument themselves through the subpaths above, so the OpenTelemetry version in use is decided in one place.
Telemetry
The dependency-injection token, bound only when telemetry is enabled. Inject it with @InjectOptional from code that must also run without it.
import { Inject } from '@vercube/di';
import { Telemetry } from '@vercube/telemetry';
class ReportService {
@Inject(Telemetry)
private gTelemetry!: Telemetry;
}
span
span<T>(name: string, fn: (span: Span) => T, options?: SpanOptions): T
Runs fn inside a new span that ends when the work settles. The return value is passed through unchanged, so wrapping synchronous code does not make it asynchronous.
const total = this.gTelemetry.span('report.total', (span) => {
span.setAttribute('report.rows', rows.length);
return rows.reduce((sum, row) => sum + row.amount, 0);
});
activeSpan, traceId, spanId
activeSpan(): Span | undefined
get traceId(): string | undefined
get spanId(): string | undefined
The span active on the current async execution path, and its ids in lowercase hex. Useful for putting a trace id into an error response so a user can quote it in a bug report.
inject, extract
inject(carrier: Headers | Record<string, string>): void
extract(headers: Headers | Record<string, string>): Context
Write W3C trace context headers for the active span into a carrier, or read them back into a context usable as a span parent. extract is what lets a background worker continue the trace of the request that queued its job.
onFlush, flush
onFlush(flush: () => Promise<void>): void
flush(): Promise<void>
flush() pushes out everything buffered by batching exporters and drains. Call it before a process that may be frozen or killed goes away. onFlush registers additional work to run when it does.
tracer, meter
The underlying OpenTelemetry Tracer and Meter, for anything the wrapper does not cover, such as creating your own counters and histograms.
const refunds = this.gTelemetry.meter.createCounter('billing.refunds');
refunds.add(1, { 'billing.reason': reason });
@vercube/telemetry/api
The OpenTelemetry API, re-exported in full. Use it to annotate spans without adding a dependency of your own.
import { SpanKind, trace } from '@vercube/telemetry/api';
trace.getActiveSpan()?.setAttribute('invoice.id', id);
Anything @opentelemetry/api exports is available here, including the types: Span, Context, Tracer, Meter, Attributes, Counter, Histogram.
@vercube/telemetry/attributes
The attribute keys the framework writes. The HTTP names follow the stable OpenTelemetry semantic conventions, the vercube.* names are this framework's own.
import { HTTP_ROUTE, VERCUBE_CONTROLLER } from '@vercube/telemetry/attributes';
const route = span.attributes[HTTP_ROUTE];
Reading a span rather than producing one is what this is for, so it does not pull in the plugin.
@vercube/telemetry/instrument
The toolkit for instrumenting a library. See Instrumenting your own code.
@vercube/telemetry/sdk
Everything that needs the OpenTelemetry SDK lives behind this subpath, so the application entry stays free of it.
startNodeTelemetry
startNodeTelemetry(options?: NodeTelemetryOptions): Promise<NodeTelemetry>
Wires a Node tracer provider and registers it globally. Accepts serviceName, serviceVersion, environment, resourceAttributes, sampler, endpoint, headers, exporter and spanProcessors. Returns a handle with shutdown().
The context manager and the propagator are deliberately not replaced: TelemetryPlugin already installed ones backed by Vercube's request context, and letting the SDK swap them in would open a second AsyncLocalStorage frame per request.
addSpanProcessor
addSpanProcessor(processor: SpanProcessor): () => void
Adds a span processor whether or not a provider exists yet. This is how a package can observe spans without owning the SDK setup, and it works the same whether the application called startNodeTelemetry first, later, or never.
addMetricReader, ensureMeterProvider
addMetricReader(reader: IMetricReader): () => void
ensureMeterProvider(options?: NodeTelemetryOptions): MeterProvider
Metric readers have to be registered before the provider is built, and instruments have to be created after it exists. The OpenTelemetry metrics API has no proxy meter, so an instrument created before a provider is registered stays a no-op for the life of the process.
SDK re-exports
The SDK classes and types a consumer needs to build a pipeline of its own: NodeTracerProvider, BasicTracerProvider, MeterProvider, BatchSpanProcessor, SimpleSpanProcessor, InMemorySpanExporter, InMemoryMetricExporter, PeriodicExportingMetricReader, AggregationTemporality, and the types ReadableSpan, SpanProcessor, SpanExporter, IMetricReader, ResourceMetrics, Sampler.
The SDK's mutable span class is re-exported as the type alias SdkSpan, because it is a different thing from the API's Span interface and the two are not interchangeable. It is export type, so it is available to import type and has no runtime binding. SpanProcessor.onStart receives an SdkSpan, onEnd receives a ReadableSpan.
import { JsonTraceSerializer } from '@vercube/telemetry/otlp';
import type { ReadableSpan, SdkSpan, SpanProcessor } from '@vercube/telemetry/sdk';
class MyProcessor implements SpanProcessor {
public onStart(_span: SdkSpan): void {}
public onEnd(span: ReadableSpan): void {
void JsonTraceSerializer.serializeRequest([span]);
}
public async forceFlush(): Promise<void> {}
public async shutdown(): Promise<void> {}
}
@vercube/telemetry/otlp
JsonTraceSerializer and JsonMetricsSerializer turn collected signals into the OTLP/JSON wire format without sending them anywhere, which is what a consumer shipping its own transport needs. @vercube/devtools uses them to push spans over its own bus.
createOtlpTraceExporter(options: OtlpExporterOptions): Promise<SpanExporter>
createOtlpMetricExporter(options: OtlpExporterOptions): Promise<PushMetricExporter>
Both take { endpoint, headers? } and append the OTLP signal path for you. The exporter packages are optional peer dependencies loaded on demand, so a missing one produces a message telling you what to install rather than a module-resolution error at import time.
@vercube/telemetry/testing
createTestTelemetry
createTestTelemetry(): TestTelemetry
Registers in-memory tracer and meter providers plus a context manager, and hands back everything a test needs to assert on what was recorded.
import { createTestTelemetry } from '@vercube/telemetry/testing';
const telemetry = createTestTelemetry();
afterEach(() => telemetry.reset());
afterAll(() => telemetry.shutdown());
it('traces the request', async () => {
await app.fetch(new Request('http://localhost/users/1'));
expect(telemetry.span('GET /users/:id')).toBeDefined();
});
spans() returns finished spans, span(name) finds one, collect() collects the registered instruments, settle() waits for spans whose end was deferred (body capture defers it), and reset() clears everything between tests.