@senzops/apm-node 1.2.3 → 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.
- package/dist/index.global.js +1 -1
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/register.js +1 -1
- package/dist/register.js.map +1 -1
- package/dist/register.mjs +1 -1
- package/dist/register.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/client.ts +172 -167
- package/src/core/context.ts +2 -2
- package/src/core/types.ts +48 -43
- package/src/instrumentation/http.ts +1 -1
package/package.json
CHANGED
package/src/core/client.ts
CHANGED
|
@@ -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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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();
|
package/src/core/context.ts
CHANGED
|
@@ -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
|
-
|
|
105
|
-
|
|
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
|
+
}
|