Skip to content

Repository files navigation

@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

importSenzorfrom'@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

#LibraryInstrumentation KeyWhat's Captured
1Node http / httpshttpInbound requests, outbound calls, distributed trace propagation
2fetch (global)fetchOutbound HTTP, W3C Traceparent propagation
3undiciundiciOutbound HTTP via Node's native HTTP client
4ExpressexpressRoute matching, middleware spans, error capture
5FastifyfastifyRoute matching, hook spans, lifecycle spans
6KoakoaMiddleware stack, route detection
7NestJSnestjsController/method resolution, Guards, Interceptors, Pipes
8HapihapiRoute handling, request lifecycle
9RestifyrestifyRoute matching, handler chain spans
10ConnectconnectMiddleware stack instrumentation

Databases

#LibraryInstrumentation KeyWhat's Captured
11pg (PostgreSQL)pgQueries, prepared statements, row counts, sanitized SQL
12mongodbmongoCollection operations (find, insert, update, delete, aggregate, bulk)
13mongoosemongooseModel operations with model/collection names
14mysql / mysql2mysqlQueries, sanitized SQL, connection metadata
15redis / ioredisredisCommands (GET, SET, HGETALL, etc.), key names
16knexknexQuery builder operations, raw queries, transactions
17tedious (SQL Server)tediousT-SQL queries, stored procedures, row counts
18cassandra-drivercassandraCQL queries, batch operations, prepared statements
19memcachedmemcachedget, set, delete, incr/decr, flush operations

Messaging & Queues

#LibraryInstrumentation KeyWhat's Captured
20kafkajskafkaProducer send, consumer message processing, topic/partition
21amqplib (RabbitMQ)amqplibPublish, consume, ack/nack, queue/exchange names
22socket.iosocketioEvent emit/receive, namespace, room operations
23bullmqbullmqWorker job processing as task runs, queue delay, retries
24node-croncronScheduled job execution as task runs

AI / LLM SDKs

#LibraryInstrumentation KeyWhat's Captured
25openaiopenaiChat completions, embeddings, images, audio, model, token usage
26@anthropic-ai/sdkanthropicMessages, completions, model, input/output tokens, stop reason
27@google/generative-aigoogle-genaigenerateContent, chat, embeddings, countTokens, token usage
28@google-cloud/vertexaigoogle-genaiVertex AI generateContent, generateContentStream
29@azure/openaiazure-openaiChat, completions, embeddings, images, audio (v1.x API)
30cohere-aicohereChat, embed, rerank, classify, summarize, tokenize
31@mistralai/mistralaimistralChat, FIM, embeddings, model, token usage
32ai (Vercel AI SDK)vercel-aigenerateText/streamText/generateObject/streamObject/embed, all fronted providers, streaming usage
33@langchain/corelangchainChat model invoke across providers, normalized usage_metadata
34groq-sdkgroqChat completions, embeddings, audio, streaming token usage
35ollamaollamaLocal chat/generate/embeddings, prompt_eval_count / eval_count
36@modelcontextprotocol/sdkmcpMCP 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:

