Runtime: Beignet requires Node.js 22.12 or newer. Bun is optional.
Beignet is experimental alpha software. The 0.0.x package line is for early
evaluation, and APIs may change between releases while the framework settles.
OpenTelemetry tracing and metrics for Beignet requests, use cases, listeners, jobs, schedules, tasks, and provider instrumentation.
The package is an adapter, not an SDK bootstrap. Your app owns the global OpenTelemetry SDK, exporter, sampler, resource attributes, and shutdown or serverless flush behavior.
bun add @beignet/provider-tracing-opentelemetry @opentelemetry/api
Install the SDK or host integration that exports your telemetry separately.
Install Vercel's OpenTelemetry bootstrap:
bun add @vercel/otel
Create one idempotent app-owned registration function:
// lib/telemetry.ts
import { registerOTel } from "@vercel/otel";
const state = globalThis as typeof globalThis & {
__appTelemetryRegistered?: boolean;
};
export function registerTelemetry() {
if (state.__appTelemetryRegistered) return;
registerOTel({ serviceName: "my-app" });
state.__appTelemetryRegistered = true;
}
Call it from the root Next.js instrumentation hook:
// instrumentation.ts
import { registerTelemetry } from "@/lib/telemetry";
export function register() {
registerTelemetry();
}
Next.js only invokes that hook for the Next.js runtime. Standalone job workers, task and schedule commands, outbox drains, and scripts must call the same registration function before initializing their Beignet server. Otherwise the provider safely uses OpenTelemetry's no-op globals and exports nothing.
Then install the Beignet provider after devtools and before providers whose operations should feed OpenTelemetry:
import { createDevtoolsProvider } from "@beignet/devtools";
import { createOpenTelemetryTracingProvider } from "@beignet/provider-tracing-opentelemetry";
import { registerTelemetry } from "@/lib/telemetry";
registerTelemetry();
export const providers = [
createDevtoolsProvider(),
createOpenTelemetryTracingProvider(),
// Database, mail, jobs, and other instrumented providers follow.
] as const;
Provider ordering matters for instrumentation composition. The OpenTelemetry provider forwards events to an earlier devtools or instrumentation sink, while later providers resolve the composed sink and contribute their operation metrics and span events.
If Sentry is also installed and the app uses another OpenTelemetry SDK, disable Sentry's SDK setup so only one tracing pipeline owns process instrumentation:
createSentryErrorReportingProvider({
init: { skipOpenTelemetrySetup: true },
});
Beignet creates active spans with stable names and low-cardinality attributes:
| Boundary | Span name |
|---|---|
| HTTP request | beignet.request <contract> |
| Use case | beignet.use_case <name> |
| Listener | beignet.listener <name> |
| Job handler | beignet.job <name> |
| Outbox delivery | beignet.outbox deliver <name> |
| Schedule handler | beignet.schedule <name> |
| Task handler | beignet.task <name> |
Incoming traceparent and tracestate headers continue the request trace.
Nested in-process work uses the active OpenTelemetry context automatically.
Beignet's versioned TraceCarrier continues context through outbox rows, Redis
event messages, BullMQ jobs, and Inngest functions. Old messages without a
carrier start a new trace. Malformed or unknown carrier metadata is ignored so
telemetry cannot replace message delivery behavior. OpenTelemetry baggage is
not propagated.
The adapter records these instruments through the registered global meter, or
through an injected meter:
beignet.request.durationbeignet.use_case.durationbeignet.listener.durationbeignet.job.durationbeignet.outbox.delivery.durationbeignet.schedule.durationbeignet.task.durationbeignet.operation.errorsbeignet.provider.operation.countDuration units are milliseconds. Metric attributes contain operation names, types, outcomes, attempts, and provider names where available; payloads, request bodies, tenant IDs, user IDs, and error messages are excluded.
TraceOperation.attributes are span-only. Custom tracing integrations must put
only bounded operation dimensions in TraceOperation.metricAttributes; never
copy request, actor, tenant, or payload values into metric labels.
Not every tracing bootstrap installs a metric exporter. In that case the OpenTelemetry API's no-op meter receives these calls until the app registers a real meter provider.
Set meter: false to disable Beignet metrics while retaining spans, or inject
a configured meter directly:
createOpenTelemetryTracingProvider({
meter: myMeter,
});
Failed spans set OpenTelemetry error status and the low-cardinality
error.type attribute. They do not record exception messages or stacks by
default. Apps that have reviewed their exporter redaction policy can opt in:
createOpenTelemetryTracingProvider({
recordExceptions: true,
});
The provider does not export OpenTelemetry logs. Continue using Beignet's logger and error-reporting ports for structured logs and captured exceptions.
Use createOpenTelemetryTracing(...) in tests or custom composition:
import { createOpenTelemetryTracing } from "@beignet/provider-tracing-opentelemetry";
const { tracing, instrumentation } = createOpenTelemetryTracing({
tracer,
meter,
instrumentation: existingSink,
});
This package starts no workers, network clients, timers, or background loops. Its runtime is safe to install in serverless processes; export and flush semantics remain the responsibility of the app-owned OpenTelemetry SDK. Failures in tracer span mutation or metric recording are isolated from the wrapped application operation.