@senzops/apm-node 1.2.2 → 1.2.3

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 (53) hide show
  1. package/dist/index.global.js +1 -1
  2. package/dist/index.global.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/register.js +1 -1
  8. package/dist/register.js.map +1 -1
  9. package/dist/register.mjs +1 -1
  10. package/dist/register.mjs.map +1 -1
  11. package/package.json +1 -1
  12. package/src/instrumentation/express.ts +12 -2
  13. package/.claude/worktrees/infallible-chatelet-f3fb36/.claude/settings.local.json +0 -9
  14. package/.claude/worktrees/infallible-chatelet-f3fb36/CHANGELOG.md +0 -49
  15. package/.claude/worktrees/infallible-chatelet-f3fb36/README.md +0 -398
  16. package/.claude/worktrees/infallible-chatelet-f3fb36/package-lock.json +0 -1494
  17. package/.claude/worktrees/infallible-chatelet-f3fb36/package.json +0 -42
  18. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/client.ts +0 -451
  19. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/context.ts +0 -48
  20. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/normalizer.ts +0 -44
  21. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/sanitizer.ts +0 -203
  22. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/transport.ts +0 -273
  23. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/types.ts +0 -106
  24. package/.claude/worktrees/infallible-chatelet-f3fb36/src/index.ts +0 -36
  25. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/bullmq.ts +0 -195
  26. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/cron.ts +0 -204
  27. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/express.ts +0 -338
  28. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/fastify.ts +0 -296
  29. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/framework.ts +0 -301
  30. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/hook.ts +0 -134
  31. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/http.ts +0 -530
  32. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/koa.ts +0 -173
  33. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongo.ts +0 -202
  34. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongoose.ts +0 -156
  35. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mysql.ts +0 -169
  36. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/patch.ts +0 -56
  37. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/pg.ts +0 -131
  38. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/redis.ts +0 -109
  39. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/span.ts +0 -73
  40. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/undici.ts +0 -189
  41. package/.claude/worktrees/infallible-chatelet-f3fb36/src/middleware/express.ts +0 -48
  42. package/.claude/worktrees/infallible-chatelet-f3fb36/src/register.ts +0 -58
  43. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/getClientIp.ts +0 -175
  44. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/ids.ts +0 -7
  45. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/internal.ts +0 -1
  46. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/sdkMeta.ts +0 -6
  47. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/traceContext.ts +0 -44
  48. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/fastify.ts +0 -35
  49. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/h3.ts +0 -59
  50. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/next.ts +0 -131
  51. package/.claude/worktrees/infallible-chatelet-f3fb36/tsconfig.json +0 -15
  52. package/.claude/worktrees/infallible-chatelet-f3fb36/tsup.config.ts +0 -21
  53. package/.claude/worktrees/infallible-chatelet-f3fb36/wiki.md +0 -852
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senzops/apm-node",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "Universal APM SDK for Senzor",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -273,6 +273,16 @@ const patchRouteMethodHandlers = (
273
273
  }
274
274
  };
275
275
 
276
+ const getSafeRouter = (app: any) => {
277
+ if (!app) return undefined;
278
+ if (app._router) return app._router;
279
+ try {
280
+ return app.router;
281
+ } catch {
282
+ return undefined;
283
+ }
284
+ };
285
+
276
286
  const patchExpress = (
277
287
  expressModule: any,
278
288
  options?: SenzorOptions
@@ -320,9 +330,9 @@ const patchExpress = (
320
330
  'senzor.express.application.use',
321
331
  (original) =>
322
332
  function patchedExpressApplicationUse(this: any, ...args: any[]) {
323
- const router = this?.router || this?._router;
333
+ const router = getSafeRouter(this);
324
334
  const result = original.apply(this, args);
325
- const activeRouter = this?.router || this?._router || router;
335
+ const activeRouter = getSafeRouter(this) || router;
326
336
  const layer = activeRouter?.stack?.[activeRouter.stack.length - 1];
327
337
  patchLayer(layer, getLayerPath(args), options);
328
338
  return result;
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm install *)",
5
- "Bash(node -e \"const fs = require\\('fs'\\); const content = fs.readFileSync\\('node_modules/@senzops/apm-node/dist/index.mjs', 'utf8'\\); const hasCreateRequire = content.includes\\('createRequire'\\); const hasDynamicRequire = content.includes\\('Dynamic require'\\); console.log\\('createRequire:', hasCreateRequire\\); console.log\\('Dynamic require error:', hasDynamicRequire\\)\")",
6
- "Bash(timeout 8 node --input-type=module -e ' *)"
7
- ]
8
- }
9
- }
@@ -1,49 +0,0 @@
1
- # 1.2.1
2
-
3
- feat: more enriched spans
4
-
5
- # 1.2.0
6
-
7
- feat: enhance package to be like OTEL
8
-
9
- # 1.1.18
10
-
11
- chore: introduce more robust ip extraction
12
-
13
- # 1.1.17
14
-
15
- feat: add logs monitoring
16
-
17
- # 1.1.16
18
-
19
- feat: support traceparent of RUM
20
-
21
- # 1.1.15
22
-
23
- feat: make setupGlobalErrorHandlers more robust and send much refined data
24
-
25
- # 1.1.14
26
-
27
- chore: make task auto instrumentation more robust
28
- fix: ESM module patching
29
- fix: module importing and patching for cron and bullmq auto instrumentation
30
-
31
- # 1.1.12
32
-
33
- feat: add advanced features in task monitoring
34
-
35
- # 1.1.10
36
-
37
- fix: module resolution for task monitoring instrumentations
38
-
39
- # 1.1.9
40
-
41
- feat: add task monitoring
42
-
43
- # 1.1.8
44
-
45
- feat: add error tracking in apm
46
-
47
- # 1.1.7
48
-
49
- stable working code
@@ -1,398 +0,0 @@
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
- ```