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

@senzops/apm-node

v1.6.0

Published

Universal APM SDK for Senzor

Readme

@senzops/apm-node

Official Node.js APM SDK for Senzor. Zero-dependency, production-grade distributed tracing, error tracking, log correlation, background task monitoring, and runtime metrics for Node.js services.

Replaces OpenTelemetry auto-instrumentation with a lightweight, Senzor-native alternative. 43 auto-instrumentations, 8 framework wrappers, and full AWS Lambda support in a single package with zero runtime dependencies.

Installation

npm install @senzops/apm-node

Quick Start

Option 1: Preload Mode (Recommended)

Preload ensures instrumentation hooks install before your application imports any library.

# CommonJS
SENZOR_API_KEY=sz_apm_xxx node -r @senzops/apm-node/register server.js

# ESM
SENZOR_API_KEY=sz_apm_xxx node --import @senzops/apm-node/register server.mjs

Option 2: Programmatic Init

import Senzor from '@senzops/apm-node';

Senzor.init({
  apiKey: process.env.SENZOR_API_KEY!,
});

Initialize as early as possible, before importing application modules.


Auto-Instrumentation Coverage

All instrumentations activate automatically when the corresponding library is imported. No configuration required.

Web Frameworks & HTTP

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 1 | Node http / https | http | Inbound requests, outbound calls, distributed trace propagation | | 2 | fetch (global) | fetch | Outbound HTTP, W3C Traceparent propagation | | 3 | undici | undici | Outbound HTTP via Node's native HTTP client | | 4 | Express | express | Route matching, middleware spans, error capture | | 5 | Fastify | fastify | Route matching, hook spans, lifecycle spans | | 6 | Koa | koa | Middleware stack, route detection | | 7 | NestJS | nestjs | Controller/method resolution, Guards, Interceptors, Pipes | | 8 | Hapi | hapi | Route handling, request lifecycle | | 9 | Restify | restify | Route matching, handler chain spans | | 10 | Connect | connect | Middleware stack instrumentation |

Databases

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 11 | pg (PostgreSQL) | pg | Queries, prepared statements, row counts, sanitized SQL | | 12 | mongodb | mongo | Collection operations (find, insert, update, delete, aggregate, bulk) | | 13 | mongoose | mongoose | Model operations with model/collection names | | 14 | mysql / mysql2 | mysql | Queries, sanitized SQL, connection metadata | | 15 | redis / ioredis | redis | Commands (GET, SET, HGETALL, etc.), key names | | 16 | knex | knex | Query builder operations, raw queries, transactions | | 17 | tedious (SQL Server) | tedious | T-SQL queries, stored procedures, row counts | | 18 | cassandra-driver | cassandra | CQL queries, batch operations, prepared statements | | 19 | memcached | memcached | get, set, delete, incr/decr, flush operations |

Messaging & Queues

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 20 | kafkajs | kafka | Producer send, consumer message processing, topic/partition | | 21 | amqplib (RabbitMQ) | amqplib | Publish, consume, ack/nack, queue/exchange names | | 22 | socket.io | socketio | Event emit/receive, namespace, room operations | | 23 | bullmq | bullmq | Worker job processing as task runs, queue delay, retries | | 24 | node-cron | cron | Scheduled job execution as task runs |

AI / LLM SDKs

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 25 | openai | openai | Chat completions, embeddings, images, audio, model, token usage | | 26 | @anthropic-ai/sdk | anthropic | Messages, completions, model, input/output tokens, stop reason | | 27 | @google/generative-ai | google-genai | generateContent, chat, embeddings, countTokens, token usage | | 28 | @google-cloud/vertexai | google-genai | Vertex AI generateContent, generateContentStream | | 29 | @azure/openai | azure-openai | Chat, completions, embeddings, images, audio (v1.x API) | | 30 | cohere-ai | cohere | Chat, embed, rerank, classify, summarize, tokenize | | 31 | @mistralai/mistralai | mistral | Chat, FIM, embeddings, model, token usage | | 32 | ai (Vercel AI SDK) | vercel-ai | generateText/streamText/generateObject/streamObject/embed, all fronted providers, streaming usage | | 33 | @langchain/core | langchain | Chat model invoke across providers, normalized usage_metadata | | 34 | groq-sdk | groq | Chat completions, embeddings, audio, streaming token usage | | 35 | ollama | ollama | Local chat/generate/embeddings, prompt_eval_count / eval_count | | 36 | @modelcontextprotocol/sdk | mcp | MCP client callTool / readResource — server, method, tool, resource, isError |

