npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@foam-ai/vercel-otel

v0.4.5

Published

OpenTelemetry OTLP exporter configuration for Foam, designed to spread into @vercel/otel's registerOTel() in a Next.js Vercel app. Optionally mirrors server-side console.* output as OTel log records.

Downloads

309

Readme

@foam-ai/vercel-otel

OpenTelemetry exporters for Foam, designed for use with Vercel's instrumentation hooks. Provides OTLP HTTP exporters for traces and logs by default; OTLP metrics and a console.* -> OTel-logs bridge are available as opt-ins.

Installation

npm install @foam-ai/vercel-otel

Usage

import { registerOTel } from '@vercel/otel';
import { foamOtelConfig } from '@foam-ai/vercel-otel/server';

// Note: pass the raw API key; the Bearer prefix is added automatically.
registerOTel({
  serviceName: 'my-nextjs-app',
  ...foamOtelConfig({
    isProduction: process.env.VERCEL_ENV === 'production' && !!process.env.FOAM_API_KEY,
    apiKey: process.env.FOAM_API_KEY ?? '',
    // Opt-in: mirror server-side console.* into Foam logs.
    captureConsoleLogs: true,
    // Opt-in: register the OTLP metric exporter (host must record metrics).
    metrics: true,
  }),
});

By default this configures OTLP exporters for traces and logs only. metrics and captureConsoleLogs are opt-in flags — both register additional plumbing (a PeriodicExportingMetricReader and a console.* monkey-patch respectively) that should only run when the host actually wants them.

foamOtelConfig fails closed: if isProduction is false, apiKey is missing / whitespace-only / non-string, or any internal error is thrown, it returns {} so spreading it into registerOTel(...) is a no-op rather than a crash. Telemetry misconfiguration will never break app startup, and a misconfigured FOAM_API_KEY env var ("", " ", accidentally unset) cannot result in Authorization: Bearer requests being sent to the Foam collector.

Options

| Option | Type | Default | Behavior | | -------------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------- | | isProduction | boolean | — | Hard gate. When false, foamOtelConfig returns {} and registers nothing. | | apiKey | string | — | Foam API key. Sent as Authorization: Bearer <apiKey> on every OTLP export request. Leading and trailing whitespace is trimmed; whitespace-only or non-string values are treated as missing and short-circuit to the disabled (empty-config) path. | | serviceName | string? | — | Optional, accepted for backwards compatibility. Not used by this package. Pass serviceName to registerOTel(...) instead — @vercel/otel attaches it to the resource shared by traces, logs, and metrics, and OTEL_SERVICE_NAME overrides both. | | traces | boolean | true | Register the OTLP trace exporter / BatchSpanProcessor. | | logs | boolean | true | Register the OTLP log exporter / SimpleLogRecordProcessor. Simple (export-on-emit) is used because @vercel/otel does not flush the LoggerProvider per request on serverless; see Logs on serverless. | | metrics | boolean | false | Opt in to the OTLP metric exporter / PeriodicExportingMetricReader. Off by default because the host must record metrics for anything to ship; see Metrics. | | exportTimeoutMs | number? | 15000 | Maximum time the waitUntil wrapper keeps a single OTLP log or metric export armed before resolving its promise to a failed ExportResult itself. Bounds the worst-case stuck-export scenario (network hang, exporter bug) so it cannot extend a Vercel function up to the platform's wall-clock timeout. Default sits 5s above the OTLP HTTP exporter's own 10s timeout, so the inner exporter's timeout fires first under normal conditions. 0 or negative values fall back to the default. See Logs on serverless. | | captureConsoleLogs | boolean | false | Opt in to patching Node-side console.* to emit OTel log records in addition to writing to stdout/stderr. Off by default because monkey-patching is a global side effect; see Console capture. |

What "register" means

foamOtelConfig returns the OTLP exporter / processor / reader instances that @vercel/otel plugs into its OTel SDK. Registering a signal does not produce telemetry on its own — the host app still has to:

  • emit traces (typically automatically via @vercel/otel instrumentation),
  • record metrics through the OTel meter API (metrics.getMeter('...').createCounter(...) etc.),
  • emit log records through the OTel logs API (or enable captureConsoleLogs and use plain console.*).

