@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-otelUsage
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/otelinstrumentation), - record metrics through the OTel meter API (
metrics.getMeter('...').createCounter(...)etc.), - emit log records through the OTel logs API (or enable
captureConsoleLogsand use plainconsole.*).
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/otelwraps every span processor in aCompositeSpanProcessorwhoseonStartcallsvrc.waitUntil(() => forceFlush())against the Vercel request context. That is what guaranteesBatchSpanProcessorflushes before the function freezes. - For logs, no such wrapping exists. The
LoggerProviderreceives the processors as-is, and the only flush hook isSdk.shutdown()— which@vercel/oteldoes not invoke per request on Vercel serverless. With the default 5-secondBatchLogRecordProcessorschedule, 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 thewaitUntilpromise to a failedExportResultitself afterexportTimeoutMsso 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 10stimeoutMillis, so under normal conditions the inner exporter's timeout fires first and ours only kicks in if the inner one fails to. shutdown()andforceFlush()await wrapper-tracked in-flight exports before forwarding to the inner exporter. Hosts that drain viaLoggerProvider.shutdown()get back-pressure equivalent to "every export the wrapper observed has settled," independent of any internal_sendingPromisestracking the inner OTLP exporter happens to do.- Diagnostics flow through
@opentelemetry/api'sdiag.warn. Timeouts, non-successExportResults, swallowedwaitUntilthrows, and swallowed result-callback throws are all logged with a@foam-ai/vercel-otel:prefix so a host that has calleddiag.setLogger(...)(a common OTel pattern;@vercel/otelitself 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 vianode:util.format, and aconsole.methodattribute, - 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 in0.4.4). - Patching is idempotent: calling
foamOtelConfigmultiple 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/otelhas 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:
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
metricsoff until the host actually uses the meter API.Best-effort flushing on short-lived invocations. The 60-second periodic timer rarely fires before a Vercel serverless function returns.
@vercel/otelonly wires per-requestwaitUntil-based flushing into the span processor pipeline (viaCompositeSpanProcessor); theMeterProvideris only flushed viaSdk.shutdown(), which Vercel does not invoke per request. From0.4.4the OTLP metric exporter is wrapped in the samewaitUntildecorator as the log exporter, so any metric export that does happen to fire inside a request handler (e.g. via a manualmeterProvider.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.0ships:- Patch: bug fixes only, no API changes.
- Minor: additive options/exports only.
- Major: breaking API, runtime, exporter, or config behavior changes.
- The current
0.xtrain follows the npm convention that minor releases may include breaking changes. Default-behavior shifts (e.g. defaultingcaptureConsoleLogstotruein0.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
/serverexport inpackage.json. - Smoke-tested example for Next.js +
@vercel/otel. - No browser code included in the
/serverbundle.
Backward Compatibility
Patch/minor releases must not:
- Change the shape of
foamOtelConfig(additive optional fields are allowed). - Make
foamOtelConfigthrow. - 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 tofalse. - 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 all0.4.4users, no breaking changes):- Per-export
waitUntiltimeout (exportTimeoutMs, default 15000ms).0.4.4registered the in-flight OTLP export promise withwaitUntilbut 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.5resolves the promise to a failedExportResultitself afterexportTimeoutMs, with adiag.warndescribing the timeout. Default sits 5s above the OTLP HTTP exporter's own 10stimeoutMillis, 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()andforceFlush()now await wrapper-tracked in-flight exports before forwarding to the inner exporter.0.4.4forwarded both calls immediately; an in-flight export tracked by the wrapper could be torn down mid-flight ifLoggerProvider.shutdown()ran while it was still in the air. The wrapper now keeps aSet<Promise<ExportResult>>of every in-flight export it kicked off andPromise.allSettleds it before forwarding. This also stopsforceFlush()from depending onOTLPExporterBase._sendingPromises(an undocumented internal of the inner exporter) for correctness.AggregationTemporalityfallback uses the named enum.0.4.4'sWaitUntilMetricExporter.selectAggregationTemporalityreturned a hardcoded1with a comment about avoiding SDK identity coupling. Identity coupling to@opentelemetry/sdk-metricsis already accepted (we instantiateMetricReaderandPeriodicExportingMetricReaderagainst it), so importingAggregationTemporality.CUMULATIVEfrom 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'sdiag.warn. Every non-successExportResult, everywaitUntil-throw, every swallowed result-callback throw, and every wrapper timeout is now logged with a@foam-ai/vercel-otel:prefix. Hosts that have calleddiag.setLogger(...)(a common OTel pattern;@vercel/otelitself 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/apiwas already a direct dependency. - New optional
exportTimeoutMsfield onFoamOtelExporterOptions. Additive, fully backwards-compatible.0and negative values fall back to the 15000ms default. - Hosts on
0.4.4get the timeout bound, the in-flight drain on shutdown/flush, the named-enum fallback, anddiag.warnobservability on redeploy with no call-site changes.
- Per-export
0.4.3->0.4.4(hardening; recommended for all0.4.3users, no breaking changes):- Per-invocation
waitUntilfor log and metric exports. The OTLP log and metric exporters are now wrapped in an internalWaitUntilLogRecordExporter/WaitUntilMetricExporterthat probesglobalThis[Symbol.for('@vercel/request-context')].get().waitUntiland registers each in-flight export's completion promise with it when the runtime exposes one. This is the same primitive@vercel/oteluses to flush spans and the same primitive that backs the public@vercel/functionswaitUntil. 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 to0.4.3. No new dependency is added: the wrappers read the same global Symbol Vercel already populates, gated behind atry/catch. See Logs on serverless. apiKeygate now rejects whitespace and non-strings.0.4.3and earlier used a bare!options.apiKeytruthy check, which lets" "through to form anAuthorization: Bearerheader against the Foam collector.0.4.4callsapiKey.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 wayapiKey: ""always has. No behaviour change for hosts passing a real key.captureConsoleLogsonly patches after the config is built.0.4.3patchedconsole.*before constructing the per-signal exporters. If a later step infoamOtelConfighad thrown, the host would have been left running with monkey-patchedconsole.*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.4builds the full config first and only patches the console immediately before returning, so a build-time throw fails closed cleanly.serviceNameis now optional. The field has been a no-op since0.2.x(this package does not read it;@vercel/otelattachesservice.nameto the resource shared by all signals from its ownserviceNameargument, andOTEL_SERVICE_NAMEoverrides both). The type signature is nowserviceName?: stringand 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, passserviceNametoregisterOTel(...).foamOtelConfig's return type is now a typedFoamOtelV1Configinstead ofRecord<string, unknown>, mirroring the singular log/metric keys this package writes. A future regression that returns the wrong key shape (e.g. the plurallogRecordProcessors/metricReadersshipped on the unreleased@vercel/[email protected]branch — the same shape that broke0.4.2) now failstsc --noEmitinstead of being caught only by the runtime regression test. The runtime test is still in place as defence in depth.@opentelemetry/coreis now a direct dependency (exact-pinned to1.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 importExportResult/ExportResultCodefrom a stable location without relying on transitive resolution.- No code changes required at the call site. Hosts on
0.4.3get thewaitUntilreliability improvement, the tighter input validation, and the safer console-patch ordering on redeploy.
- Per-invocation
0.4.2->0.4.3(bug fix; required for all0.4.xusers —0.4.0,0.4.1, and0.4.2all fail to export logs and (when enabled) metrics):- Returns the correct
@vercel/[email protected]config keys for logs and metrics.0.4.2switched tologRecordProcessors/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 keyslogRecordProcessorandmetricReader. Verified directly against the installed@vercel/[email protected]tarball:package/dist/types/types.d.tsdeclareslogRecordProcessor?: LogRecordProcessorandmetricReader?: MetricReader, and the bundled property accesses inpackage/dist/node/index.jsonly ever read the singular names.0.4.3returns those singular keys, so log records and metric readings are now actually wired into@vercel/otel'sLoggerProvider/MeterProvider. - Keeps the
SimpleLogRecordProcessorchange from0.4.2. Even with the correct keys,BatchLogRecordProcessoris unsuitable on Vercel serverless:@vercel/otelonly wires per-requestwaitUntil-based flushing into the span processor pipeline, not the logger provider, so batched log records are stranded in memory when the function freezes.SimpleLogRecordProcessorexports 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 (includingcaptureConsoleLogs: truemirrored output) and meter-API metrics begin landing in Foam after redeploy. No code changes required; thefoamOtelConfig({ ... })call site is unchanged.
- Returns the correct
0.4.1->0.4.2(superseded by0.4.3— do not upgrade to0.4.2):- Switched the log processor from
BatchLogRecordProcessortoSimpleLogRecordProcessorfor serverless reliability. This part is correct and is preserved in0.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 on0.4.2. Skip0.4.2and upgrade directly from0.4.1to0.4.3.
- Switched the log processor from
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 at0.57.2) instead of^ranges. No public API changes; no runtime behavior changes. Hosts that deduplicated against floating^0.57resolutions before may see lockfile churn on the nextinstall.
- Stability hardening only. The bundled OTel SDK / OTLP exporter dependencies are now pinned to exact patches (
0.3.x->0.4.x:metricsdefault flipped fromtruetofalse. Hosts that record through the OTel meter API need to passmetrics: trueexplicitly to keep exporting. Hosts that did not record metrics get a small reduction in idle overhead; behavior is otherwise unchanged.captureConsoleLogsdefault flipped fromtruetofalse. Server-sideconsole.*is no longer mirrored into Foam logs unlesscaptureConsoleLogs: trueis set. Pass it explicitly to restore0.3.xbehavior.
0.2.x->0.3.x:- New
captureConsoleLogs?: booleanoption (defaulttruein0.3.x, defaultfalsefrom0.4.xonward). metricsdefault wastruein0.3.x(defaultfalsefrom0.4.xonward).
- New
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/oteland Next.js ranges. - The latest
@vercel/[email protected]and the latest supported Next major/minor. - Node
20and22.
The smoke test verifies:
registerOTel({ ...foamOtelConfig(...) })boots.- No browser bundle imports the server package.
- Disabled mode no-ops (
foamOtelConfigreturns{}). - A server route emits a trace/span without crashing.
registerOTel({ ...foamOtelConfig({ captureConsoleLogs: true }) })boots andconsole.logcalls 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.14on Vercel Node serverless and Vercel Fluid runtimes (the published 1.x line; this package's peer range is>=1.10 <2).- Next.js
15.xand16.xinstrumentation hooks. - Node
20and22. @opentelemetry/[email protected]resolved as a single instance in the host'snode_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 at0.57.2are declared as exact versions in this package'sdependencies(no^range). That meansnpm install @foam-ai/vercel-otelalways resolves the same OTel SDK floor regardless of when the host ran install, and there is nonpm dedupestep 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 ourBatchSpanProcessor/SimpleLogRecordProcessor/PeriodicExportingMetricReaderto its providers is a same-major handoff. When@vercel/otelships 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/otelis free to publish patch / minor releases within>=1.10 <2without coordinating with this package. @opentelemetry/api-logsonly on console capture. The console-capture wrapper looks uplogs.getLogger(...)lazily, afterregisterOTelhas installed its global logger provider. If the host pulls in a different@opentelemetry/api-logsmajor, console capture will silently no-op rather than crash the request.- Duplicate
@opentelemetry/apiinstalls are a real failure mode.@opentelemetry/apiuses aSymbol.for('opentelemetry.js.api.<major>')slot onglobalThisto coordinate registration across copies. If a host ends up with two copies of@opentelemetry/apiresolved at different paths innode_modules(e.g. one nested undervercel-otel/, one at the root), they will not share the global delegate andtrace.getTracer(...)can silently degrade to a NoopTracer in one of them — no error is thrown. After installing this package, runnpm dedupe/pnpm dedupe/yarn dedupeto ensure a single resolved@opentelemetry/api.
Host Upgrade Process
When the host application updates @vercel/otel or Next:
- Upgrade
@vercel/otel/nextin the host app. - Run install; peer dependency warnings will show whether this package's declared range still covers the new versions.
- Run the host app's server / instrumentation smoke test.
- If the new versions are outside this package's range, open an issue against
@foam-ai/vercel-otelso 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.
