@senzops/apm-node 1.2.2 → 1.2.4

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 (57) 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/core/client.ts +172 -167
  13. package/src/core/context.ts +2 -2
  14. package/src/core/types.ts +48 -43
  15. package/src/instrumentation/express.ts +12 -2
  16. package/src/instrumentation/http.ts +1 -1
  17. package/.claude/worktrees/infallible-chatelet-f3fb36/.claude/settings.local.json +0 -9
  18. package/.claude/worktrees/infallible-chatelet-f3fb36/CHANGELOG.md +0 -49
  19. package/.claude/worktrees/infallible-chatelet-f3fb36/README.md +0 -398
  20. package/.claude/worktrees/infallible-chatelet-f3fb36/package-lock.json +0 -1494
  21. package/.claude/worktrees/infallible-chatelet-f3fb36/package.json +0 -42
  22. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/client.ts +0 -451
  23. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/context.ts +0 -48
  24. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/normalizer.ts +0 -44
  25. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/sanitizer.ts +0 -203
  26. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/transport.ts +0 -273
  27. package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/types.ts +0 -106
  28. package/.claude/worktrees/infallible-chatelet-f3fb36/src/index.ts +0 -36
  29. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/bullmq.ts +0 -195
  30. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/cron.ts +0 -204
  31. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/express.ts +0 -338
  32. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/fastify.ts +0 -296
  33. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/framework.ts +0 -301
  34. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/hook.ts +0 -134
  35. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/http.ts +0 -530
  36. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/koa.ts +0 -173
  37. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongo.ts +0 -202
  38. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongoose.ts +0 -156
  39. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mysql.ts +0 -169
  40. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/patch.ts +0 -56
  41. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/pg.ts +0 -131
  42. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/redis.ts +0 -109
  43. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/span.ts +0 -73
  44. package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/undici.ts +0 -189
  45. package/.claude/worktrees/infallible-chatelet-f3fb36/src/middleware/express.ts +0 -48
  46. package/.claude/worktrees/infallible-chatelet-f3fb36/src/register.ts +0 -58
  47. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/getClientIp.ts +0 -175
  48. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/ids.ts +0 -7
  49. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/internal.ts +0 -1
  50. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/sdkMeta.ts +0 -6
  51. package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/traceContext.ts +0 -44
  52. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/fastify.ts +0 -35
  53. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/h3.ts +0 -59
  54. package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/next.ts +0 -131
  55. package/.claude/worktrees/infallible-chatelet-f3fb36/tsconfig.json +0 -15
  56. package/.claude/worktrees/infallible-chatelet-f3fb36/tsup.config.ts +0 -21
  57. 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.4",
4
4
  "description": "Universal APM SDK for Senzor",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -2,23 +2,23 @@ import { Transport } from './transport';
2
2
  import { Context } from './context';
3
3
  import { SenzorOptions, ActiveTrace, TaskRun, SenzorLog } from './types';
4
4
  import { randomUUID } from 'crypto';