If nothing records metrics, an enabled metric reader will simply have nothing to export.

Logs on serverless

Logs are exported through SimpleLogRecordProcessor, not BatchLogRecordProcessor, and the underlying OTLP log exporter is wrapped so each export's completion promise is registered with Vercel's per-invocation waitUntil hook. The reason for both pieces is specific to how @vercel/otel wires its providers:

  • For traces, @vercel/otel wraps every span processor in a CompositeSpanProcessor whose onStart calls vrc.waitUntil(() => forceFlush()) against the Vercel request context. That is what guarantees BatchSpanProcessor flushes before the function freezes.
  • For logs, no such wrapping exists. The LoggerProvider receives the processors as-is, and the only flush hook is Sdk.shutdown() — which @vercel/otel does not invoke per request on Vercel serverless. With the default 5-second BatchLogRecordProcessor schedule, a typical short request returns long before any batch fires, and the records are lost when the function freezes.

SimpleLogRecordProcessor exports each record on emit, so the OTLP HTTP request is initiated synchronously inside the request lifecycle. From 0.4.4 the package additionally wraps the OTLP log exporter in an internal WaitUntilLogRecordExporter that probes globalThis[Symbol.for('@vercel/request-context')].get().waitUntil (the same primitive @vercel/otel uses for its span flush, and the same primitive that backs the public @vercel/functions waitUntil API) and registers the in-flight export promise with it when present. On Vercel that means a short request handler can return before the OTLP HTTP response lands and the runtime will still keep the function alive long enough for the export to complete; outside Vercel (long-lived Node servers, containers, tests) the wrapper falls through and behaviour is identical to the bare SimpleLogRecordProcessor + OTLPLogExporter pair. The metric exporter gets the same wrapper for symmetry.

From 0.4.5 the wrapper is also bounded:

  • Per-export timeout (exportTimeoutMs, default 15000ms). If the inner OTLP HTTP request never invokes its callback (network hang, slow collector, exporter bug), the wrapper resolves the waitUntil promise to a failed ExportResult itself after exportTimeoutMs so a single stuck export cannot extend the host's serverless invocation up to the platform's wall-clock function timeout. Default sits 5s above the OTLP HTTP exporter's own 10s timeoutMillis, so under normal conditions the inner exporter's timeout fires first and ours only kicks in if the inner one fails to.
  • shutdown() and forceFlush() await wrapper-tracked in-flight exports before forwarding to the inner exporter. Hosts that drain via LoggerProvider.shutdown() get back-pressure equivalent to "every export the wrapper observed has settled," independent of any internal _sendingPromises tracking the inner OTLP exporter happens to do.
  • Diagnostics flow through @opentelemetry/api's diag.warn. Timeouts, non-success ExportResults, swallowed waitUntil throws, and swallowed result-callback throws are all logged with a @foam-ai/vercel-otel: prefix so a host that has called diag.setLogger(...) (a common OTel pattern; @vercel/otel itself uses it) sees Foam delivery problems without needing any new hook from this package. The wrappers themselves still never throw into the host.

This trades batching for delivery, which is the right trade-off for the volumes typical of console.* mirroring on a Vercel app. On long-lived Node servers either processor would work; the package picks the combination that works in both environments.

Console capture

Pass captureConsoleLogs: true to replace the global console.log/info/warn/error/debug methods on the Node side with wrappers that:

  • call the original method first, so stdout/stderr output is unchanged,
  • emit a corresponding OTel log record with severityNumber, severityText, body formatted via node:util.format, and a console.method attribute,
  • never throw into the host app — every step is wrapped in try/catch.

This is what makes existing console.* output show up in Foam logs without code changes:

foamOtelConfig({
  isProduction,
  apiKey,
  captureConsoleLogs: true,
});

Off by default because monkey-patching console.* is a global side effect on the host runtime. Opt in only when you actually want server-side console.* output to land in Foam logs.

When enabled:

  • Patching only activates after the production gate (isProduction && trimmed apiKey) passes and the rest of the config is built without throwing, so a config-build error never leaves the console permanently patched against an OTel logs pipeline that was never installed (introduced in 0.4.4).
  • Patching is idempotent: calling foamOtelConfig multiple times only patches the console once.
  • The wrapper looks up the OTel logger lazily on each call, so it picks up whatever logger provider @vercel/otel has installed by the time the request runs.
  • The Edge / Worker build of this package is a no-op, so console capture only ever activates in the Node runtime.