awaitSenzor.ai.agent({name: 'researcher'},async()=>{// LLM calls + provider auto-instrumentation here nest under the agentconstdocs=awaitSenzor.ai.tool({name: 'search_docs',args: { q }},()=>searchDocs(q));constdata=awaitSenzor.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-onlySenzor.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

#LibraryInstrumentation KeyWhat's Captured
32@aws-sdk/* (AWS SDK v3)aws-sdkAll AWS service calls (S3, DynamoDB, SQS, SNS, Lambda, etc.), request ID, region, HTTP status
33AWS Bedrock Runtimeaws-sdkModel invocations with GenAI attributes (tokens, model ID, finish reason)
34Firebase Admin (Firestore)firebaseDocument CRUD, collection queries, transactions, batch commits
35Firebase Admin (Auth)firebaseUser management, token verification, session cookies (16 methods)
36Firebase Admin (FCM)firebasePush notification delivery, multicast, topic operations (9 methods)

Logging Libraries

#LibraryInstrumentation KeyWhat's Captured
37pinopinoTrace/span ID injection into structured log output
38winstonwinstonTrace/span ID injection into transport output
39bunyanbunyanTrace/span ID injection into log records

RPC & Network

#LibraryInstrumentation KeyWhat's Captured
40@grpc/grpc-jsgrpcUnary/streaming calls, service/method, status codes, metadata propagation
41graphqlgraphqlResolvers, operation name/type, field paths, errors
42Node dnsdnsDNS lookups, resolve calls, hostname, record types
43Node netnetTCP socket connections, data transfer, connection timing

Utilities

#LibraryInstrumentation KeyWhat's Captured
44dataloaderdataloaderBatch load calls, batch size, cache hits
45lru-memoizerlru-memoizerMemoized function calls, cache hit/miss
46generic-poolgeneric-poolPool acquire/release, pool size, pending count
47Node fsfsFile 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

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});constapp=express();app.use(Senzor.requestHandler());// First middlewareapp.get('/users/:id',handler);app.use(Senzor.errorHandler());// Last middlewareapp.listen(3000);

Fastify

importSenzorfrom'@senzops/apm-node';fastify.register(Senzor.fastifyPlugin,{apiKey: process.env.SENZOR_API_KEY!,});

NestJS

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});asyncfunctionbootstrap(){constapp=awaitNestFactory.create(AppModule);app.use(Senzor.requestHandler());awaitapp.listen(3000);}

Next.js (App Router)

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});exportconstGET=Senzor.wrapNextRoute(async(req)=>{returnResponse.json({ok: true});});

Next.js (Pages Router)

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});exportdefaultSenzor.wrapNextPages(async(req,res)=>{res.status(200).json({ok: true});});

H3 / Nuxt / Nitro

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});exportdefaultSenzor.wrapH3(defineEventHandler(async(event)=>{return{ok: true};}));

Nitro Plugin (Cloudflare Workers)

import{Senzor}from'@senzops/apm-node';exportdefaultdefineNitroPlugin((nitroApp)=>{Senzor.init({apiKey: '<YOUR_APM_KEY>'});Senzor.nitroPlugin(nitroApp);});

Cloudflare Workers

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});exportdefault{fetch: Senzor.worker(async(request,env,ctx)=>{returnnewResponse('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:

constsenzorLayer=newlambda.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',});constfn=newlambda.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

importSenzorfrom'@senzops/apm-node';Senzor.init({apiKey: process.env.SENZOR_API_KEY!});exportconsthandler=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

constprocessPayment=Senzor.wrapTask('process_payment','custom',{metadata: {owner: 'billing'}},async(invoiceId: string)=>{awaitchargeCustomer(invoiceId);});awaitprocessPayment('inv_123');

Manual Spans

constspan=Senzor.startSpan('calculate_invoice','function');try{consttotal=awaitcalculateInvoice(invoiceId);span.end({ invoiceId, total },200);}catch(error){span.end({ invoiceId,error: String(error)},500);throwerror;}

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

OptionTypeDefaultDescription
apiKeystringrequiredSenzor service API key
endpointstringhttps://api.senzor.devIngest endpoint
batchSizenumber100Flush threshold
flushIntervalnumber10000Flush interval (ms)
flushTimeoutMsnumber5000Per-request timeout (ms)
maxQueueSizenumber10000Max queued items before drop
maxSpansPerTracenumber500Max child spans per trace
maxAttributeLengthnumber2048Max string length for attributes
maxAttributesnumber64Max attributes per object
captureHeadersbooleanfalseCapture sanitized request headers
captureDbStatementbooleantrueCapture sanitized SQL in spans
instrumentationsboolean | string[]trueEnable/disable specific instrumentations
frameworkSpansbooleantrueCapture framework middleware/router spans
captureMiddlewareSpansbooleantrueCapture middleware execution spans
captureRouterSpansbooleantrueCapture router dispatch spans
captureLifecycleHookSpansbooleantrueCapture framework lifecycle hook spans
autoLogsbooleantrueCapture and correlate console logs
runtimeMetricsbooleantrueCollect runtime metrics (event loop, GC, heap)
runtimeMetricsIntervalnumber15000Runtime metrics collection interval (ms)
debugbooleanfalsePrint SDK diagnostics

Selective Instrumentation

// Enable only specific instrumentationsSenzor.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

VariableDescription
SENZOR_API_KEYService API key
SENZOR_ENDPOINTIngest endpoint
SENZOR_DEBUGtrue / 1 to enable diagnostics
SENZOR_AUTO_LOGSfalse to disable log capture
SENZOR_BATCH_SIZEBatch size
SENZOR_FLUSH_INTERVALFlush interval (ms)
SENZOR_FLUSH_TIMEOUT_MSFlush timeout (ms)
SENZOR_MAX_QUEUE_SIZEMax queued items
SENZOR_MAX_SPANS_PER_TRACEMax spans per trace
SENZOR_CAPTURE_HEADERStrue to capture headers
SENZOR_CAPTURE_DB_STATEMENTfalse for restrictive SQL
SENZOR_FRAMEWORK_SPANSfalse to disable framework spans
SENZOR_CAPTURE_MIDDLEWARE_SPANSfalse to disable middleware spans
SENZOR_CAPTURE_ROUTER_SPANSfalse to disable router spans
SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANSfalse to disable lifecycle spans
SENZOR_RUNTIME_METRICSfalse to disable runtime metrics
SENZOR_RUNTIME_METRICS_INTERVALCollection 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 SDKSenzor.preload(options)// Preload instrumentation hooksSenzor.flush()// Force flush queued telemetrySenzor.track(data)// Send a manual traceSenzor.startSpan(name,type)// Start a manual spanSenzor.captureException(error,ctx)// Capture an errorSenzor.wrapTask(name,type,opts,fn)// Wrap a function as a taskSenzor.startTask(name,type,opts,fn)// Start a task contextSenzor.requestHandler()// Express request middlewareSenzor.errorHandler()// Express error middlewareSenzor.fastifyPlugin// Fastify pluginSenzor.wrapNextRoute(handler)// Next.js App Router wrapperSenzor.wrapNextPages(handler)// Next.js Pages Router wrapperSenzor.wrapH3(handler)// H3/Nuxt/Nitro wrapperSenzor.nitroPlugin// Nitro plugin (Cloudflare Workers)Senzor.worker(handler)// Cloudflare Workers wrapperSenzor.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

About

Senzor APM SDK for Node.js

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages