@senzops/apm-node 1.2.8 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +527 -398
  3. package/dist/index.d.mts +5 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.global.js +1 -1
  6. package/dist/index.global.js.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/lambda-handler.d.mts +13 -0
  12. package/dist/lambda-handler.d.ts +13 -0
  13. package/dist/lambda-handler.js +2 -0
  14. package/dist/lambda-handler.js.map +1 -0
  15. package/dist/lambda-handler.mjs +2 -0
  16. package/dist/lambda-handler.mjs.map +1 -0
  17. package/dist/register.js +1 -1
  18. package/dist/register.js.map +1 -1
  19. package/dist/register.mjs +1 -1
  20. package/dist/register.mjs.map +1 -1
  21. package/package.json +6 -1
  22. package/src/core/client.ts +57 -0
  23. package/src/core/transport.ts +20 -3
  24. package/src/core/types.ts +5 -1
  25. package/src/index.ts +4 -0
  26. package/src/instrumentation/amqplib.ts +371 -0
  27. package/src/instrumentation/anthropic.ts +245 -0
  28. package/src/instrumentation/aws-sdk.ts +403 -0
  29. package/src/instrumentation/azure-openai.ts +177 -0
  30. package/src/instrumentation/bunyan.ts +93 -0
  31. package/src/instrumentation/cassandra.ts +367 -0
  32. package/src/instrumentation/cohere.ts +227 -0
  33. package/src/instrumentation/connect.ts +200 -0
  34. package/src/instrumentation/dataloader.ts +291 -0
  35. package/src/instrumentation/dns.ts +220 -0
  36. package/src/instrumentation/firebase.ts +445 -0
  37. package/src/instrumentation/fs.ts +260 -0
  38. package/src/instrumentation/generic-pool.ts +317 -0
  39. package/src/instrumentation/google-genai.ts +426 -0
  40. package/src/instrumentation/graphql.ts +434 -0
  41. package/src/instrumentation/grpc.ts +666 -0
  42. package/src/instrumentation/hapi.ts +257 -0
  43. package/src/instrumentation/kafka.ts +360 -0
  44. package/src/instrumentation/knex.ts +249 -0
  45. package/src/instrumentation/lru-memoizer.ts +175 -0
  46. package/src/instrumentation/memcached.ts +190 -0
  47. package/src/instrumentation/mistral.ts +254 -0
  48. package/src/instrumentation/nestjs.ts +243 -0
  49. package/src/instrumentation/net.ts +171 -0
  50. package/src/instrumentation/openai.ts +281 -0
  51. package/src/instrumentation/pino.ts +170 -0
  52. package/src/instrumentation/restify.ts +213 -0
  53. package/src/instrumentation/runtime.ts +352 -0
  54. package/src/instrumentation/socketio.ts +272 -0
  55. package/src/instrumentation/tedious.ts +509 -0
  56. package/src/instrumentation/winston.ts +149 -0
  57. package/src/lambda-handler.ts +262 -0
  58. package/src/register.ts +22 -3
  59. package/src/wrappers/lambda.ts +417 -0
  60. package/tsup.config.ts +4 -4
  61. package/wiki.md +1693 -852