captureConsoleLogs requires logs to also be enabled. With logs: false, the option is ignored.

Metrics

Off by default. Pass metrics: true to register a PeriodicExportingMetricReader (60-second internal interval) wired to the OTLP metric exporter. Two things to know before turning it on:

  1. Nothing exports unless the host records something. Add metric instruments via the OTel API:

    import { metrics } from '@opentelemetry/api';
    
    const meter = metrics.getMeter('my-app');
    const requestCount = meter.createCounter('http_request_count');
    requestCount.add(1, { route: '/api/health' });

    With no recorded metrics, an enabled metric reader adds an idle periodic timer and a no-op HTTP exporter — pure overhead. Leave metrics off until the host actually uses the meter API.

  2. Best-effort flushing on short-lived invocations. The 60-second periodic timer rarely fires before a Vercel serverless function returns. @vercel/otel only wires per-request waitUntil-based flushing into the span processor pipeline (via CompositeSpanProcessor); the MeterProvider is only flushed via Sdk.shutdown(), which Vercel does not invoke per request. From 0.4.4 the OTLP metric exporter is wrapped in the same waitUntil decorator as the log exporter, so any metric export that does happen to fire inside a request handler (e.g. via a manual meterProvider.forceFlush()) is kept alive until the OTLP HTTP request settles — but the periodic timer itself remains best-effort. Treat metrics as best-effort on serverless and authoritative only on long-lived Node servers (always-on Next.js, custom servers, containers).

Versioning And Compatibility

Package Versioning

  • Published as a normal semver package: @foam-ai/vercel-otel.
  • Pin against a minor range, for example:
{
  "@foam-ai/vercel-otel": "^0.3.0"
}
  • Semver applies strictly once 1.0.0 ships:
    • Patch: bug fixes only, no API changes.
    • Minor: additive options/exports only.
    • Major: breaking API, runtime, exporter, or config behavior changes.
  • The current 0.x train follows the npm convention that minor releases may include breaking changes. Default-behavior shifts (e.g. defaulting captureConsoleLogs to true in 0.3.0) will land in a minor bump and be called out in the release notes.

Required Runtime Support

  • Node.js 20+, compatible with Vercel's current Next.js server runtime.
  • ESM-compatible package.
  • Works with Next.js instrumentation via app/instrumentation.ts.
  • Works with @vercel/otel >=1.10 <2.

Peer Dependencies

@vercel/otel and next are declared as optional peer dependencies, so the host app owns the version of each:

{
  "peerDependencies": {
    "@vercel/otel": ">=1.10.0 <2",
    "next": ">=13 <18"
  },
  "peerDependenciesMeta": {
    "@vercel/otel": { "optional": true },
    "next": { "optional": true }
  }
}

Both are optional. The package contains a single function (foamOtelConfig) that returns config to spread into @vercel/otel's registerOTel; if you do not use @vercel/otel, this package has nothing to do.

Export Stability

The following exports are stable until the next major version:

import { foamOtelConfig } from "@foam-ai/vercel-otel/server";

The /server entrypoint contains the server (Node.js) implementation. The bare entrypoint (@foam-ai/vercel-otel) re-exports the same API, but resolves to a no-op build under the edge-light and worker package conditions so it is safe to import from a shared Next.js instrumentation.ts that runs in both Node and Edge runtimes.

Environment Contract

Configuration is passed in from the host app rather than read from process.env inside the package, so the host owns env-var naming and gating:

foamOtelConfig({
  isProduction: process.env.VERCEL_ENV === "production" && !!process.env.FOAM_API_KEY,
  apiKey: process.env.FOAM_API_KEY ?? "",
});

The package never reads process.env directly; explicit options are the only configuration surface.

Release Artifacts

Each release should include:

  • Changelog entry.
  • TypeScript declarations.
  • ESM build.
  • Published /server export in package.json.
  • Smoke-tested example for Next.js + @vercel/otel.
  • No browser code included in the /server bundle.

Backward Compatibility