5
- import { instrumentHttp, instrumentFetch } from '../instrumentation/http';
6
- import { instrumentExpress } from '../instrumentation/express';
7
- import { instrumentFastify } from '../instrumentation/fastify';
8
- import { instrumentKoa } from '../instrumentation/koa';
9
- import { instrumentMongo } from '../instrumentation/mongo';
10
- import { instrumentPg } from '../instrumentation/pg';
11
- import { instrumentUndici } from '../instrumentation/undici';
12
- import { instrumentRedis } from '../instrumentation/redis';
13
- import { instrumentMysql } from '../instrumentation/mysql';
14
- import { instrumentMongoose } from '../instrumentation/mongoose';
15
- import { instrumentBullMQ } from '../instrumentation/bullmq';
16
- import { instrumentNodeCron } from '../instrumentation/cron';
17
- import { SDK_META } from '../utils/sdkMeta';
18
- import { parseTraceparent } from '../utils/traceContext';
19
- import { generateSpanId, generateTraceId } from '../utils/ids';
20
- import { sanitizeAttributes } from './sanitizer';
21
- import { startCapturedSpan } from '../instrumentation/span';
5
+ import { instrumentHttp, instrumentFetch } from '../instrumentation/http';
6
+ import { instrumentExpress } from '../instrumentation/express';
7
+ import { instrumentFastify } from '../instrumentation/fastify';
8
+ import { instrumentKoa } from '../instrumentation/koa';
9
+ import { instrumentMongo } from '../instrumentation/mongo';
10
+ import { instrumentPg } from '../instrumentation/pg';
11
+ import { instrumentUndici } from '../instrumentation/undici';
12
+ import { instrumentRedis } from '../instrumentation/redis';
13
+ import { instrumentMysql } from '../instrumentation/mysql';
14
+ import { instrumentMongoose } from '../instrumentation/mongoose';
15
+ import { instrumentBullMQ } from '../instrumentation/bullmq';
16
+ import { instrumentNodeCron } from '../instrumentation/cron';
17
+ import { SDK_META } from '../utils/sdkMeta';
18
+ import { parseTraceparent } from '../utils/traceContext';
19
+ import { generateSpanId, generateTraceId } from '../utils/ids';
20
+ import { sanitizeAttributes } from './sanitizer';
21
+ import { startCapturedSpan } from '../instrumentation/span';
22
22
 
23
23
  // Memory-safe JSON stringifier to handle cyclical objects
24
24
  // (like Express 'req' objects) passed into console.log
@@ -33,69 +33,69 @@ const safeStringify = (obj: any): string => {
33
33
  });
34
34
  };
35
35
 