All AI instrumentations follow OTel GenAI semantic conventions (gen_ai.system, gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.usage.output_tokens, gen_ai.response.finish_reason) and feed the first-class AI Monitoring pillar (cost, tokens, latency, traces). Beyond auto-instrumentation, monitor ANY model — including unsupported providers and in-browser models (WebLLM) — with the manual API: Senzor.ai.trace(), Senzor.ai.generation(), Senzor.ai.wrapGeneration().

Agent observability (agents, tools, MCP, multi-agent)

Beyond single LLM calls, model the whole agent as a trace tree. Tool execution (Vercel AI SDK, LangChain) and MCP client calls are captured automatically; for custom agents, group work with the manual scopes — anything recorded inside auto-nests beneath the enclosing scope (parent/child + depth), and parallel tool calls stay correctly parented:

await Senzor.ai.agent({ name: 'researcher' }, async () => {
  // LLM calls + provider auto-instrumentation here nest under the agent
  const docs = await Senzor.ai.tool({ name: 'search_docs', args: { q } }, () => searchDocs(q));
  const data = await Senzor.ai.mcp({ server: 'github', method: 'tools/call', toolName: 'list_issues' },
    () => mcpClient.callTool({ name: 'list_issues', arguments: { repo } }));
  Senzor.ai.handoff({ from: 'researcher', to: 'writer', reason: 'analysis complete' });
});

agent / tool / mcp / handoff (plus chain, reasoning, guardrail) are structural observations: they build the trace tree but never pollute cost/call metrics (only generation/embedding do). Tool args/results and MCP payloads are captured only under content capture and masked server-side; identity (tool name, MCP server, agent name) is always recorded so failures stay attributable. Senzor is observability-only — these APIs record what your agent does; they never execute or re-run anything.

AI Monitoring sources have their own ingest key, separate from APM/Task. Pass it under ai.apiKey so AI telemetry routes to its source while other pillars keep their key:

// AI-only
Senzor.init({ ai: { apiKey: '<YOUR_AI_KEY>', captureContent: false } });

// Alongside APM/Task (one init, per-pillar keys)
Senzor.init({
  apiKey: '<YOUR_APM_OR_TASK_KEY>',
  ai: { apiKey: '<YOUR_AI_KEY>' },
});