Patch/minor releases must not:

  • Change the shape of foamOtelConfig (additive optional fields are allowed).
  • Make foamOtelConfig throw.
  • Add browser-side behavior.
  • Add global side effects to the default code path. Optional features that monkey-patch host globals (e.g. captureConsoleLogs) must remain opt-in and default to false.
  • Change default no-op behavior when disabled or missing API key.
  • Require the host app to call a new init() function.

Initial Version

The package is currently 0.3.0. Once the public API has been validated in production at scale, a 1.0.0 release will be cut with the same surface and a strict semver commitment.

Upgrade Notes

  • 0.4.4 -> 0.4.5 (reliability + observability; recommended for all 0.4.4 users, no breaking changes):
    • Per-export waitUntil timeout (exportTimeoutMs, default 15000ms). 0.4.4 registered the in-flight OTLP export promise with waitUntil but never bounded it. If the inner OTLP HTTP request never invoked its callback (network hang, slow collector, exporter bug), waitUntil(donePromise) would keep the Vercel function alive until the platform's own wall-clock function timeout cut it off. 0.4.5 resolves the promise to a failed ExportResult itself after exportTimeoutMs, with a diag.warn describing the timeout. Default sits 5s above the OTLP HTTP exporter's own 10s timeoutMillis, so the inner exporter's timeout fires first under normal conditions and ours only kicks in if it doesn't. Configurable per call: foamOtelConfig({ ..., exportTimeoutMs: 30000 }).
    • shutdown() and forceFlush() now await wrapper-tracked in-flight exports before forwarding to the inner exporter. 0.4.4 forwarded both calls immediately; an in-flight export tracked by the wrapper could be torn down mid-flight if LoggerProvider.shutdown() ran while it was still in the air. The wrapper now keeps a Set<Promise<ExportResult>> of every in-flight export it kicked off and Promise.allSettleds it before forwarding. This also stops forceFlush() from depending on OTLPExporterBase._sendingPromises (an undocumented internal of the inner exporter) for correctness.
    • AggregationTemporality fallback uses the named enum. 0.4.4's WaitUntilMetricExporter.selectAggregationTemporality returned a hardcoded 1 with a comment about avoiding SDK identity coupling. Identity coupling to @opentelemetry/sdk-metrics is already accepted (we instantiate MetricReader and PeriodicExportingMetricReader against it), so importing AggregationTemporality.CUMULATIVE from the same module costs nothing more and is much safer than a magic number that's quietly tied to an exact enum pin.
    • Diagnostics via @opentelemetry/api's diag.warn. Every non-success ExportResult, every waitUntil-throw, every swallowed result-callback throw, and every wrapper timeout is now logged with a @foam-ai/vercel-otel: prefix. Hosts that have called diag.setLogger(...) (a common OTel pattern; @vercel/otel itself uses the same channel) see Foam delivery problems without needing a new callback hook from this package. The wrappers themselves still never throw into the host. No new dependency: @opentelemetry/api was already a direct dependency.
    • New optional exportTimeoutMs field on FoamOtelExporterOptions. Additive, fully backwards-compatible. 0 and negative values fall back to the 15000ms default.
    • Hosts on 0.4.4 get the timeout bound, the in-flight drain on shutdown/flush, the named-enum fallback, and diag.warn observability on redeploy with no call-site changes.
  • 0.4.3 -> 0.4.4 (hardening; recommended for all 0.4.3 users, no breaking changes):
    • Per-invocation waitUntil for log and metric exports. The OTLP log and metric exporters are now wrapped in an internal WaitUntilLogRecordExporter / WaitUntilMetricExporter that probes globalThis[Symbol.for('@vercel/request-context')].get().waitUntil and registers each in-flight export's completion promise with it when the runtime exposes one. This is the same primitive @vercel/otel uses to flush spans and the same primitive that backs the public @vercel/functions waitUntil. On Vercel that means a short request handler can return before the OTLP HTTP response lands and the runtime will still keep the function alive long enough for the export to complete. Outside Vercel (long-lived Node servers, containers, tests) the wrappers fall through and behaviour is identical to 0.4.3. No new dependency is added: the wrappers read the same global Symbol Vercel already populates, gated behind a try/catch. See Logs on serverless.
    • apiKey gate now rejects whitespace and non-strings. 0.4.3 and earlier used a bare !options.apiKey truthy check, which lets " " through to form an Authorization: Bearer header against the Foam collector. 0.4.4 calls apiKey.trim() first, treats the result as missing if empty, and treats any non-string value as missing. Whitespace API keys now short-circuit to the disabled (empty-config) path the same way apiKey: "" always has. No behaviour change for hosts passing a real key.
    • captureConsoleLogs only patches after the config is built. 0.4.3 patched console.* before constructing the per-signal exporters. If a later step in foamOtelConfig had thrown, the host would have been left running with monkey-patched console.* methods routed at an OTel logs pipeline that was never installed; console.* would have continued to emit log records but they would have gone nowhere, with no way to revert without restarting the process. 0.4.4 builds the full config first and only patches the console immediately before returning, so a build-time throw fails closed cleanly.
    • serviceName is now optional. The field has been a no-op since 0.2.x (this package does not read it; @vercel/otel attaches service.name to the resource shared by all signals from its own serviceName argument, and OTEL_SERVICE_NAME overrides both). The type signature is now serviceName?: string and the README example drops the redundant pass-through. Hosts that still pass it continue to work — the field is accepted for backwards compatibility — but it does nothing in this package. To set the service name, pass serviceName to registerOTel(...).
    • foamOtelConfig's return type is now a typed FoamOtelV1Config instead of Record<string, unknown>, mirroring the singular log/metric keys this package writes. A future regression that returns the wrong key shape (e.g. the plural logRecordProcessors / metricReaders shipped on the unreleased @vercel/[email protected] branch — the same shape that broke 0.4.2) now fails tsc --noEmit instead of being caught only by the runtime regression test. The runtime test is still in place as defence in depth.
    • @opentelemetry/core is now a direct dependency (exact-pinned to 1.30.1, matching the rest of the OTel JS 1.x train this package bundles). It was already pulled in transitively through every SDK package; declaring it directly lets the new wrappers import ExportResult / ExportResultCode from a stable location without relying on transitive resolution.
    • No code changes required at the call site. Hosts on 0.4.3 get the waitUntil reliability improvement, the tighter input validation, and the safer console-patch ordering on redeploy.
  • 0.4.2 -> 0.4.3 (bug fix; required for all 0.4.x users — 0.4.0, 0.4.1, and 0.4.2 all fail to export logs and (when enabled) metrics):
    • Returns the correct @vercel/[email protected] config keys for logs and metrics. 0.4.2 switched to logRecordProcessors / metricReaders (plural arrays) under the mistaken belief that those were the published @vercel/[email protected] keys. They are not — those names exist only on the unreleased @vercel/[email protected] branch. The published 1.x line that this package's peer range (>=1.10 <2) targets reads the singular keys logRecordProcessor and metricReader. Verified directly against the installed @vercel/[email protected] tarball: package/dist/types/types.d.ts declares logRecordProcessor?: LogRecordProcessor and metricReader?: MetricReader, and the bundled property accesses in package/dist/node/index.js only ever read the singular names. 0.4.3 returns those singular keys, so log records and metric readings are now actually wired into @vercel/otel's LoggerProvider / MeterProvider.
    • Keeps the SimpleLogRecordProcessor change from 0.4.2. Even with the correct keys, BatchLogRecordProcessor is unsuitable on Vercel serverless: @vercel/otel only wires per-request waitUntil-based flushing into the span processor pipeline, not the logger provider, so batched log records are stranded in memory when the function freezes. SimpleLogRecordProcessor exports each record synchronously on emit so the OTLP HTTP request is kicked off inside the request lifecycle. See Logs on serverless for the full rationale.
    • Net effect for hosts upgrading 0.4.x -> 0.4.3: server-side log records (including captureConsoleLogs: true mirrored output) and meter-API metrics begin landing in Foam after redeploy. No code changes required; the foamOtelConfig({ ... }) call site is unchanged.
  • 0.4.1 -> 0.4.2 (superseded by 0.4.3 — do not upgrade to 0.4.2):
    • Switched the log processor from BatchLogRecordProcessor to SimpleLogRecordProcessor for serverless reliability. This part is correct and is preserved in 0.4.3.
    • Also changed the returned config-key shape from singular (logRecordProcessor / metricReader) to plural (logRecordProcessors / metricReaders). This part was wrong: the plural names belong to the unreleased @vercel/[email protected] branch and are silently ignored by every published @vercel/[email protected]. Logs and metrics did not export on 0.4.2. Skip 0.4.2 and upgrade directly from 0.4.1 to 0.4.3.
  • 0.4.0 -> 0.4.1:
    • Stability hardening only. The bundled OTel SDK / OTLP exporter dependencies are now pinned to exact patches (@opentelemetry/[email protected], @opentelemetry/[email protected], @opentelemetry/[email protected], @opentelemetry/[email protected], and the matching OTLP HTTP exporters at 0.57.2) instead of ^ ranges. No public API changes; no runtime behavior changes. Hosts that deduplicated against floating ^0.57 resolutions before may see lockfile churn on the next install.
  • 0.3.x -> 0.4.x:
    • metrics default flipped from true to false. Hosts that record through the OTel meter API need to pass metrics: true explicitly to keep exporting. Hosts that did not record metrics get a small reduction in idle overhead; behavior is otherwise unchanged.
    • captureConsoleLogs default flipped from true to false. Server-side console.* is no longer mirrored into Foam logs unless captureConsoleLogs: true is set. Pass it explicitly to restore 0.3.x behavior.
  • 0.2.x -> 0.3.x:
    • New captureConsoleLogs?: boolean option (default true in 0.3.x, default false from 0.4.x onward).
    • metrics default was true in 0.3.x (default false from 0.4.x onward).