36
- export class SenzorClient {
37
- private transport: Transport | null = null;
38
- private options: SenzorOptions | null = null;
39
- private isInstrumented = false;
40
-
41
- public preload(options: Partial<SenzorOptions> = {}) {
42
- const endpoint = options.endpoint || 'https://api.senzor.dev/api/ingest/apm';
43
- const debug = options.debug || false;
44
-
45
- this.options = {
46
- apiKey: '',
47
- ...this.options,
48
- ...options
49
- };
50
-
51
- this.installNativeInstrumentations(endpoint, debug);
52
- }
53
-
54
- public init(options: SenzorOptions) {
55
- if (!options.apiKey) {
56
- console.warn('[Senzor] API Key missing. SDK disabled.');
57
- return;
58
- }
36
+ export class SenzorClient {
37
+ private transport: Transport | null = null;
38
+ private options: SenzorOptions | null = null;
39
+ private isInstrumented = false;
40
+
41
+ public preload(options: Partial<SenzorOptions> = {}) {
42
+ const endpoint = options.endpoint || 'https://api.senzor.dev/api/ingest/apm';
43
+ const debug = options.debug || false;
44
+
45
+ this.options = {
46
+ apiKey: '',
47
+ ...this.options,
48
+ ...options
49
+ };
50
+
51
+ this.installNativeInstrumentations(endpoint, debug);
52
+ }
53
+
54
+ public init(options: SenzorOptions) {
55
+ if (!options.apiKey) {
56
+ console.warn('[Senzor] API Key missing. SDK disabled.');
57
+ return;
58
+ }
59
59
  this.options = options;
60
60
  const endpoint = options.endpoint || 'https://api.senzor.dev/api/ingest/apm';
61
61
  const debug = options.debug || false;
62
-
63
- this.transport = new Transport({ ...options, endpoint });
64
- this.installNativeInstrumentations(endpoint, debug);
65
- }
66
-
67
- private isInstrumentationEnabled(name: string): boolean {
68
- const setting = this.options?.instrumentations;
69
- if (setting === false) return false;
70
- if (Array.isArray(setting)) return setting.includes(name);
71
- return true;
72
- }
73
-
74
- private installNativeInstrumentations(endpoint: string, debug: boolean) {
75
- if (!this.isInstrumented) {
76
- this.setupGlobalErrorHandlers();
77
- this.setupLogInterception(); // Fire up Auto Log Instrumentation
78
-
79
- try { if (this.isInstrumentationEnabled('http')) instrumentHttp(this, endpoint, this.options || undefined); } catch (e) { }
80
- try { if (this.isInstrumentationEnabled('express')) instrumentExpress(this.options || undefined); } catch (e) { }
81
- try { if (this.isInstrumentationEnabled('fastify')) instrumentFastify(this.options || undefined); } catch (e) { }
82
- try { if (this.isInstrumentationEnabled('koa')) instrumentKoa(this.options || undefined); } catch (e) { }
83
- try { if (this.isInstrumentationEnabled('fetch')) instrumentFetch(endpoint, this.options || undefined); } catch (e) { }
84
- try { if (this.isInstrumentationEnabled('undici')) instrumentUndici(this.options || undefined); } catch (e) { }
85
- try { if (this.isInstrumentationEnabled('mongo')) instrumentMongo(this.options || undefined); } catch (e) { }
86
- try { if (this.isInstrumentationEnabled('mongoose')) instrumentMongoose(this.options || undefined); } catch (e) { }
87
- try { if (this.isInstrumentationEnabled('pg')) instrumentPg(this.options || undefined); } catch (e) { }
88
- try { if (this.isInstrumentationEnabled('mysql')) instrumentMysql(this.options || undefined); } catch (e) { }
89
- try { if (this.isInstrumentationEnabled('redis')) instrumentRedis(this.options || undefined); } catch (e) { }
90
-
91
- // Task Integrations
92
- try { if (this.isInstrumentationEnabled('bullmq')) instrumentBullMQ(this, debug); } catch (e) { }
93
- try { if (this.isInstrumentationEnabled('cron')) instrumentNodeCron(this, debug); } catch (e) { }
94
-
95
- this.isInstrumented = true;
96
- if (debug) console.log('[Senzor] Auto-instrumentation enabled');
97
- }
98
- }
62
+
63
+ this.transport = new Transport({ ...options, endpoint });
64
+ this.installNativeInstrumentations(endpoint, debug);
65
+ }
66
+
67
+ private isInstrumentationEnabled(name: string): boolean {
68
+ const setting = this.options?.instrumentations;
69
+ if (setting === false) return false;
70
+ if (Array.isArray(setting)) return setting.includes(name);
71
+ return true;
72
+ }
73
+
74
+ private installNativeInstrumentations(endpoint: string, debug: boolean) {
75
+ if (!this.isInstrumented) {
76
+ this.setupGlobalErrorHandlers();
77
+ this.setupLogInterception(); // Fire up Auto Log Instrumentation
78
+
79
+ try { if (this.isInstrumentationEnabled('http')) instrumentHttp(this, endpoint, this.options || undefined); } catch (e) { }
80
+ try { if (this.isInstrumentationEnabled('express')) instrumentExpress(this.options || undefined); } catch (e) { }
81
+ try { if (this.isInstrumentationEnabled('fastify')) instrumentFastify(this.options || undefined); } catch (e) { }
82
+ try { if (this.isInstrumentationEnabled('koa')) instrumentKoa(this.options || undefined); } catch (e) { }
83
+ try { if (this.isInstrumentationEnabled('fetch')) instrumentFetch(endpoint, this.options || undefined); } catch (e) { }
84
+ try { if (this.isInstrumentationEnabled('undici')) instrumentUndici(this.options || undefined); } catch (e) { }
85
+ try { if (this.isInstrumentationEnabled('mongo')) instrumentMongo(this.options || undefined); } catch (e) { }
86
+ try { if (this.isInstrumentationEnabled('mongoose')) instrumentMongoose(this.options || undefined); } catch (e) { }
87
+ try { if (this.isInstrumentationEnabled('pg')) instrumentPg(this.options || undefined); } catch (e) { }
88
+ try { if (this.isInstrumentationEnabled('mysql')) instrumentMysql(this.options || undefined); } catch (e) { }
89
+ try { if (this.isInstrumentationEnabled('redis')) instrumentRedis(this.options || undefined); } catch (e) { }
90
+
91
+ // Task Integrations
92
+ try { if (this.isInstrumentationEnabled('bullmq')) instrumentBullMQ(this, debug); } catch (e) { }
93
+ try { if (this.isInstrumentationEnabled('cron')) instrumentNodeCron(this, debug); } catch (e) { }
94
+
95
+ this.isInstrumented = true;
96
+ if (debug) console.log('[Senzor] Auto-instrumentation enabled');
97
+ }
98
+ }
99
99
 
100
100
  // --- Enterprise Auto-Log Interception ---
101
101
  private setupLogInterception() {
@@ -132,13 +132,13 @@ export class SenzorClient {
132
132
  attributes.errorStack = arg.stack;
133
133
  attributes.errorName = arg.name;
134
134
  } else if (typeof arg === 'object' && arg !== null) {
135
- try {
136
- // New Relic Style Destructuring: Merge all object keys into `attributes`
137
- const parsed = JSON.parse(safeStringify(arg));
138
- attributes = { ...attributes, ...sanitizeAttributes(parsed, this.options || undefined) };
139
- } catch (e) {
140
- attributes.unparseableObject = true;
141
- }
135
+ try {
136
+ // New Relic Style Destructuring: Merge all object keys into `attributes`
137
+ const parsed = JSON.parse(safeStringify(arg));
138
+ attributes = { ...attributes, ...sanitizeAttributes(parsed, this.options || undefined) };
139
+ } catch (e) {
140
+ attributes.unparseableObject = true;
141
+ }
142
142
  } else {
143
143
  message += (message ? ' ' : '') + String(arg);
144
144
  }
@@ -254,20 +254,17 @@ export class SenzorClient {
254
254
  process.on('SIGINT', () => safeCapture(new Error('Process received SIGINT'), { type: 'processSignal', signal: 'SIGINT' }));
255
255
  }
256
256
 
257
- public startTrace<T>(data: Partial<ActiveTrace['data']> & { headers?: any }, next: () => T): T {
258
- if (!this.transport) return next();
259
-
260
- const existingTrace = Context.current();
261
- if (existingTrace?.contextType === 'apm') {
262
- existingTrace.data = {
263
- ...existingTrace.data,
264
- ...data
265
- };
266
- return next();
267
- }
268
-
269
- let inheritedTraceId: string | undefined = undefined;
270
- let inheritedParentSpanId: string | undefined = undefined;
257
+ public startTrace<T>(data: Partial<ActiveTrace['data']> & { headers?: any }, next: () => T): T {
258
+ if (!this.transport) return next();
259
+
260
+ const existingTrace = Context.current();
261
+ if (existingTrace?.contextType === 'apm') {
262
+ Object.assign(existingTrace.data, data);
263
+ return next();
264
+ }
265
+
266
+ let inheritedTraceId: string | undefined = undefined;
267
+ let inheritedParentSpanId: string | undefined = undefined;
271
268
 
272
269
  if (data.headers) {
273
270
  const getHeader = (key: string) => {
@@ -290,44 +287,47 @@ export class SenzorClient {
290
287
  }
291
288
  }
292
289
 
293
- const activeTraceId = inheritedTraceId || generateTraceId();
294
- const rootSpanId = generateSpanId();
295
-
296
- const trace: ActiveTrace = {
297
- id: activeTraceId,
298
- contextType: 'apm',
299
- startTime: performance.now(),
300
- rootSpanId,
301
- activeSpanId: rootSpanId,
302
- data: { ...data, parentTraceId: inheritedTraceId, parentSpanId: inheritedParentSpanId, rootSpanId },
303
- spans: [],
304
- maxSpans: this.options?.maxSpansPerTrace ?? 500,
305
- droppedSpans: 0
306
- };
290
+ const activeTraceId = inheritedTraceId || generateTraceId();
291
+ const rootSpanId = generateSpanId();
292
+
293
+ const trace: ActiveTrace = {
294
+ id: activeTraceId,
295
+ contextType: 'apm',
296
+ startTime: performance.now(),
297
+ rootSpanId,
298
+ activeSpanId: rootSpanId,
299
+ data: { ...data, parentTraceId: inheritedTraceId, parentSpanId: inheritedParentSpanId, rootSpanId },
300
+ spans: [],
301
+ maxSpans: this.options?.maxSpansPerTrace ?? 500,
302
+ state: {
303
+ ended: false,
304
+ droppedSpans: 0
305
+ }
306
+ };
307
307
 
308
308
  return Context.run(trace, next);
309
309
  }
310
310
 
311
- public endTrace(status: number, extraData: any = {}) {
312
- const trace = Context.current();
313
- if (!trace || trace.contextType !== 'apm' || !this.transport) return;
314
- if (trace.ended) return;
315
- trace.ended = true;
316
- const duration = performance.now() - trace.startTime;
317
-
318
- const payload = {
319
- traceId: trace.id,
320
- parentTraceId: trace.data.parentTraceId,
321
- parentSpanId: trace.data.parentSpanId,
322
- rootSpanId: trace.rootSpanId,
323
- ...trace.data,
324
- ...extraData,
325
- status,
326
- duration,
327
- spans: trace.spans,
328
- droppedSpans: trace.droppedSpans,
329
- timestamp: new Date().toISOString()
330
- };
311
+ public endTrace(status: number, extraData: any = {}) {
312
+ const trace = Context.current();
313
+ if (!trace || trace.contextType !== 'apm' || !this.transport) return;
314
+ if (trace.state.ended) return;
315
+ trace.state.ended = true;
316
+ const duration = performance.now() - trace.startTime;
317
+
318
+ const payload = {
319
+ traceId: trace.id,
320
+ parentTraceId: trace.data.parentTraceId,
321
+ parentSpanId: trace.data.parentSpanId,
322
+ rootSpanId: trace.rootSpanId,
323
+ ...trace.data,
324
+ ...extraData,
325
+ status,
326
+ duration,
327
+ spans: trace.spans,
328
+ droppedSpans: trace.state.droppedSpans,
329
+ timestamp: new Date().toISOString()
330
+ };
331
331
  this.transport.addTrace(payload);
332
332
  }
333
333
 
@@ -341,25 +341,30 @@ export class SenzorClient {
341
341
  const startMemory = process.memoryUsage ? process.memoryUsage().heapUsed : 0;
342
342
  const startCpu = process.cpuUsage ? process.cpuUsage() : undefined;
343
343
 
344
- const task: ActiveTrace = {
345
- id: randomUUID(),
346
- contextType: 'task',
347
- startTime: performance.now(),
348
- rootSpanId: generateSpanId(),
349
- startMemory,
350
- startCpu,
351
- data: { taskName: name, taskType: type, triggerTraceId, ...options },
352
- spans: [],
353
- maxSpans: this.options?.maxSpansPerTrace ?? 500,
354
- droppedSpans: 0
355
- };
356
- task.activeSpanId = task.rootSpanId;
357
- return Context.run(task, next);
358
- }
344
+ const task: ActiveTrace = {
345
+ id: randomUUID(),
346
+ contextType: 'task',
347
+ startTime: performance.now(),
348
+ rootSpanId: generateSpanId(),
349
+ startMemory,
350
+ startCpu,
351
+ data: { taskName: name, taskType: type, triggerTraceId, ...options },
352
+ spans: [],
353
+ maxSpans: this.options?.maxSpansPerTrace ?? 500,
354
+ state: {
355
+ ended: false,
356
+ droppedSpans: 0
357
+ }
358
+ };
359
+ task.activeSpanId = task.rootSpanId;
360
+ return Context.run(task, next);
361
+ }
359
362
 
360
363
  public endTask(status: 'success' | 'failed', extraMetadata: any = {}) {
361
364
  const task = Context.current();
362
365
  if (!task || task.contextType !== 'task' || !this.transport) return;
366
+ if (task.state.ended) return;
367
+ task.state.ended = true;
363
368
 
364
369
  let resourceMetrics;
365
370
  if (process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
@@ -381,7 +386,7 @@ export class SenzorClient {
381
386
  queueDelay: task.data.queueDelay,
382
387
  attempts: task.data.attempts,
383
388
  isDeadLetter: task.data.isDeadLetter,
384
- metadata: { ...task.data.metadata, ...extraMetadata, droppedSpans: task.droppedSpans },
389
+ metadata: { ...task.data.metadata, ...extraMetadata, droppedSpans: task.state.droppedSpans },
385
390
  resourceMetrics,
386
391
  status,
387
392
  duration: performance.now() - task.startTime,
@@ -420,32 +425,32 @@ export class SenzorClient {
420
425
 
421
426
  const currentTrace = Context.current();
422
427
 
423
- const errPayload = {
424
- errorClass: parsedError.name || 'Error',
425
- message: parsedError.message,
426
- stackTrace: parsedError.stack,
427
- context: sanitizeAttributes(context, this.options || undefined),
428
- timestamp: new Date().toISOString()
429
- };
428
+ const errPayload = {
429
+ errorClass: parsedError.name || 'Error',
430
+ message: parsedError.message,
431
+ stackTrace: parsedError.stack,
432
+ context: sanitizeAttributes(context, this.options || undefined),
433
+ timestamp: new Date().toISOString()
434
+ };
430
435
 
431
436
  if (currentTrace?.contextType === 'task') {
432
437
  this.transport.addError({ ...errPayload, runId: currentTrace.id }, 'task');
433
438
  } else {
434
439
  this.transport.addError({ ...errPayload, traceId: currentTrace?.id }, 'apm');
435
440
  }
436
- }
437
-
438
- public track(data: any) {
439
- this.transport?.addTrace({ traceId: generateTraceId(), ...data, spans: [], timestamp: new Date().toISOString() });
440
- }
441
-
442
- public startSpan(name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') {
443
- const span = startCapturedSpan(name, type, {}, this.options || undefined);
444
- if (!span) return { end: () => { } };
445
- return { end: (meta?: any, status?: number) => span.end(status, meta) };
446
- }
441
+ }
442
+
443
+ public track(data: any) {
444
+ this.transport?.addTrace({ traceId: generateTraceId(), ...data, spans: [], timestamp: new Date().toISOString() });
445
+ }
446
+
447
+ public startSpan(name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') {
448
+ const span = startCapturedSpan(name, type, {}, this.options || undefined);
449
+ if (!span) return { end: () => { } };
450
+ return { end: (meta?: any, status?: number) => span.end(status, meta) };
451
+ }
447
452
 
448
453
  public async flush() { if (this.transport) await this.transport.flush(); }
449
454
  }
450
455
 
451
- export const client = new SenzorClient();
456
+ export const client = new SenzorClient();
@@ -35,11 +35,11 @@ export const Context = {
35
35
  },
36
36
 
37
37
  addSpanToTrace: (trace: ActiveTrace, span: Span) => {
38
- if (trace.ended) return;
38
+ if (trace.state.ended) return;
39
39
 
40
40
  const maxSpans = trace.maxSpans ?? 500;
41
41
  if (trace.spans.length >= maxSpans) {
42
- trace.droppedSpans = (trace.droppedSpans ?? 0) + 1;
42
+ trace.state.droppedSpans = (trace.state.droppedSpans ?? 0) + 1;
43
43
  return;
44
44
  }
45
45
 
package/src/core/types.ts CHANGED
@@ -1,32 +1,32 @@
1
- export interface SenzorOptions {
2
- apiKey: string;
3
- endpoint?: string;
4
- batchSize?: number;
5
- flushInterval?: number;
6
- flushTimeoutMs?: number;
7
- maxQueueSize?: number;
8
- maxSpansPerTrace?: number;
9
- maxAttributeLength?: number;
10
- maxAttributes?: number;
11
- captureHeaders?: boolean;
12
- captureDbStatement?: boolean;
13
- instrumentations?: boolean | string[];
14
- frameworkSpans?: boolean;
15
- captureMiddlewareSpans?: boolean;
16
- captureRouterSpans?: boolean;
17
- captureLifecycleHookSpans?: boolean;
18
- ignoreFrameworkSpanTypes?: string[];
19
- debug?: boolean;
20
- autoLogs?: boolean;
21
- }
22
-
23
- export interface Span {
24
- spanId: string;
25
- parentSpanId?: string;
26
- name: string;
27
- type: 'db' | 'http' | 'function' | 'custom';
28
- startTime: number;
29
- duration: number;
1
+ export interface SenzorOptions {
2
+ apiKey: string;
3
+ endpoint?: string;
4
+ batchSize?: number;
5
+ flushInterval?: number;
6
+ flushTimeoutMs?: number;
7
+ maxQueueSize?: number;
8
+ maxSpansPerTrace?: number;
9
+ maxAttributeLength?: number;
10
+ maxAttributes?: number;
11
+ captureHeaders?: boolean;
12
+ captureDbStatement?: boolean;
13
+ instrumentations?: boolean | string[];
14
+ frameworkSpans?: boolean;
15
+ captureMiddlewareSpans?: boolean;
16
+ captureRouterSpans?: boolean;
17
+ captureLifecycleHookSpans?: boolean;
18
+ ignoreFrameworkSpanTypes?: string[];
19
+ debug?: boolean;
20
+ autoLogs?: boolean;
21
+ }
22
+
23
+ export interface Span {
24
+ spanId: string;
25
+ parentSpanId?: string;
26
+ name: string;
27
+ type: 'db' | 'http' | 'function' | 'custom';
28
+ startTime: number;
29
+ duration: number;
30
30
  status?: number;
31
31
  meta?: Record<string, any>;
32
32
  }
@@ -89,18 +89,23 @@ export interface TaskRun {
89
89
  timestamp: string;
90
90
  }
91
91
 
92
+ // NEW: Shared mutable state for a trace/task to prevent duplication during context shallow copying
93
+ export interface ActiveTraceState {
94
+ ended: boolean;
95
+ droppedSpans: number;
96
+ }
97
+
92
98
  // Unified Context Payload for async_hooks
93
- export interface ActiveTrace {
94
- id: string; // The APM traceId OR the Task runId
95
- contextType: 'apm' | 'task';
96
- startTime: number;
97
- rootSpanId?: string;
98
- activeSpanId?: string;
99
- startMemory?: number; // Baseline heap
100
- startCpu?: NodeJS.CpuUsage; // Baseline CPU tick
101
- data: any; // Holds Partial<Trace> or Partial<TaskRun>
102
- spans: Span[];
103
- maxSpans?: number;
104
- droppedSpans?: number;
105
- ended?: boolean;
106
- }
99
+ export interface ActiveTrace {
100
+ id: string; // The APM traceId OR the Task runId
101
+ contextType: 'apm' | 'task';
102
+ startTime: number;
103
+ rootSpanId?: string;
104
+ activeSpanId?: string;
105
+ startMemory?: number; // Baseline heap
106
+ startCpu?: NodeJS.CpuUsage; // Baseline CPU tick
107
+ data: any; // Holds Partial<Trace> or Partial<TaskRun>
108
+ spans: Span[];
109
+ maxSpans?: number;
110
+ state: ActiveTraceState;
111
+ }
@@ -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;
@@ -259,7 +259,7 @@ const patchIncomingServer = (
259
259
  finalized = true;
260
260
 
261
261
  setImmediate(() => {
262
- if (trace.ended) return;
262
+ if (trace.state.ended) return;
263
263
 
264
264
  Context.run(trace, () => {
265
265
  client.endTrace(res.statusCode || 0, {
@@ -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