package/README.md CHANGED
@@ -1,398 +1,527 @@
1
- # @senzops/apm-node
2
-
3
- Official Node.js SDK for Senzor APM.
4
-
5
- `@senzops/apm-node` captures application traces, spans, errors, logs, and task runs from Node.js API services and sends them to Senzor using the Senzor ingestion format. It is designed to be used directly instead of OpenTelemetry in Senzor-instrumented Node services.
6
-
7
- The SDK has two supported modes:
8
-
9
- - Production auto-instrumentation through a preload entrypoint.
10
- - Explicit framework wrappers and manual APIs for environments where preload is not available.
11
-
12
- ## Installation
13
-
14
- ```sh
15
- npm install @senzops/apm-node
16
- ```
17
-
18
- ```sh
19
- yarn add @senzops/apm-node
20
- ```
21
-
22
- ```sh
23
- pnpm add @senzops/apm-node
24
- ```
25
-
26
- ## Requirements
27
-
28
- - Node.js `18.0.0` or newer.
29
- - A Senzor service API key.
30
- - Network access from the application runtime to the Senzor ingest endpoint.
31
-
32
- ## Recommended Production Setup
33
-
34
- Use preload mode so Senzor can patch Node and common libraries before your application imports them.
35
-
36
- ```sh
37
- SENZOR_API_KEY=sz_apm_your_key_here node -r @senzops/apm-node/register server.js
38
- ```
39
-
40
- For ESM applications:
41
-
42
- ```sh
43
- SENZOR_API_KEY=sz_apm_your_key_here node --import @senzops/apm-node/register server.mjs
44
- ```
45
-
46
- With preload enabled, the SDK can automatically capture inbound HTTP requests for common Node frameworks because it instruments the underlying `http` and `https` server lifecycle.
47
-
48
- ## Programmatic Setup
49
-
50
- If preload mode is not possible, initialize Senzor as early as possible in your application entrypoint.
51
-
52
- ```js
53
- const Senzor = require('@senzops/apm-node').default;
54
-
55
- Senzor.init({
56
- apiKey: 'sz_apm_your_key_here',
57
- endpoint: 'https://api.senzor.dev',
58
- batchSize: 100,
59
- flushInterval: 10000
60
- });
61
- ```
62
-
63
- For TypeScript or ESM:
64
-
65
- ```ts
66
- import Senzor from '@senzops/apm-node';
67
-
68
- Senzor.init({
69
- apiKey: process.env.SENZOR_API_KEY!
70
- });
71
- ```
72
-
73
- ## What Gets Captured
74
-
75
- The SDK captures these signals using the Senzor ingestion format:
76
-
77
- - APM traces for inbound API requests.
78
- - Child spans for outgoing HTTP calls, database operations, cache calls, and custom work.
79
- - Errors with trace or task context.
80
- - Console logs correlated with the active trace or task.
81
- - Background task runs for queues and scheduled jobs.
82
-
83
- Current native auto-instrumentation coverage:
84
-
85
- | Area | Libraries and runtimes |
86
- | --- | --- |
87
- | Inbound HTTP | Node `http`, Node `https`, Express, NestJS, Fastify, Koa, H3, Nuxt/Nitro, Restify, Hapi-style services through Node server capture |
88
- | Outbound HTTP | `http`, `https`, `fetch`, `undici` |
89
- | Databases | `pg`, `mongodb`, `mongoose`, `mysql`, `mysql2` |
90
- | Cache | `redis`, `ioredis` |
91
- | Jobs | `bullmq`, `node-cron` |
92
- | Logs | `console.log`, `console.info`, `console.warn`, `console.error`, `console.debug` |
93
- | Errors | `uncaughtException`, `unhandledRejection`, process warnings, manual captured exceptions |
94
-
95
- ## Express Example
96
-
97
- Preload mode is preferred:
98
-
99
- ```sh
100
- SENZOR_API_KEY=sz_apm_your_key_here node -r @senzops/apm-node/register app.js
101
- ```
102
-
103
- You can still use the Express middleware to refine route detection and capture Express error objects:
104
-
105
- ```js
106
- const express = require('express');
107
- const Senzor = require('@senzops/apm-node').default;
108
-
109
- Senzor.init({
110
- apiKey: process.env.SENZOR_API_KEY
111
- });
112
-
113
- const app = express();
114
-
115
- app.use(Senzor.requestHandler());
116
-
117
- app.get('/users/:id', async (req, res) => {
118
- res.json({ id: req.params.id });
119
- });
120
-
121
- app.use(Senzor.errorHandler());
122
-
123
- app.listen(3000);
124
- ```
125
-
126
- ## Fastify Example
127
-
128
- ```ts
129
- import Fastify from 'fastify';
130
- import Senzor from '@senzops/apm-node';
131
-
132
- const fastify = Fastify();
133
-
134
- fastify.register(Senzor.fastifyPlugin, {
135
- apiKey: process.env.SENZOR_API_KEY!
136
- });
137
-
138
- fastify.get('/health', async () => ({ ok: true }));
139
-
140
- await fastify.listen({ port: 3000 });
141
- ```
142
-
143
- ## Next.js Example
144
-
145
- App Router:
146
-
147
- ```ts
148
- import Senzor from '@senzops/apm-node';
149
-
150
- Senzor.init({
151
- apiKey: process.env.SENZOR_API_KEY!
152
- });
153
-
154
- export const GET = Senzor.wrapNextRoute(async () => {
155
- return Response.json({ ok: true });
156
- });
157
- ```
158
-
159
- Pages Router:
160
-
161
- ```ts
162
- import Senzor from '@senzops/apm-node';
163
-
164
- Senzor.init({
165
- apiKey: process.env.SENZOR_API_KEY!
166
- });
167
-
168
- export default Senzor.wrapNextPages(async function handler(req, res) {
169
- res.status(200).json({ ok: true });
170
- });
171
- ```
172
-
173
- In serverless runtimes, flush before the function exits when you need deterministic delivery:
174
-
175
- ```ts
176
- await Senzor.flush();
177
- ```
178
-
179
- ## Manual Spans
180
-
181
- Use manual spans for business operations that are not covered by auto-instrumentation.
182
-
183
- ```ts
184
- const span = Senzor.startSpan('calculate_invoice_total', 'function');
185
-
186
- try {
187
- const total = await calculateInvoiceTotal(invoiceId);
188
- span.end({ invoiceId, total }, 200);
189
- return total;
190
- } catch (error) {
191
- span.end({ invoiceId, error: String(error) }, 500);
192
- Senzor.captureException(error, { invoiceId });
193
- throw error;
194
- }
195
- ```
196
-
197
- ## Background Tasks
198
-
199
- ```ts
200
- const sendInvoiceEmail = Senzor.wrapTask(
201
- 'send_invoice_email',
202
- 'custom',
203
- { metadata: { owner: 'billing' } },
204
- async (invoiceId: string) => {
205
- await sendEmail(invoiceId);
206
- }
207
- );
208
-
209
- await sendInvoiceEmail('inv_123');
210
- ```
211
-
212
- Auto-instrumented task integrations:
213
-
214
- - BullMQ workers.
215
- - node-cron scheduled jobs.
216
-
217
- ## Configuration
218
-
219
- | Option | Type | Default | Description |
220
- | --- | --- | --- | --- |
221
- | `apiKey` | `string` | Required for sending data | Senzor service API key. |
222
- | `endpoint` | `string` | `https://api.senzor.dev/api/ingest/apm` | Senzor ingest endpoint or base URL. |
223
- | `batchSize` | `number` | `100` | Flush when this many queued telemetry items are collected. |
224
- | `flushInterval` | `number` | `10000` | Flush interval in milliseconds. |
225
- | `flushTimeoutMs` | `number` | `5000` | Timeout for a single ingest request. |
226
- | `maxQueueSize` | `number` | `10000` | Maximum queued items per queue before old items are dropped. |
227
- | `maxSpansPerTrace` | `number` | `500` | Maximum child spans retained for a single trace or task. |
228
- | `maxAttributeLength` | `number` | `2048` | Maximum string length for attributes and metadata values. |
229
- | `maxAttributes` | `number` | `64` | Maximum number of attributes retained per object. |
230
- | `captureHeaders` | `boolean` | `false` | Capture sanitized request headers in trace metadata. |
231
- | `captureDbStatement` | `boolean` | SDK sanitizes SQL by default | Controls how much SQL statement text is retained. |
232
- | `instrumentations` | `boolean \| string[]` | `true` | Disable all instrumentation with `false`, or enable only named integrations. |
233
- | `frameworkSpans` | `boolean` | `true` | Capture framework middleware, router, handler, and lifecycle spans. |
234
- | `captureMiddlewareSpans` | `boolean` | `true` | Capture middleware spans for supported frameworks. |
235
- | `captureRouterSpans` | `boolean` | `true` | Capture router/route-dispatch spans. |
236
- | `captureLifecycleHookSpans` | `boolean` | `true` | Capture lifecycle hook spans such as Fastify hooks. |
237
- | `ignoreFrameworkSpanTypes` | `string[]` | `[]` | Skip selected framework span types such as `middleware` or `router`. |
238
- | `autoLogs` | `boolean` | `true` | Capture console logs and correlate them with active traces or tasks. |
239
- | `debug` | `boolean` | `false` | Print SDK diagnostics. |
240
-
241
- Named instrumentation values include:
242
-
243
- ```ts
244
- [
245
- 'http',
246
- 'express',
247
- 'fastify',
248
- 'koa',
249
- 'fetch',
250
- 'undici',
251
- 'mongo',
252
- 'mongoose',
253
- 'pg',
254
- 'mysql',
255
- 'redis',
256
- 'bullmq',
257
- 'cron'
258
- ]
259
- ```
260
-
261
- ## Environment Variables
262
-
263
- The preload entrypoint reads these environment variables:
264
-
265
- | Variable | Description |
266
- | --- | --- |
267
- | `SENZOR_API_KEY` | Service API key. |
268
- | `SENZOR_APM_API_KEY` | Alternative API key variable. |
269
- | `SENZOR_SERVICE_API_KEY` | Alternative API key variable. |
270
- | `SENZOR_ENDPOINT` | Ingest endpoint or base URL. |
271
- | `SENZOR_APM_ENDPOINT` | Alternative endpoint variable. |
272
- | `SENZOR_DEBUG` | Set to `true` or `1` to enable SDK diagnostics. |
273
- | `SENZOR_AUTO_LOGS` | Set to `false` to disable console log capture. |
274
- | `SENZOR_BATCH_SIZE` | Batch size. |
275
- | `SENZOR_FLUSH_INTERVAL` | Flush interval in milliseconds. |
276
- | `SENZOR_FLUSH_TIMEOUT_MS` | Flush timeout in milliseconds. |
277
- | `SENZOR_MAX_QUEUE_SIZE` | Maximum queued telemetry items per queue. |
278
- | `SENZOR_MAX_SPANS_PER_TRACE` | Maximum spans retained per trace. |
279
- | `SENZOR_CAPTURE_HEADERS` | Set to `true` to capture sanitized headers. |
280
- | `SENZOR_CAPTURE_DB_STATEMENT` | Set to `false` for more restrictive SQL metadata. |
281
- | `SENZOR_FRAMEWORK_SPANS` | Set to `false` to disable framework execution spans. |
282
- | `SENZOR_CAPTURE_MIDDLEWARE_SPANS` | Set to `false` to disable middleware spans. |
283
- | `SENZOR_CAPTURE_ROUTER_SPANS` | Set to `false` to disable router spans. |
284
- | `SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS` | Set to `false` to disable lifecycle hook spans. |
285
-
286
- ## Ingestion Payload Shape
287
-
288
- The SDK sends APM data to `/api/ingest/apm`:
289
-
290
- ```json
291
- {
292
- "traces": [
293
- {
294
- "traceId": "f3b2c2c9c70443f5a4b7f0ff6d5b9a17",
295
- "method": "GET",
296
- "route": "/users/:id",
297
- "path": "/users/123?include=roles",
298
- "status": 200,
299
- "duration": 42.81,
300
- "ip": "203.0.113.10",
301
- "userAgent": "Mozilla/5.0",
302
- "timestamp": "2026-05-16T15:30:00.000Z",
303
- "spans": [
304
- {
305
- "spanId": "9d8a4d5f17e24d2a",
306
- "parentSpanId": "91f6c551d5a2403f",
307
- "name": "Postgres SELECT",
308
- "type": "db",
309
- "startTime": 4.22,
310
- "duration": 12.45,
311
- "status": 0,
312
- "meta": {
313
- "operation": "SELECT",
314
- "db.system.name": "postgresql",
315
- "db.operation.name": "SELECT"
316
- }
317
- }
318
- ]
319
- }
320
- ],
321
- "errors": [],
322
- "logs": []
323
- }
324
- ```
325
-
326
- Task data is sent to `/api/ingest/task`:
327
-
328
- ```json
329
- {
330
- "runs": [
331
- {
332
- "runId": "3917bd35-b1d6-4e23-a1d2-d969e1a7d6a1",
333
- "taskName": "billing:send_invoice_email",
334
- "taskType": "queue",
335
- "status": "success",
336
- "duration": 188.3,
337
- "queueDelay": 92,
338
- "attempts": 1,
339
- "resourceMetrics": {
340
- "memoryDeltaBytes": 1048576,
341
- "cpuUserUs": 12000,
342
- "cpuSystemUs": 3000
343
- },
344
- "spans": [],
345
- "timestamp": "2026-05-16T15:30:00.000Z"
346
- }
347
- ],
348
- "errors": [],
349
- "logs": []
350
- }
351
- ```
352
-
353
- ## Security Defaults
354
-
355
- The SDK redacts common sensitive fields from attributes, headers, errors, and logs:
356
-
357
- - `authorization`
358
- - `cookie`
359
- - `set-cookie`
360
- - `password`
361
- - `secret`
362
- - `token`
363
- - `apiKey`
364
- - `x-api-key`
365
- - `accessToken`
366
- - `refreshToken`
367
- - `clientSecret`
368
- - `privateKey`
369
-
370
- Header capture is disabled by default. SQL metadata is normalized to reduce sensitive values and high-cardinality payloads.
371
-
372
- ## Production Notes
373
-
374
- - Use preload mode whenever possible.
375
- - Initialize the SDK before importing application modules when preload mode is not available.
376
- - Keep `debug` disabled in production unless actively troubleshooting.
377
- - Use `Senzor.flush()` before serverless function exit.
378
- - Keep route names low-cardinality, for example `/users/:id` instead of `/users/123`.
379
- - Do not capture request or response bodies unless your service has a strict data policy and the ingestion backend is prepared for that data.
380
-
381
- ## Public API
382
-
383
- ```ts
384
- Senzor.init(options)
385
- Senzor.preload(options)
386
- Senzor.flush()
387
- Senzor.track(data)
388
- Senzor.startSpan(name, type)
389
- Senzor.captureException(error, context)
390
- Senzor.wrapTask(name, type, options, fn)
391
- Senzor.startTask(name, type, options, fn)
392
- Senzor.requestHandler()
393
- Senzor.errorHandler()
394
- Senzor.wrapNextRoute(handler)
395
- Senzor.wrapNextPages(handler)
396
- Senzor.wrapH3(handler)
397
- Senzor.fastifyPlugin
398
- ```
1
+ # @senzops/apm-node
2
+
3
+ Official Node.js APM SDK for [Senzor](https://senzor.dev). Zero-dependency, production-grade distributed tracing, error tracking, log correlation, background task monitoring, and runtime metrics for Node.js services.
4
+
5
+ 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.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install @senzops/apm-node
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### Option 1: Preload Mode (Recommended)
16
+
17
+ Preload ensures instrumentation hooks install before your application imports any library.
18
+
19
+ ```sh
20
+ # CommonJS
21
+ SENZOR_API_KEY=sz_apm_xxx node -r @senzops/apm-node/register server.js
22
+
23
+ # ESM
24
+ SENZOR_API_KEY=sz_apm_xxx node --import @senzops/apm-node/register server.mjs
25
+ ```
26
+
27
+ ### Option 2: Programmatic Init
28
+
29
+ ```ts
30
+ import Senzor from '@senzops/apm-node';
31
+
32
+ Senzor.init({
33
+ apiKey: process.env.SENZOR_API_KEY!,
34
+ });
35
+ ```
36
+
37
+ Initialize as early as possible, before importing application modules.
38
+
39
+ ---
40
+
41
+ ## Auto-Instrumentation Coverage
42
+
43
+ All instrumentations activate automatically when the corresponding library is imported. No configuration required.
44
+
45
+ ### Web Frameworks & HTTP
46
+
47
+ | # | Library | Instrumentation Key | What's Captured |
48
+ |---|---------|-------------------|-----------------|
49
+ | 1 | Node `http` / `https` | `http` | Inbound requests, outbound calls, distributed trace propagation |
50
+ | 2 | `fetch` (global) | `fetch` | Outbound HTTP, W3C Traceparent propagation |
51
+ | 3 | `undici` | `undici` | Outbound HTTP via Node's native HTTP client |
52
+ | 4 | Express | `express` | Route matching, middleware spans, error capture |
53
+ | 5 | Fastify | `fastify` | Route matching, hook spans, lifecycle spans |
54
+ | 6 | Koa | `koa` | Middleware stack, route detection |
55
+ | 7 | NestJS | `nestjs` | Controller/method resolution, Guards, Interceptors, Pipes |
56
+ | 8 | Hapi | `hapi` | Route handling, request lifecycle |
57
+ | 9 | Restify | `restify` | Route matching, handler chain spans |
58
+ | 10 | Connect | `connect` | Middleware stack instrumentation |
59
+
60
+ ### Databases
61
+
62
+ | # | Library | Instrumentation Key | What's Captured |
63
+ |---|---------|-------------------|-----------------|
64
+ | 11 | `pg` (PostgreSQL) | `pg` | Queries, prepared statements, row counts, sanitized SQL |
65
+ | 12 | `mongodb` | `mongo` | Collection operations (find, insert, update, delete, aggregate, bulk) |
66
+ | 13 | `mongoose` | `mongoose` | Model operations with model/collection names |
67
+ | 14 | `mysql` / `mysql2` | `mysql` | Queries, sanitized SQL, connection metadata |
68
+ | 15 | `redis` / `ioredis` | `redis` | Commands (GET, SET, HGETALL, etc.), key names |
69
+ | 16 | `knex` | `knex` | Query builder operations, raw queries, transactions |
70
+ | 17 | `tedious` (SQL Server) | `tedious` | T-SQL queries, stored procedures, row counts |
71
+ | 18 | `cassandra-driver` | `cassandra` | CQL queries, batch operations, prepared statements |
72
+ | 19 | `memcached` | `memcached` | get, set, delete, incr/decr, flush operations |
73
+
74
+ ### Messaging & Queues
75
+
76
+ | # | Library | Instrumentation Key | What's Captured |
77
+ |---|---------|-------------------|-----------------|
78
+ | 20 | `kafkajs` | `kafka` | Producer send, consumer message processing, topic/partition |
79
+ | 21 | `amqplib` (RabbitMQ) | `amqplib` | Publish, consume, ack/nack, queue/exchange names |
80
+ | 22 | `socket.io` | `socketio` | Event emit/receive, namespace, room operations |
81
+ | 23 | `bullmq` | `bullmq` | Worker job processing as task runs, queue delay, retries |
82
+ | 24 | `node-cron` | `cron` | Scheduled job execution as task runs |
83
+
84
+ ### AI / LLM SDKs
85
+
86
+ | # | Library | Instrumentation Key | What's Captured |
87
+ |---|---------|-------------------|-----------------|
88
+ | 25 | `openai` | `openai` | Chat completions, embeddings, images, audio, model, token usage |
89
+ | 26 | `@anthropic-ai/sdk` | `anthropic` | Messages, completions, model, input/output tokens, stop reason |
90
+ | 27 | `@google/generative-ai` | `google-genai` | generateContent, chat, embeddings, countTokens, token usage |
91
+ | 28 | `@google-cloud/vertexai` | `google-genai` | Vertex AI generateContent, generateContentStream |
92
+ | 29 | `@azure/openai` | `azure-openai` | Chat, completions, embeddings, images, audio (v1.x API) |
93
+ | 30 | `cohere-ai` | `cohere` | Chat, embed, rerank, classify, summarize, tokenize |
94
+ | 31 | `@mistralai/mistralai` | `mistral` | Chat, FIM, embeddings, model, token usage |
95
+
96
+ All AI instrumentations follow [OTel GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) (`gen_ai.system`, `gen_ai.request.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.response.finish_reason`).
97
+
98
+ ### Cloud & Infrastructure
99
+
100
+ | # | Library | Instrumentation Key | What's Captured |
101
+ |---|---------|-------------------|-----------------|
102
+ | 32 | `@aws-sdk/*` (AWS SDK v3) | `aws-sdk` | All AWS service calls (S3, DynamoDB, SQS, SNS, Lambda, etc.), request ID, region, HTTP status |
103
+ | 33 | AWS Bedrock Runtime | `aws-sdk` | Model invocations with GenAI attributes (tokens, model ID, finish reason) |
104
+ | 34 | Firebase Admin (Firestore) | `firebase` | Document CRUD, collection queries, transactions, batch commits |
105
+ | 35 | Firebase Admin (Auth) | `firebase` | User management, token verification, session cookies (16 methods) |
106
+ | 36 | Firebase Admin (FCM) | `firebase` | Push notification delivery, multicast, topic operations (9 methods) |
107
+
108
+ ### Logging Libraries
109
+
110
+ | # | Library | Instrumentation Key | What's Captured |
111
+ |---|---------|-------------------|-----------------|
112
+ | 37 | `pino` | `pino` | Trace/span ID injection into structured log output |
113
+ | 38 | `winston` | `winston` | Trace/span ID injection into transport output |
114
+ | 39 | `bunyan` | `bunyan` | Trace/span ID injection into log records |
115
+
116
+ ### RPC & Network
117
+
118
+ | # | Library | Instrumentation Key | What's Captured |
119
+ |---|---------|-------------------|-----------------|
120
+ | 40 | `@grpc/grpc-js` | `grpc` | Unary/streaming calls, service/method, status codes, metadata propagation |
121
+ | 41 | `graphql` | `graphql` | Resolvers, operation name/type, field paths, errors |
122
+ | 42 | Node `dns` | `dns` | DNS lookups, resolve calls, hostname, record types |
123
+ | 43 | Node `net` | `net` | TCP socket connections, data transfer, connection timing |
124
+
125
+ ### Utilities
126
+
127
+ | # | Library | Instrumentation Key | What's Captured |
128
+ |---|---------|-------------------|-----------------|
129
+ | 44 | `dataloader` | `dataloader` | Batch load calls, batch size, cache hits |
130
+ | 45 | `lru-memoizer` | `lru-memoizer` | Memoized function calls, cache hit/miss |
131
+ | 46 | `generic-pool` | `generic-pool` | Pool acquire/release, pool size, pending count |
132
+ | 47 | Node `fs` | `fs` | File system reads, writes, stats, directory operations |
133
+
134
+ ### Runtime Metrics
135
+
136
+ Collected every 15 seconds (configurable) and sent alongside trace data:
137
+
138
+ - **Event Loop**: lag (p50, p99, max), utilization (ELU)
139
+ - **Garbage Collection**: duration by GC type (minor, major, incremental, weakcb)
140
+ - **Memory**: heap used/total, RSS, external, array buffers
141
+ - **Active Handles & Requests**: open file descriptors, active network connections
142
+
143
+ ---
144
+
145
+ ## Framework Wrappers
146
+
147
+ ### Express
148
+
149
+ ```ts
150
+ import Senzor from '@senzops/apm-node';
151
+
152
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
153
+
154
+ const app = express();
155
+ app.use(Senzor.requestHandler()); // First middleware
156
+ app.get('/users/:id', handler);
157
+ app.use(Senzor.errorHandler()); // Last middleware
158
+ app.listen(3000);
159
+ ```
160
+
161
+ ### Fastify
162
+
163
+ ```ts
164
+ import Senzor from '@senzops/apm-node';
165
+
166
+ fastify.register(Senzor.fastifyPlugin, {
167
+ apiKey: process.env.SENZOR_API_KEY!,
168
+ });
169
+ ```
170
+
171
+ ### NestJS
172
+
173
+ ```ts
174
+ import Senzor from '@senzops/apm-node';
175
+
176
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
177
+
178
+ async function bootstrap() {
179
+ const app = await NestFactory.create(AppModule);
180
+ app.use(Senzor.requestHandler());
181
+ await app.listen(3000);
182
+ }
183
+ ```
184
+
185
+ ### Next.js (App Router)
186
+
187
+ ```ts
188
+ import Senzor from '@senzops/apm-node';
189
+
190
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
191
+
192
+ export const GET = Senzor.wrapNextRoute(async (req) => {
193
+ return Response.json({ ok: true });
194
+ });
195
+ ```
196
+
197
+ ### Next.js (Pages Router)
198
+
199
+ ```ts
200
+ import Senzor from '@senzops/apm-node';
201
+
202
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
203
+
204
+ export default Senzor.wrapNextPages(async (req, res) => {
205
+ res.status(200).json({ ok: true });
206
+ });
207
+ ```
208
+
209
+ ### H3 / Nuxt / Nitro
210
+
211
+ ```ts
212
+ import Senzor from '@senzops/apm-node';
213
+
214
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
215
+
216
+ export default Senzor.wrapH3(defineEventHandler(async (event) => {
217
+ return { ok: true };
218
+ }));
219
+ ```
220
+
221
+ ### Nitro Plugin (Cloudflare Workers)
222
+
223
+ ```ts
224
+ import { Senzor } from '@senzops/apm-node';
225
+
226
+ export default defineNitroPlugin((nitroApp) => {
227
+ Senzor.init({ apiKey: '<YOUR_APM_KEY>' });
228
+ Senzor.nitroPlugin(nitroApp);
229
+ });
230
+ ```
231
+
232
+ ### Cloudflare Workers
233
+
234
+ ```ts
235
+ import Senzor from '@senzops/apm-node';
236
+
237
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
238
+
239
+ export default {
240
+ fetch: Senzor.worker(async (request, env, ctx) => {
241
+ return new Response('OK');
242
+ }),
243
+ };
244
+ ```
245
+
246
+ ### AWS Lambda
247
+
248
+ Three deployment options, from zero-code to code-level:
249
+
250
+ #### Option 1: Lambda Extension Layer (Zero Code Changes, Recommended)
251
+
252
+ Build and publish a Lambda Layer, then reconfigure your function. No changes to your application code.
253
+
254
+ ```sh
255
+ # 1. Build the layer
256
+ mkdir -p senzor-layer/nodejs && cd senzor-layer/nodejs
257
+ npm init -y && npm install @senzops/apm-node
258
+ cd .. && zip -r senzor-apm-layer.zip nodejs/
259
+
260
+ # 2. Publish
261
+ aws lambda publish-layer-version \
262
+ --layer-name senzor-apm-node \
263
+ --zip-file fileb://senzor-apm-layer.zip \
264
+ --compatible-runtimes nodejs18.x nodejs20.x nodejs22.x
265
+
266
+ # 3. Attach to function and configure
267
+ aws lambda update-function-configuration \
268
+ --function-name my-function \
269
+ --layers <LAYER_ARN> \
270
+ --handler @senzops/apm-node/dist/lambda-handler.handler \
271
+ --environment Variables="{ \
272
+ SENZOR_API_KEY=sz_apm_xxx, \
273
+ SENZOR_LAMBDA_HANDLER=index.handler, \
274
+ NODE_OPTIONS=--require @senzops/apm-node/register \
275
+ }"
276
+ ```
277
+
278
+ 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.
279
+
280
+ **AWS CDK:**
281
+
282
+ ```ts
283
+ const senzorLayer = new lambda.LayerVersion(this, 'SenzorApmLayer', {
284
+ code: lambda.Code.fromAsset(path.join(__dirname, 'senzor-layer')),
285
+ compatibleRuntimes: [lambda.Runtime.NODEJS_18_X, lambda.Runtime.NODEJS_20_X],
286
+ description: 'Senzor APM Lambda Extension Layer',
287
+ });
288
+
289
+ const fn = new lambda.Function(this, 'MyFunction', {
290
+ runtime: lambda.Runtime.NODEJS_20_X,
291
+ handler: '@senzops/apm-node/dist/lambda-handler.handler',
292
+ code: lambda.Code.fromAsset('lambda'),
293
+ layers: [senzorLayer],
294
+ environment: {
295
+ SENZOR_API_KEY: 'sz_apm_xxx',
296
+ SENZOR_LAMBDA_HANDLER: 'index.handler',
297
+ NODE_OPTIONS: '--require @senzops/apm-node/register',
298
+ },
299
+ });
300
+ ```
301
+
302
+ Also works with SAM, Serverless Framework, Terraform, and the AWS Console. See the [wiki](wiki.md#4-aws-lambda-integration) for full examples.
303
+
304
+ #### Option 2: Code-Level Handler Wrapper
305
+
306
+ ```ts
307
+ import Senzor from '@senzops/apm-node';
308
+
309
+ Senzor.init({ apiKey: process.env.SENZOR_API_KEY! });
310
+
311
+ export const handler = Senzor.wrapLambda(async (event, context) => {
312
+ return { statusCode: 200, body: JSON.stringify({ ok: true }) };
313
+ });
314
+ ```
315
+
316
+ #### What's Captured
317
+
318
+ Both approaches provide:
319
+ - **Cold start detection** tagged on the first invocation per container
320
+ - **Trigger-type detection**: API Gateway v1/v2, ALB, SQS, SNS, DynamoDB Streams, EventBridge, S3, Scheduled events
321
+ - **Lambda context extraction**: function name, request ID, memory limit, log group, invoked ARN, region, account ID
322
+ - **Forced flush** before each invocation returns (Lambda freezes the process immediately after)
323
+ - **Lambda Extensions API** registration for SHUTDOWN lifecycle safety-net flush
324
+ - **Auto-optimized settings**: runtime metrics disabled, batch size 10, flush on demand only
325
+
326
+ ---
327
+
328
+ ## Background Task Monitoring
329
+
330
+ ### Auto-Instrumented Tasks
331
+
332
+ 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.
333
+
334
+ ### Manual Task Wrapping
335
+
336
+ ```ts
337
+ const processPayment = Senzor.wrapTask(
338
+ 'process_payment',
339
+ 'custom',
340
+ { metadata: { owner: 'billing' } },
341
+ async (invoiceId: string) => {
342
+ await chargeCustomer(invoiceId);
343
+ }
344
+ );
345
+
346
+ await processPayment('inv_123');
347
+ ```
348
+
349
+ ---
350
+
351
+ ## Manual Spans
352
+
353
+ ```ts
354
+ const span = Senzor.startSpan('calculate_invoice', 'function');
355
+ try {
356
+ const total = await calculateInvoice(invoiceId);
357
+ span.end({ invoiceId, total }, 200);
358
+ } catch (error) {
359
+ span.end({ invoiceId, error: String(error) }, 500);
360
+ throw error;
361
+ }
362
+ ```
363
+
364
+ Span types: `http`, `db`, `function`, `custom`, `rpc`, `messaging`, `dns`, `net`.
365
+
366
+ ---
367
+
368
+ ## Error Tracking
369
+
370
+ Automatic capture of `uncaughtException`, `unhandledRejection`, process warnings, `SIGTERM`, and `SIGINT` with full stack traces, process context, and memory snapshots.
371
+
372
+ Manual capture:
373
+
374
+ ```ts
375
+ Senzor.captureException(error, { userId, operation: 'charge' });
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Log Correlation
381
+
382
+ 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.
383
+
384
+ ---
385
+
386
+ ## Configuration
387
+
388
+ | Option | Type | Default | Description |
389
+ |--------|------|---------|-------------|
390
+ | `apiKey` | `string` | **required** | Senzor service API key |
391
+ | `endpoint` | `string` | `https://api.senzor.dev` | Ingest endpoint |
392
+ | `batchSize` | `number` | `100` | Flush threshold |
393
+ | `flushInterval` | `number` | `10000` | Flush interval (ms) |
394
+ | `flushTimeoutMs` | `number` | `5000` | Per-request timeout (ms) |
395
+ | `maxQueueSize` | `number` | `10000` | Max queued items before drop |
396
+ | `maxSpansPerTrace` | `number` | `500` | Max child spans per trace |
397
+ | `maxAttributeLength` | `number` | `2048` | Max string length for attributes |
398
+ | `maxAttributes` | `number` | `64` | Max attributes per object |
399
+ | `captureHeaders` | `boolean` | `false` | Capture sanitized request headers |
400
+ | `captureDbStatement` | `boolean` | `true` | Capture sanitized SQL in spans |
401
+ | `instrumentations` | `boolean \| string[]` | `true` | Enable/disable specific instrumentations |
402
+ | `frameworkSpans` | `boolean` | `true` | Capture framework middleware/router spans |
403
+ | `captureMiddlewareSpans` | `boolean` | `true` | Capture middleware execution spans |
404
+ | `captureRouterSpans` | `boolean` | `true` | Capture router dispatch spans |
405
+ | `captureLifecycleHookSpans` | `boolean` | `true` | Capture framework lifecycle hook spans |
406
+ | `autoLogs` | `boolean` | `true` | Capture and correlate console logs |
407
+ | `runtimeMetrics` | `boolean` | `true` | Collect runtime metrics (event loop, GC, heap) |
408
+ | `runtimeMetricsInterval` | `number` | `15000` | Runtime metrics collection interval (ms) |
409
+ | `debug` | `boolean` | `false` | Print SDK diagnostics |
410
+
411
+ ### Selective Instrumentation
412
+
413
+ ```ts
414
+ // Enable only specific instrumentations
415
+ Senzor.init({
416
+ apiKey: process.env.SENZOR_API_KEY!,
417
+ instrumentations: ['http', 'fetch', 'pg', 'redis', 'openai'],
418
+ });
419
+
420
+ // Disable all auto-instrumentation (manual APIs only)
421
+ Senzor.init({
422
+ apiKey: process.env.SENZOR_API_KEY!,
423
+ instrumentations: false,
424
+ });
425
+ ```
426
+
427
+ All instrumentation key names:
428
+
429
+ ```
430
+ http, fetch, undici, express, fastify, koa, nestjs, hapi, restify, connect,
431
+ pg, mongo, mongoose, mysql, redis, knex, tedious, cassandra, memcached,
432
+ kafka, amqplib, socketio, bullmq, cron,
433
+ openai, anthropic, google-genai, azure-openai, cohere, mistral,
434
+ aws-sdk, firebase,
435
+ pino, winston, bunyan,
436
+ grpc, graphql, dns, net,
437
+ dataloader, lru-memoizer, generic-pool, fs
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Environment Variables
443
+
444
+ | Variable | Description |
445
+ |----------|-------------|
446
+ | `SENZOR_API_KEY` | Service API key |
447
+ | `SENZOR_ENDPOINT` | Ingest endpoint |
448
+ | `SENZOR_DEBUG` | `true` / `1` to enable diagnostics |
449
+ | `SENZOR_AUTO_LOGS` | `false` to disable log capture |
450
+ | `SENZOR_BATCH_SIZE` | Batch size |
451
+ | `SENZOR_FLUSH_INTERVAL` | Flush interval (ms) |
452
+ | `SENZOR_FLUSH_TIMEOUT_MS` | Flush timeout (ms) |
453
+ | `SENZOR_MAX_QUEUE_SIZE` | Max queued items |
454
+ | `SENZOR_MAX_SPANS_PER_TRACE` | Max spans per trace |
455
+ | `SENZOR_CAPTURE_HEADERS` | `true` to capture headers |
456
+ | `SENZOR_CAPTURE_DB_STATEMENT` | `false` for restrictive SQL |
457
+ | `SENZOR_FRAMEWORK_SPANS` | `false` to disable framework spans |
458
+ | `SENZOR_CAPTURE_MIDDLEWARE_SPANS` | `false` to disable middleware spans |
459
+ | `SENZOR_CAPTURE_ROUTER_SPANS` | `false` to disable router spans |
460
+ | `SENZOR_CAPTURE_LIFECYCLE_HOOK_SPANS` | `false` to disable lifecycle spans |
461
+ | `SENZOR_RUNTIME_METRICS` | `false` to disable runtime metrics |
462
+ | `SENZOR_RUNTIME_METRICS_INTERVAL` | Collection interval (ms) |
463
+
464
+ Alternative API key variables: `SENZOR_APM_API_KEY`, `SENZOR_SERVICE_API_KEY`.
465
+ Alternative endpoint variables: `SENZOR_APM_ENDPOINT`.
466
+
467
+ ---
468
+
469
+ ## Distributed Tracing
470
+
471
+ The SDK automatically propagates trace context on outgoing HTTP calls:
472
+
473
+ ```
474
+ traceparent: 00-{traceId}-{spanId}-01
475
+ x-senzor-trace-id: {traceId}
476
+ x-senzor-parent-span-id: {spanId}
477
+ ```
478
+
479
+ Incoming `traceparent` headers are parsed to link upstream traces.
480
+
481
+ ---
482
+
483
+ ## Security Defaults
484
+
485
+ Sensitive fields are automatically redacted from attributes, headers, logs, and error context:
486
+
487
+ `authorization`, `cookie`, `set-cookie`, `password`, `secret`, `token`, `apiKey`, `x-api-key`, `accessToken`, `refreshToken`, `clientSecret`, `privateKey`
488
+
489
+ Header capture is disabled by default. SQL statements are normalized to strip literal values.
490
+
491
+ ---
492
+
493
+ ## Public API Reference
494
+
495
+ ```ts
496
+ Senzor.init(options) // Initialize SDK
497
+ Senzor.preload(options) // Preload instrumentation hooks
498
+ Senzor.flush() // Force flush queued telemetry
499
+ Senzor.track(data) // Send a manual trace
500
+ Senzor.startSpan(name, type) // Start a manual span
501
+ Senzor.captureException(error, ctx) // Capture an error
502
+
503
+ Senzor.wrapTask(name, type, opts, fn) // Wrap a function as a task
504
+ Senzor.startTask(name, type, opts, fn) // Start a task context
505
+
506
+ Senzor.requestHandler() // Express request middleware
507
+ Senzor.errorHandler() // Express error middleware
508
+ Senzor.fastifyPlugin // Fastify plugin
509
+ Senzor.wrapNextRoute(handler) // Next.js App Router wrapper
510
+ Senzor.wrapNextPages(handler) // Next.js Pages Router wrapper
511
+ Senzor.wrapH3(handler) // H3/Nuxt/Nitro wrapper
512
+ Senzor.nitroPlugin // Nitro plugin (Cloudflare Workers)
513
+ Senzor.worker(handler) // Cloudflare Workers wrapper
514
+ Senzor.wrapLambda(handler) // AWS Lambda wrapper
515
+ ```
516
+
517
+ ---
518
+
519
+ ## Requirements
520
+
521
+ - Node.js >= 18.0.0 (or Bun >= 1.0.0)
522
+ - A Senzor service API key
523
+ - Network access to the Senzor ingest endpoint
524
+
525
+ ## License
526
+
527
+ MIT