Vercel Compatibility Requirements

This package treats @vercel/otel and Next.js as integration surfaces with tested version ranges, and fails safely outside those ranges.

Peer Dependency Range

@vercel/otel and next are declared as optional peer dependencies (see Peer Dependencies) so the host application controls the exact versions installed. Foam's published compatibility range is what determines whether a given combination is supported.

No Private APIs

@foam-ai/vercel-otel/server only returns documented @vercel/otel config fields accepted by registerOTel. It does not import internal Vercel files, monkey-patch Next internals, inspect .next, or depend on private Vercel runtime behavior.

Tested Configurations

A Next.js instrumentation smoke test covers, at minimum:

  • The current declared @vercel/otel and Next.js ranges.
  • The latest @vercel/[email protected] and the latest supported Next major/minor.
  • Node 20 and 22.

The smoke test verifies:

  • registerOTel({ ...foamOtelConfig(...) }) boots.
  • No browser bundle imports the server package.
  • Disabled mode no-ops (foamOtelConfig returns {}).
  • A server route emits a trace/span without crashing.
  • registerOTel({ ...foamOtelConfig({ captureConsoleLogs: true }) }) boots and console.log calls do not throw.
  • metrics.getMeter('...').createCounter('...').add(1) runs without crashing.

Compatibility Contract

