New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@apitraffic/apm

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@apitraffic/apm

OpenTelemetry performance monitoring for ApiTraffic. Install it next to @apitraffic/node to time the database, cache, queue and outbound calls inside each request: it brings the OpenTelemetry SDK and the instrumentations it uses, pinned to versions that i

latest
Source
npmnpm
Version
3.0.0
Version published
Weekly downloads
317
-53.24%
Maintainers
1
Weekly downloads
 
Created
Source




Website   •   Docs   •   APM Documentation   •   X   •   Discord

@apitraffic/apm

npm version license

@apitraffic/apm is the opt-in OpenTelemetry bridge for ApiTraffic. It maps OpenTelemetry spans - pg, mysql/mysql2, mongodb, redis/ioredis, undici/fetch, kafka, amqp, the AWS SDK and anything else your OpenTelemetry setup traces - onto the active ApiTraffic request as ApiTraffic spans plus per-request rollup metrics, so each request's waterfall shows where its time went.

All OpenTelemetry weight lives in this package, declared as peer dependencies, so the base client (@apitraffic/utilities) stays lightweight - nothing OpenTelemetry is installed unless you add it.

Contents

Do I need to install this?

Yes, to turn on performance monitoring. Since 3.0 no ApiTraffic package installs it for you:

  • Using @apitraffic/node or a framework package built on it (NestJS, Meteor, LoopBack, AdonisJS, Sails, Strapi)? Install this package next to it, then start your app with that package's preload, for example node -r @apitraffic/node/register app.js. The preload finds this package and calls its register().
  • Using a standalone framework package (@apitraffic/express, -fastify, -koa, -hapi)? Install this package and call require('@apitraffic/apm').register() on the first line of your entry file, or use processor mode.

Either way, APM attaches spans to requests the SDK is already capturing; it does not capture requests itself.

Requirements

  • Node 20+ (engines: >=20.0.0).
  • An ApiTraffic SDK capturing requests in the same process: @apitraffic/node or one of the framework packages. Your plan must include APM.
  • @opentelemetry/api >=1.8.0 <1.10.0, the one peer dependency: your app and every instrumentation must share a single copy. npm and pnpm install it with this package; with yarn 1, add it yourself. The upper bound is the one @opentelemetry/sdk-trace-node 2.12 sets.

Everything else bundled mode needs is a dependency of this package, at versions that install together: @opentelemetry/sdk-trace-node, @opentelemetry/instrumentation and the instrumentations APM uses (the default set, plus graphql, grpc, mongoose, knex, http, pino, winston and bunyan for enabledInstrumentations). @opentelemetry/auto-instrumentations-node is no longer needed: each instrumentation is loaded from its own package, and nothing is loaded that is not enabled. @opentelemetry/sdk-trace-base (>=1.25.0 <3) is an optional peer, used for types only.

Installation

npm install @apitraffic/apm

The same package serves both modes: processor mode next to the OpenTelemetry setup you already have, and bundled mode in an application with no other tracing. With pnpm, yarn or bun, use pnpm add, yarn add or bun add.

Upgrading from 2.x

@apitraffic/node 2.x installed this package for you, and bundled mode needed @opentelemetry/sdk-trace-node, @opentelemetry/instrumentation and @opentelemetry/auto-instrumentations-node installed by hand. In 3.0, install @apitraffic/apm yourself and remove those three unless your own code uses them. ES-module apps change the hook registration to register('@opentelemetry/instrumentation/hook.mjs', import.meta.resolve('@apitraffic/apm')). An instrumentation this package does not ship that you name in enabledInstrumentations is loaded from your app (install its package), or from @opentelemetry/auto-instrumentations-node if you keep it installed.

Zero impact on your application

This bridge holds to the SDK's first rule: zero impact on your application. It never throws into app code, never alters a wrapped call, never blocks the request path. On any internal error it drops the span (and logs only when debug is on). When bundled mode cannot or should not start - OpenTelemetry missing, another tracer already in the process - register() says why on the console, returns, and your app boots normally. Importing this package loads no OpenTelemetry code; it is required lazily.

Which mode?

Your application…Use
already sets up OpenTelemetry, or might laterProcessor mode (recommended)
runs Datadog (dd-trace) or New RelicNeither - bundled mode steps aside (see below); request capture is unaffected
has no tracing of any kind and you want zero setupBundled mode

Bundled mode claims the process-wide OpenTelemetry tracer provider (and context manager; OpenTelemetry logs, metrics and propagation are left alone), and the OpenTelemetry API only accepts one tracer provider: an OpenTelemetry setup the application starts after register() is refused with nothing but a diagnostic message. Processor mode cannot cause that, because your application keeps owning its provider.

Add ApiTrafficSpanProcessor to the tracer provider you already have. ApiTraffic receives the spans your instrumentations already produce; nothing is patched a second time and nothing about your exporters, propagators or sampling changes.

import { NodeSDK } from '@opentelemetry/sdk-node';
import { ApiTrafficSpanProcessor } from '@apitraffic/apm';

const sdk = new NodeSDK({
  spanProcessors: [
    new ApiTrafficSpanProcessor(),
    // ...your existing processors
  ],
  instrumentations: [/* your existing instrumentations */],
});
sdk.start();

new ApiTrafficSpanProcessor(options?) takes the same options as register(); the bundled-mode ones (enabledInstrumentations, instrumentHttp, …) are ignored here, because your setup chooses its own instrumentations.

The processor correlates each span with its request when the span starts (a driver's callback usually runs outside the request's async context), so it has to be a processor: ApiTrafficSpanExporter is deprecated, because behind a BatchSpanProcessor an exporter sees spans long after their request and drops every one. A span that started outside any request (a background job, a pool keep-alive) is dropped rather than attributed to whichever request is active where it ends.

forceFlush() on the processor (or on your provider) also asks the base client to deliver the request payloads it has queued, waiting at most flushTimeoutMs. The processor's shutdown() flushes the same way but stops nothing: your provider shutting down does not mean the app has stopped serving HTTP. To stop the base client too, call its own shutdown() (for example apitraffic.shutdown() from @apitraffic/node).

Bundled mode: only when nothing else in the app does tracing

OpenTelemetry patches a library as it is loaded, so register() has to run before your application loads pg, redis and the rest. If it runs late, those libraries produce no spans; register() prints which ones it saw already loaded and returns them in loadedTooEarly.

Bundled mode no longer uses @opentelemetry/sdk-node; you can remove it if nothing else in your app does.

CommonJS - a preload:

// instrument.js
require('@apitraffic/apm').register();
node -r ./instrument.js ./server.js

With @apitraffic/node, node -r @apitraffic/node/register ./server.js does the same thing without a file of your own.

ESM ("type": "module", .mjs, or TypeScript compiled to ESM) - --import, plus OpenTelemetry's loader hook so packages loaded through import can be patched too. The hook comes from @opentelemetry/instrumentation, which is in the install line above.

// instrument.mjs
import { register } from 'node:module';
import apm from '@apitraffic/apm';

register('@opentelemetry/instrumentation/hook.mjs', import.meta.resolve('@apitraffic/apm'));
apm.register();
node --import ./instrument.mjs ./server.mjs

What register() sets up

What register(options?) sets up is deliberately narrow:

  • A curated set of instrumentations: pg, mysql, mysql2, mongodb, tedious, oracledb, cassandra-driver, redis, ioredis, memcached, undici (fetch), amqplib, kafkajs and aws-sdk. Anything else in the auto-instrumentation catalog (graphql, grpc, mongoose, knex, …) is opt-in via enabledInstrumentations. Core http/https stays off unless instrumentHttp is set - the base client already records those calls.
  • No log correlation: the pino, winston and bunyan instrumentations, which add trace_id/span_id to every log line, are off. Opting into one gives you the ids in your logs but does not turn log lines into OpenTelemetry log records.
  • Tracing only: a NodeTracerProvider whose one span processor is ours, registered as the global tracer provider with the AsyncLocalStorage context manager, and the instrumentations above bound to it. Nothing else: no log or meter provider (the global ones stay free for your own OpenTelemetry logs or metrics setup, before or after register()), no exporters of any kind - OTEL_METRICS_EXPORTER, OTEL_LOGS_EXPORTER and OTEL_EXPORTER_OTLP_ENDPOINT are not read, and OTEL_TRACES_EXPORTER is ignored (the 1.x tracer provider looks it up but has no exporter registered under any name, and its default processor is replaced by ours), so nothing is posted to localhost:4318 or your collector - no propagators (no traceparent/baggage headers added to your outbound calls, whatever OTEL_PROPAGATORS says), and no resource detectors. The instrumentations get a no-op meter and logger provider, so they record no metrics or log records into a provider you register.
  • OTEL_TRACES_SAMPLER is ignored, as is OTEL_TRACES_SAMPLER_ARG: the provider records every span, and ApiTraffic's own capture decides which ones a request keeps (the base client's spanSampleRate and per-request span cap). An OpenTelemetry sampler would drop spans before that capture saw them. To sample with OpenTelemetry, use processor mode on your own provider.

When it does not start

register() is idempotent and fail-open. It returns { registered, reason?, loadedTooEarly? } and never throws. It does not start when:

reasonCause
otel-sdk-disabledOTEL_SDK_DISABLED=true, OpenTelemetry's own off switch for the process.
other-apm-detecteddd-trace or the New Relic agent is running or about to (see below). Two tracers wrap every driver call twice. Processor mode is the way to use APM alongside your own OpenTelemetry setup.
provider-already-activeAn OpenTelemetry tracer provider is already registered. Use processor mode.
otel-peers-missing@opentelemetry/api (or, after a broken install, the SDK this package depends on) could not be loaded. The message names what is missing.
span-processor-not-attachedThe installed @opentelemetry/sdk-trace-node did not take the ApiTraffic span processor, so bundled mode would record nothing. Use processor mode.
captureSpans-disabledcaptureSpans is false.
already-registeredregister() already started bundled mode in this process.
unexpected-errorSomething failed inside register(); your app keeps running without APM.

The first five are printed on the console once per process, whether or not debug is on. A successful start also prints one line saying bundled mode owns the tracer provider - the breadcrumb for an OpenTelemetry setup someone adds later.

How another agent is detected, per agent:

  • Its own off switch wins. DD_TRACE_ENABLED=false (dd-trace), or NEW_RELIC_ENABLED=false / NEW_RELIC_AGENT_ENABLED=false (New Relic): the agent patches nothing, so it does not count even when it is loaded.
  • Loaded: global._ddtrace is set, or dd-trace / newrelic is in require.cache (both are normally preloaded with -r).
  • About to load: the agent package is installed (resolvable from the app) and its configuration is in the environment - DD_SERVICE, DD_ENV, DD_AGENT_HOST, DD_TRACE_AGENT_URL or DD_TRACE_ENABLED=true for dd-trace; NEW_RELIC_LICENSE_KEY or NEW_RELIC_APP_NAME for New Relic. Environment variables alone do not count: the Heroku New Relic add-on, for one, sets NEW_RELIC_LICENSE_KEY for apps that never install the agent.

If the detection is wrong for your app - say the agent is installed and configured but never started - start bundled mode anyway with register({ force: true }) or API_TRAFFIC_APM_FORCE=true. force only skips this check; it never overrides OTEL_SDK_DISABLED or an OpenTelemetry provider that is already registered.

Stopping bundled mode

shutdown() from @apitraffic/apm stops bundled mode: it unpatches the instrumentations, hands the global tracer provider and context manager back (only if they are still the ones register() installed), and flushes and shuts down the provider, so register() or your own OpenTelemetry setup can start afterwards. Tracers obtained before shutdown() (a module that cached trace.getTracer(...)) stay bound to the provider it shut down - that is how OpenTelemetry's proxy tracers work - so their spans go nowhere: ApiTraffic ignores them from then on. Get tracers again after a later register() or your own setup.