Cloud & Infrastructure

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 32 | @aws-sdk/* (AWS SDK v3) | aws-sdk | All AWS service calls (S3, DynamoDB, SQS, SNS, Lambda, etc.), request ID, region, HTTP status | | 33 | AWS Bedrock Runtime | aws-sdk | Model invocations with GenAI attributes (tokens, model ID, finish reason) | | 34 | Firebase Admin (Firestore) | firebase | Document CRUD, collection queries, transactions, batch commits | | 35 | Firebase Admin (Auth) | firebase | User management, token verification, session cookies (16 methods) | | 36 | Firebase Admin (FCM) | firebase | Push notification delivery, multicast, topic operations (9 methods) |

Logging Libraries

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 37 | pino | pino | Trace/span ID injection into structured log output | | 38 | winston | winston | Trace/span ID injection into transport output | | 39 | bunyan | bunyan | Trace/span ID injection into log records |

RPC & Network

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 40 | @grpc/grpc-js | grpc | Unary/streaming calls, service/method, status codes, metadata propagation | | 41 | graphql | graphql | Resolvers, operation name/type, field paths, errors | | 42 | Node dns | dns | DNS lookups, resolve calls, hostname, record types | | 43 | Node net | net | TCP socket connections, data transfer, connection timing |

Utilities

| # | Library | Instrumentation Key | What's Captured | |---|---------|-------------------|-----------------| | 44 | dataloader | dataloader | Batch load calls, batch size, cache hits | | 45 | lru-memoizer | lru-memoizer | Memoized function calls, cache hit/miss | | 46 | generic-pool | generic-pool | Pool acquire/release, pool size, pending count | | 47 | Node fs | fs | File system reads, writes, stats, directory operations |

Runtime Metrics

Collected every 15 seconds (configurable) and sent alongside trace data:

  • Event Loop: lag (p50, p99, max), utilization (ELU)
  • Garbage Collection: duration by GC type (minor, major, incremental, weakcb)
  • Memory: heap used/total, RSS, external, array buffers
  • Active Handles & Requests: open file descriptors, active network connections

Framework Wrappers

Express

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

const app = express();
app.use(Senzor.requestHandler());    // First middleware
app.get('/users/:id', handler);
app.use(Senzor.errorHandler());      // Last middleware
app.listen(3000);

Fastify

import Senzor from '@senzops/apm-node';

fastify.register(Senzor.fastifyPlugin, {
  apiKey: process.env.SENZOR_API_KEY!,
});

NestJS

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(Senzor.requestHandler());
  await app.listen(3000);
}

Next.js (App Router)

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

export const GET = Senzor.wrapNextRoute(async (req) => {
  return Response.json({ ok: true });
});

Next.js (Pages Router)

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

export default Senzor.wrapNextPages(async (req, res) => {
  res.status(200).json({ ok: true });
});

H3 / Nuxt / Nitro

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

export default Senzor.wrapH3(defineEventHandler(async (event) => {
  return { ok: true };
}));

Nitro Plugin (Cloudflare Workers)

import { Senzor } from '@senzops/apm-node';

export default defineNitroPlugin((nitroApp) => {
  Senzor.init({ apiKey: '<YOUR_APM_KEY>' });
  Senzor.nitroPlugin(nitroApp);
});

Cloudflare Workers

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

export default {
  fetch: Senzor.worker(async (request, env, ctx) => {
    return new Response('OK');
  }),
};

AWS Lambda

Three deployment options, from zero-code to code-level:

Option 1: Lambda Extension Layer (Zero Code Changes, Recommended)

Build and publish a Lambda Layer, then reconfigure your function. No changes to your application code.

# 1. Build the layer
mkdir -p senzor-layer/nodejs && cd senzor-layer/nodejs
npm init -y && npm install @senzops/apm-node
cd .. && zip -r senzor-apm-layer.zip nodejs/

# 2. Publish
aws lambda publish-layer-version \
  --layer-name senzor-apm-node \
  --zip-file fileb://senzor-apm-layer.zip \
  --compatible-runtimes nodejs18.x nodejs20.x nodejs22.x

# 3. Attach to function and configure
aws lambda update-function-configuration \
  --function-name my-function \
  --layers <LAYER_ARN> \
  --handler @senzops/apm-node/dist/lambda-handler.handler \
  --environment Variables="{ \
    SENZOR_API_KEY=sz_apm_xxx, \
    SENZOR_LAMBDA_HANDLER=index.handler, \
    NODE_OPTIONS=--require @senzops/apm-node/register \
  }"

The auto-handler wrapper reads SENZOR_LAMBDA_HANDLER to load your original handler, wraps it with full APM instrumentation, and re-exports it for Lambda to invoke.

AWS CDK:

const senzorLayer = new lambda.LayerVersion(this, 'SenzorApmLayer', {
  code: lambda.Code.fromAsset(path.join(__dirname, 'senzor-layer')),
  compatibleRuntimes: [lambda.Runtime.NODEJS_18_X, lambda.Runtime.NODEJS_20_X],
  description: 'Senzor APM Lambda Extension Layer',
});

const fn = new lambda.Function(this, 'MyFunction', {
  runtime: lambda.Runtime.NODEJS_20_X,
  handler: '@senzops/apm-node/dist/lambda-handler.handler',
  code: lambda.Code.fromAsset('lambda'),
  layers: [senzorLayer],
  environment: {
    SENZOR_API_KEY: 'sz_apm_xxx',
    SENZOR_LAMBDA_HANDLER: 'index.handler',
    NODE_OPTIONS: '--require @senzops/apm-node/register',
  },
});

Also works with SAM, Serverless Framework, Terraform, and the AWS Console. See the wiki for full examples.

Option 2: Code-Level Handler Wrapper

import Senzor from '@senzops/apm-node';

Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });

export const handler = Senzor.wrapLambda(async (event, context) => {
  return { statusCode: 200, body: JSON.stringify({ ok: true }) };
});

What's Captured

Both approaches provide:

  • Cold start detection tagged on the first invocation per container
  • Trigger-type detection: API Gateway v1/v2, ALB, SQS, SNS, DynamoDB Streams, EventBridge, S3, Scheduled events
  • Lambda context extraction: function name, request ID, memory limit, log group, invoked ARN, region, account ID
  • Forced flush before each invocation returns (Lambda freezes the process immediately after)
  • Lambda Extensions API registration for SHUTDOWN lifecycle safety-net flush
  • Auto-optimized settings: runtime metrics disabled, batch size 10, flush on demand only

Background Task Monitoring

Auto-Instrumented Tasks

BullMQ workers and node-cron jobs are captured automatically as task runs with queue delay, retry count, dead-letter detection, and CPU/memory resource metrics.

Manual Task Wrapping

const processPayment = Senzor.wrapTask(
  'process_payment',
  'custom',
  { metadata: { owner: 'billing' } },
  async (invoiceId: string) => {
    await chargeCustomer(invoiceId);
  }
);

await processPayment('inv_123');

Manual Spans

const span = Senzor.startSpan('calculate_invoice', 'function');
try {
  const total = await calculateInvoice(invoiceId);
  span.end({ invoiceId, total }, 200);
} catch (error) {
  span.end({ invoiceId, error: String(error) }, 500);
  throw error;
}

Span types: http, db, function, custom, rpc, messaging, dns, net.


Error Tracking

Automatic capture of uncaughtException, unhandledRejection, process warnings, SIGTERM, and SIGINT with full stack traces, process context, and memory snapshots.

Manual capture:

Senzor.captureException(error, { userId, operation: 'charge' });

Log Correlation

Console logs (log, info, warn, error, debug) are automatically captured and correlated with the active trace or task context. Structured logging libraries (pino, winston, bunyan) get trace/span IDs injected for correlation.


Configuration

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | required | Senzor service API key | | endpoint | string | https://api.senzor.dev | Ingest endpoint | | batchSize | number | 100 | Flush threshold | | flushInterval | number | 10000 | Flush interval (ms) | | flushTimeoutMs | number | 5000 | Per-request timeout (ms) | | maxQueueSize | number | 10000 | Max queued items before drop | | maxSpansPerTrace | number | 500 | Max child spans per trace | | maxAttributeLength | number | 2048 | Max string length for attributes | | maxAttributes | number | 64 | Max attributes per object | | captureHeaders | boolean | false | Capture sanitized request headers | | captureDbStatement | boolean | true | Capture sanitized SQL in spans | | instrumentations | boolean \| string[] | true | Enable/disable specific instrumentations | | frameworkSpans | boolean | true | Capture framework middleware/router spans | | captureMiddlewareSpans | boolean | true | Capture middleware execution spans | | captureRouterSpans | boolean | true | Capture router dispatch spans | | captureLifecycleHookSpans | boolean | true | Capture framework lifecycle hook spans | | autoLogs | boolean | true | Capture and correlate console logs | | runtimeMetrics | boolean | true | Collect runtime metrics (event loop, GC, heap) | | runtimeMetricsInterval | number | 15000 | Runtime metrics collection interval (ms) | | debug | boolean | false | Print SDK diagnostics |

Selective Instrumentation

// Enable only specific instrumentations
Senzor.init({
  apiKey: process.env.SENZOR_API_KEY!,
  instrumentations: ['http', 'fetch', 'pg', 'redis', 'openai'],
});

// Disable all auto-instrumentation (manual APIs only)
Senzor.init({
  apiKey: process.env.SENZOR_API_KEY!,
  instrumentations: false,
});

All instrumentation key names:

http, fetch, undici, express, fastify, koa, nestjs, hapi, restify, connect,
pg, mongo, mongoose, mysql, redis, knex, tedious, cassandra, memcached,
kafka, amqplib, socketio, bullmq, cron,
openai, anthropic, google-genai, azure-openai, cohere, mistral,
aws-sdk, firebase,
pino, winston, bunyan,
grpc, graphql, dns, net,
dataloader, lru-memoizer, generic-pool, fs

Environment Variables

| Variable | Description | |----------|-------------| | SENZOR_API_KEY | Service API key | | SENZOR_ENDPOINT | Ingest endpoint | | SENZOR_DEBUG | true / 1 to enable diagnostics | | SENZOR_AUTO_LOGS | false to disable log capture | | SENZOR_BATCH_SIZE | Batch size | | SENZOR_FLUSH_INTERVAL | Flush interval (ms) | | SENZOR_FLUSH_TIMEOUT_MS | Flush timeout (ms) | | SENZOR_MAX_QUEUE_SIZE | Max queued items | | SENZOR_MAX_SPANS_PER_TRACE | Max spans per trace | | SENZOR_CAPTURE_HEADERS | true to capture headers | | SENZOR_CAPTURE_DB_STATEMENT | false for restrictive SQL | | SENZOR_FRAMEWORK_SPANS | false to disable framework spans | | SENZOR_CAPTURE_MIDDLEWARE_SPANS | false to disable middleware spans | | SENZOR_CAPTURE_ROUTER_SPANS | false to disable router spans | | SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS | false to disable lifecycle spans | | SENZOR_RUNTIME_METRICS | false to disable runtime metrics | | SENZOR_RUNTIME_METRICS_INTERVAL | Collection interval (ms) |

Alternative API key variables: SENZOR_APM_API_KEY, SENZOR_SERVICE_API_KEY. Alternative endpoint variables: SENZOR_APM_ENDPOINT.


Distributed Tracing

The SDK automatically propagates trace context on outgoing HTTP calls:

traceparent: 00-{traceId}-{spanId}-01
x-senzor-trace-id: {traceId}
x-senzor-parent-span-id: {spanId}

Incoming traceparent headers are parsed to link upstream traces.


Security Defaults

Sensitive fields are automatically redacted from attributes, headers, logs, and error context:

authorization, cookie, set-cookie, password, secret, token, apiKey, x-api-key, accessToken, refreshToken, clientSecret, privateKey

Header capture is disabled by default. SQL statements are normalized to strip literal values.


Public API Reference

Senzor.init(options)                    // Initialize SDK
Senzor.preload(options)                 // Preload instrumentation hooks
Senzor.flush()                          // Force flush queued telemetry
Senzor.track(data)                      // Send a manual trace
Senzor.startSpan(name, type)            // Start a manual span
Senzor.captureException(error, ctx)     // Capture an error

Senzor.wrapTask(name, type, opts, fn)   // Wrap a function as a task
Senzor.startTask(name, type, opts, fn)  // Start a task context

Senzor.requestHandler()                 // Express request middleware
Senzor.errorHandler()                   // Express error middleware
Senzor.fastifyPlugin                    // Fastify plugin
Senzor.wrapNextRoute(handler)           // Next.js App Router wrapper
Senzor.wrapNextPages(handler)           // Next.js Pages Router wrapper
Senzor.wrapH3(handler)                  // H3/Nuxt/Nitro wrapper
Senzor.nitroPlugin                      // Nitro plugin (Cloudflare Workers)
Senzor.worker(handler)                  // Cloudflare Workers wrapper
Senzor.wrapLambda(handler)              // AWS Lambda wrapper

Requirements

  • Node.js >= 18.0.0 (or Bun >= 1.0.0)
  • A Senzor service API key
  • Network access to the Senzor ingest endpoint

License

MIT