| Foam package | @vercel/otel | Next.js | Node | | ------------ | ------------ | -------- | ------ | | 0.4.x | >=1.10 <2 | >=13 <18 | >=20 | | 0.3.x | >=1.10 <2 | >=13 <18 | >=20 | | 0.2.x | >=1.10 <2 | >=13 <18 | >=20 |

Note: 0.4.0, 0.4.1, and 0.4.2 all fail to export logs (and metrics, when enabled) — the first two used BatchLogRecordProcessor which doesn't flush per-request on Vercel; 0.4.2 switched to a config-key shape that @vercel/[email protected] doesn't read. 0.4.3 is correct but does not bound waitUntil per export, await in-flight exports on shutdown/flush, or surface delivery failures via diag.warn — those land in 0.4.4 (waitUntil wiring) and 0.4.5 (timeout bound, drain semantics, diagnostics). Use 0.4.5 or later. See Upgrade Notes.

The OTel SDKs (sdk-trace-base, sdk-metrics, sdk-logs, core) and OTLP exporters are bundled as direct dependencies of this package, so consumers do not install or align them. They are not part of the peer surface.

Tested matrix (0.4.5)

Manually verified against:

  • @vercel/[email protected]1.14 on Vercel Node serverless and Vercel Fluid runtimes (the published 1.x line; this package's peer range is >=1.10 <2).
  • Next.js 15.x and 16.x instrumentation hooks.
  • Node 20 and 22.
  • @opentelemetry/[email protected] resolved as a single instance in the host's node_modules.

The Configuration shape this package targets is the published @vercel/[email protected] shape: singular logRecordProcessor / metricReader, plural spanProcessors array. Verified against the installed @vercel/[email protected] tarball (package/dist/types/types.d.ts and package/dist/node/index.js), and additionally pinned at the type level via the FoamOtelV1Config interface returned from foamOtelConfig. The waitUntil hook used by WaitUntilLogRecordExporter / WaitUntilMetricExporter reads the same globalThis[Symbol.for('@vercel/request-context')] slot @vercel/otel's CompositeSpanProcessor reads (verified against the same package/dist/node/index.js); on runtimes that do not populate this Symbol the wrappers fall through to bare fire-and-forget export. When @vercel/[email protected] ships and renames the config fields to plural arrays, this package will release a corresponding major.

OpenTelemetry Version Skew

This package is intentionally insulated from OpenTelemetry version drift in the host application:

  • Bundled SDKs, pinned to exact patches on the OTel JS 1.x train. @opentelemetry/[email protected], @opentelemetry/[email protected], @opentelemetry/[email protected], @opentelemetry/[email protected], @opentelemetry/[email protected], and the matching OTLP HTTP exporters at 0.57.2 are declared as exact versions in this package's dependencies (no ^ range). That means npm install @foam-ai/vercel-otel always resolves the same OTel SDK floor regardless of when the host ran install, and there is no npm dedupe step required to keep this package's processors aligned with each other. The 1.x train is what @vercel/[email protected] peer-depends on, so handing our BatchSpanProcessor / SimpleLogRecordProcessor / PeriodicExportingMetricReader to its providers is a same-major handoff. When @vercel/otel ships a major that switches to OTel JS 2.x, this package will release a corresponding major.
  • Processor interface, not implementation. The processors handed to registerOTel (BatchSpanProcessor, SimpleLogRecordProcessor, PeriodicExportingMetricReader) are consumed via stable processor interfaces. @vercel/otel is free to publish patch / minor releases within >=1.10 <2 without coordinating with this package.
  • @opentelemetry/api-logs only on console capture. The console-capture wrapper looks up logs.getLogger(...) lazily, after registerOTel has installed its global logger provider. If the host pulls in a different @opentelemetry/api-logs major, console capture will silently no-op rather than crash the request.
  • Duplicate @opentelemetry/api installs are a real failure mode. @opentelemetry/api uses a Symbol.for('opentelemetry.js.api.<major>') slot on globalThis to coordinate registration across copies. If a host ends up with two copies of @opentelemetry/api resolved at different paths in node_modules (e.g. one nested under vercel-otel/, one at the root), they will not share the global delegate and trace.getTracer(...) can silently degrade to a NoopTracer in one of them — no error is thrown. After installing this package, run npm dedupe / pnpm dedupe / yarn dedupe to ensure a single resolved @opentelemetry/api.

Host Upgrade Process

When the host application updates @vercel/otel or Next:

  1. Upgrade @vercel/otel / next in the host app.
  2. Run install; peer dependency warnings will show whether this package's declared range still covers the new versions.
  3. Run the host app's server / instrumentation smoke test.
  4. If the new versions are outside this package's range, open an issue against @foam-ai/vercel-otel so a range-widening patch/minor (or a new major) can be released.

Safe Failure

If the runtime is unsupported or required config is missing, the package no-ops. It will not crash the host app on startup. Telemetry loss is acceptable; app startup failure is not.

Acknowledgements

This package is built on top of the following open source projects:

| Project | License | Link | |---------|---------|------| | OpenTelemetry JS (@opentelemetry/api, @opentelemetry/api-logs) | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js | | OpenTelemetry JS (@opentelemetry/exporter-trace-otlp-http, @opentelemetry/exporter-metrics-otlp-http, @opentelemetry/exporter-logs-otlp-http) | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js | | OpenTelemetry JS (@opentelemetry/sdk-trace-base, @opentelemetry/sdk-metrics, @opentelemetry/sdk-logs) | Apache-2.0 | https://github.com/open-telemetry/opentelemetry-js |

Full third-party license texts are available in THIRD_PARTY_LICENSES.md.