@apitraffic/node's shutdown() calls this for you when its register() started bundled mode.

Options

All options are also settable via API_TRAFFIC_* environment variables. An explicit option wins over the environment variable, which wins over the default. Boolean variables accept true/false, 1/0, yes/no or on/off; list variables are comma-separated (API_TRAFFIC_ENABLED_INSTRUMENTATIONS=graphql,grpc).

OptionEnvDefaultMeaning
captureSpansAPI_TRAFFIC_CAPTURE_SPANStrueMaster switch.
captureStatementsAPI_TRAFFIC_CAPTURE_STATEMENTStrueInclude normalized SQL/command in meta.statement.
captureStatementParamsAPI_TRAFFIC_CAPTURE_STATEMENT_PARAMSfalseAlso keep the statement as issued in meta.statementRaw and its bind values in meta.params. See SQL values.
captureRedisKeysAPI_TRAFFIC_CAPTURE_REDIS_KEYSfalseInclude the concrete Redis key (fingerprints always use the masked shape).
captureCallSiteAPI_TRAFFIC_CAPTURE_CALL_SITEbucket setting (on)Record the application frames that started the operation in meta.callSite / meta.callSiteFn / meta.callStack. Unset follows the bucket, which is on unless switched off in the dashboard. See Code location.
captureStackDepthAPI_TRAFFIC_CAPTURE_STACK_DEPTHbucket setting (10)Application frames kept per span; 1 keeps the call site alone.
maxSpanMetaBytesAPI_TRAFFIC_MAX_SPAN_META_BYTES2048Per-span meta cap; params/statementRaw are dropped first, then statement.
enabledInstrumentationsAPI_TRAFFIC_ENABLED_INSTRUMENTATIONS[]Bundled mode: instrumentations to turn on beyond the curated set, as full names (@opentelemetry/instrumentation-graphql) or short ones (graphql).
disabledInstrumentationsAPI_TRAFFIC_DISABLED_INSTRUMENTATIONS[]Bundled mode: instrumentations to turn off. Wins over everything else.
instrumentHttpAPI_TRAFFIC_INSTRUMENT_HTTPfalseBundled mode: enable OTel's http/https client instrumentation. Off by default because the base client's native hook already records http.out with full bodies - leaving OTel's on would double-count.
synthesizeOutboundRecordsAPI_TRAFFIC_SYNTHESIZE_OUTBOUND_RECORDStrueBackfill direction:"out" records for outbound calls the base client did not capture (e.g. undici.request/Pool used directly).
flushTimeoutMsAPI_TRAFFIC_APM_FLUSH_TIMEOUT_MS5000Longest the processor's forceFlush()/shutdown() waits for the base client to deliver its queue.
serviceNameAPI_TRAFFIC_SERVICE_NAME-Service name reported to OTel (bundled mode).
forceAPI_TRAFFIC_APM_FORCEfalseBundled mode: start even though dd-trace or New Relic appears to be active. Never overrides OTEL_SDK_DISABLED or an existing OpenTelemetry provider.
sendStatementTextOnce (base client)API_TRAFFIC_SEND_STATEMENT_TEXT_ONCEfalseSend each fingerprint's statement / callStack once per process per statementTextResendSeconds (API_TRAFFIC_STATEMENT_TEXT_RESEND_SECONDS, 3600) and list what later spans left off in textOmitted; the platform resolves them from its fingerprint dictionary. Set on the base client (the framework middleware), not register(). Leave it off unless ApiTraffic support has confirmed your account supports it.
debugAPI_TRAFFIC_DEBUGfalseVerbose internal logging.

The per-request span cap (maxSpansPerRequest, default 500), span sampling (spanSampleRate) and redisKeyPatterns belong to the base client: set them on the framework middleware or through their API_TRAFFIC_* variables, not on register(). See the SDK configuration reference.

require('@apitraffic/apm').register({
  serviceName: 'orders-api',
  enabledInstrumentations: ['graphql'],
  disabledInstrumentations: ['@opentelemetry/instrumentation-mongodb'],
});

SQL values and parameters

By default a span never carries a literal from your query: meta.statement holds the normalized statement (where id = ?) and bind values are discarded before the span is retained or sent.

Turning capture on adds two fields - meta.statementRaw (the statement as issued) and meta.params (JSON-encoded bind values) - and enables OTel's enhancedDatabaseReporting for the supported DB instrumentations. These carry whatever your users' data is, including tokens and secrets, so it is off until you ask for it, and it is worth turning back off once you are done debugging.

It can be switched on in two places:

  • locally, with captureStatementParams / API_TRAFFIC_CAPTURE_STATEMENT_PARAMS;
  • per bucket, from Bucket Settings → APM Capture in the dashboard. That reaches running servers through the SDK's settings and realtime reload, so it takes effect without a redeploy. A dashboard "on" wins over a local default; a local true does not require the dashboard.

Fingerprints are always computed from the normalized statement, so flipping this never splits a query's identity in the Performance analytics.

Code location

With captureCallSite on (the default, unless the bucket switches it off), a span carries the application frames that started the work - meta.callSite (src/services/orders.ts:142:9), meta.callSiteFn and the caller chain in meta.callStack - so a slow or repeated query points at the code that issued it. The stack is taken when the operation is initiated, because a driver callback runs far from the caller; frames in node internals, node_modules and the ApiTraffic packages are skipped, and paths are made relative to the project root (detected, or the base client's projectRoot / API_TRAFFIC_PROJECT_ROOT). Transpiled projects report the built file unless source maps are deployed.

This captures a stack per span. Once a request has reached its span cap (or is sampled out of span detail) the stack is not walked at all, since the span body would be dropped anyway; rollups still count the work. Turn it off for the bucket in the dashboard. captureCallSite: false (or API_TRAFFIC_CAPTURE_CALL_SITE=false) turns it off only for OpenTelemetry bridge spans: the SDK's own spans keep call sites while the bucket has the setting on.

Time-boxed capture

The capture switches above (captureStatementParams, captureRedisKeys, captureCallSite) are also settable per bucket from Bucket Settings → APM Capture, together with an auto-disable window (15 minutes to 24 hours, or until turned off). The window is enforced twice: the API stops reporting the switches once it passes, and the SDK arms an unref'd timer that returns capture to the defaults at that instant without waiting for a reload. A local true is never disabled by a remote expiry. See Capture settings.

What it maps

OTel signalApiTraffic typeNotes
db.system (SQL)db.querynormalized statement + fingerprint
db.system = redis / memcachedredis / cacheverb + masked key shape
db.system = mongodbdb.queryoperation + collection
messaging.systemqueuesystem + destination
rpc.system = aws-apiqueue (SQS/SNS) · db.query (DynamoDB) · http.out (S3, …)Service.Method
rpc.system = grpcrpcservice/method
GraphQLgraphqloperation + field
http.* client spanhttp.outmetadata-only (bodyCaptured:false); only for outbound calls the base client did not capture itself (it owns http/https and fetch - see below)

In processor mode every span your instrumentations produce is mapped. In bundled mode gRPC and GraphQL spans need enabledInstrumentations: ['grpc', 'graphql'].

Values are masked before anything leaves your process: literals never appear in a normalized statement, id-like parts of span names and table or collection names are masked, connection-string credentials and file paths are dropped from peer names, error messages on spans are scrubbed of values, and query strings in span URLs keep their keys only.

Outbound HTTP parity

The native http/https hook in the base client captures full request/response bodies and is the single source of truth for http.out. To avoid a second, metadata-only span for the same native call, register() disables OTel's http instrumentation by default (set instrumentHttp: true to re-enable it). fetch is likewise owned by the base client: @apitraffic/utilities wraps globalThis.fetch and records those calls with full request/response bodies and the real wire headers (captureFetch, on by default). It marks each call it captured on the active request, so this bridge drops the OTel undici span for the same call instead of adding a metadata-only duplicate.

What's left for OTel here is outbound HTTP neither hook sees - chiefly undici.request / Client / Pool used directly. For those the bridge:

  • adds a metadata-only http.out span, and
  • (optionally, synthesizeOutboundRecords) synthesizes a direction:"out" out-record, marked bodyCaptured:false.

De-dupe is per-request, and the base client's full-body records always win.

Fingerprints

Spans are fingerprinted so identical logical operations collapse to one identity (GROUP BY fingerprint analytics). SQL is normalized with a lexer per dialect, and document-store queries and Redis commands by shape, so queries that differ only in their values group together. The canonical normalizer/masker/fingerprinter lives solely in the base client (@apitraffic/utilities) and is re-exported here for convenience. This bridge calls those exported helpers with raw inputs, so a span emitted by the manual client API and the same operation captured via OTel produce an identical fingerprint. There is no divergent fallback algorithm.

API

ExportDetails
register(options?)Start bundled mode. Returns RegisterResult ({ registered, reason?, loadedTooEarly? }). Never throws.
isRegistered()true if bundled mode started in this process.
shutdown()Stop bundled mode (see Stopping bundled mode). Returns a promise; never throws.
ApiTrafficSpanProcessorThe OpenTelemetry span processor for processor mode: new ApiTrafficSpanProcessor(options?).
ApiTrafficSpanExporterDeprecated. Kept for compatibility; prints a notice once. Use ApiTrafficSpanProcessor.
resolveOptions(options?)The options after environment variables and defaults are applied.
normalizeSql, maskRedisKeyThe base client's canonical normalizers, re-exported.
mapReadableSpan, isOutboundHttp, outboundCallKey, buildOutboundRecord, registerNativeOutbound, claimOutbound, spanClaimedByNativeLower-level mapping and de-duplication helpers, for custom integrations.

TypeScript declarations ship with the package. Exported types: ApmOptions, ResolvedApmOptions, RegisterResult, ApiTrafficSpan, SpanType, SpanMeta, MinimalReadableSpan, MapContext and OutboundRecord. OpenTelemetry types are imported type-only, so importing this package does not load OpenTelemetry.

Troubleshooting

Set debug: true (or API_TRAFFIC_DEBUG=true) for verbose logging. Skip reasons are printed without it.

A request has no waterfall at all. APM was not started in that process, or was started too late. Check the start-up output: bundled mode prints why it did not start, and which drivers were already loaded when it ran. register() must run before your database and cache modules are loaded - use a -r preload (or --import for ES modules) to be sure. With your own OpenTelemetry setup, check that ApiTrafficSpanProcessor is in its spanProcessors.

Only outbound HTTP appears, no queries. The driver's instrumentation was not applied. Check that the driver is in the curated set (or listed in enabledInstrumentations), that it is loaded after register(), and that it is not listed in disabledInstrumentations. For an ES-module app, check that the OpenTelemetry loader hook is registered.

The waterfall stops partway through the request. The per-request span cap (maxSpansPerRequest, default 500) was reached - typically by an N+1 query loop. The rollups still include the dropped work.

Queries show where id = ? instead of the values. That is the default. Turn values on deliberately, and for a limited time - see SQL values and parameters.

My own OpenTelemetry setup stopped exporting. Bundled mode owns the process's tracer provider, so a provider your app registers afterwards is refused. Remove the register() preload and add ApiTrafficSpanProcessor to your own setup.

Outbound calls appear twice. OpenTelemetry's http instrumentation is enabled alongside the SDK's own outbound capture. Leave instrumentHttp off (the default).

More in Set up APM → Troubleshooting.

Upgrade notes

2.0.0 - bundled mode is tracing-only

Bundled register() changed what it installs into your process. If your application relied on any of these side effects, configure OpenTelemetry yourself (and add ApiTrafficSpanProcessor to it):

  • No NodeSDK. Bundled mode now builds a bare NodeTracerProvider and registers the instrumentations itself. Install @opentelemetry/sdk-trace-node and @opentelemetry/instrumentation in place of @opentelemetry/sdk-node. NodeSDK also registered a global logger provider (so your own OpenTelemetry logs setup was refused), started a metrics exporter from OTEL_METRICS_EXPORTER on the 1.x line, and did nothing under OTEL_SDK_DISABLED=true while register() reported success. None of that happens now; under OTEL_SDK_DISABLED=true register() returns otel-sdk-disabled.
  • No log or metric exporters. NodeSDK's defaults started an OTLP log exporter and metric reader posting to localhost:4318 or your OTEL_EXPORTER_OTLP_ENDPOINT. They are no longer started.
  • No propagators. Outbound calls no longer gain traceparent/baggage headers from bundled mode. ApiTraffic's own distributed-trace grouping reads the incoming traceparent and is unaffected.
  • No log-correlation instrumentations. pino, winston and bunyan log lines no longer carry trace_id/span_id. Opt back in with enabledInstrumentations: ['pino'] (or winston/bunyan).
  • A curated instrumentation set instead of the whole catalog. graphql, grpc, mongoose, knex, dataloader, generic-pool, the web-framework, dns, net and runtime-metric instrumentations no longer run by default. Add the ones you want with enabledInstrumentations.
  • No resource detection at boot.
  • register() steps aside when dd-trace or New Relic is running or about to (their own off switches respected; force: true to override), and when an OpenTelemetry provider is already registered - now detected by identity, so a minified server bundle no longer reports a conflict that is not there.
  • Skip reasons are printed without debug, once per process, as is one line when bundled mode starts.
  • ApiTrafficSpanExporter is deprecated (it prints a notice once); use ApiTrafficSpanProcessor.
  • Processor forceFlush()/shutdown() now flush the base client's queue when the base client supports it (2.0+), bounded by flushTimeoutMs.
  • Peer dependencies: @opentelemetry/api is the only required peer. @opentelemetry/sdk-trace-base is optional (types only; your provider brings it in processor mode). @opentelemetry/sdk-trace-node, @opentelemetry/instrumentation and @opentelemetry/auto-instrumentations-node are optional and only needed for bundled mode. @opentelemetry/sdk-node, @opentelemetry/core and @opentelemetry/semantic-conventions are no longer peers.
  • OpenTelemetry 1.x spans (sdk-trace-base 1.x, inside the peer range) now keep their parent and instrumentation scope; they used to be mapped as root spans with no scope, which also dropped GraphQL spans recognised only by scope.
  • Bundled mode on sdk-trace-node 1.25–1.27 now records spans: those versions ignore the spanProcessors option, so the processor is attached with addSpanProcessor on 1.x, and register() returns span-processor-not-attached rather than reporting a start that records nothing.
  • shutdown() hands the global tracer provider and context manager back, so register() or your own OpenTelemetry setup can start after it. Spans from tracers obtained before shutdown() are no longer recorded.
  • Bundled mode ignores OTEL_TRACES_SAMPLER: its provider samples every span, and ApiTraffic's spanSampleRate decides. always_off or a traceidratio used to drop spans silently.
  • Processor mode no longer attributes a span that started outside any request to whichever request is active where it ends (a pooled socket callback running in another request's context); such spans are dropped.
  • New query fingerprints (version 2). Expect your query groups to start fresh on the first deploy with this version; groups recorded by older SDKs are unaffected.

Changelog and license

Release notes are in CHANGELOG.md. Released under the MIT License.

Community

Follow @apitraffic/apm on npm to stay updated on new releases.

Follow us on our Blog and on X.

Chat with the ApiTraffic team and other members on Discord and follow our tutorials and other videos on YouTube.

ApiTraffic Discord

ApiTraffic YouTube

ApiTraffic on X

Keywords

apitraffic

FAQs

Package last updated on 03 Nov 2026

Related posts