@senzops/apm-node 1.1.16 → 1.1.17
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/CHANGELOG.md +4 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- 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/package.json +1 -1
- package/src/core/client.ts +111 -91
- package/src/core/transport.ts +30 -9
- package/src/core/types.ts +12 -0
package/src/core/client.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transport } from './transport';
|
|
2
2
|
import { Context } from './context';
|
|
3
|
-
import { SenzorOptions, ActiveTrace, TaskRun } from './types';
|
|
3
|
+
import { SenzorOptions, ActiveTrace, TaskRun, SenzorLog } from './types';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
5
|
import { instrumentHttp, instrumentFetch } from '../instrumentation/http';
|
|
6
6
|
import { instrumentMongo } from '../instrumentation/mongo';
|
|
@@ -8,10 +8,23 @@ import { instrumentPg } from '../instrumentation/pg';
|
|
|
8
8
|
import { instrumentBullMQ } from '../instrumentation/bullmq';
|
|
9
9
|
import { instrumentNodeCron } from '../instrumentation/cron';
|
|
10
10
|
import { SDK_META } from '../utils/sdkMeta';
|
|
11
|
-
import { parseTraceparent } from '../utils/traceContext';
|
|
11
|
+
import { parseTraceparent } from '../utils/traceContext';
|
|
12
12
|
|
|
13
13
|
const generateW3CTraceId = () => randomUUID().replace(/-/g, '');
|
|
14
14
|
|
|
15
|
+
// Memory-safe JSON stringifier to handle cyclical objects
|
|
16
|
+
// (like Express 'req' objects) passed into console.log
|
|
17
|
+
const safeStringify = (obj: any): string => {
|
|
18
|
+
const cache = new Set();
|
|
19
|
+
return JSON.stringify(obj, (key, value) => {
|
|
20
|
+
if (typeof value === 'object' && value !== null) {
|
|
21
|
+
if (cache.has(value)) return '[Circular]';
|
|
22
|
+
cache.add(value);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
15
28
|
export class SenzorClient {
|
|
16
29
|
private transport: Transport | null = null;
|
|
17
30
|
private options: SenzorOptions | null = null;
|
|
@@ -30,21 +43,101 @@ export class SenzorClient {
|
|
|
30
43
|
|
|
31
44
|
if (!this.isInstrumented) {
|
|
32
45
|
this.setupGlobalErrorHandlers();
|
|
46
|
+
this.setupLogInterception(); // Fire up Auto Log Instrumentation
|
|
33
47
|
|
|
34
48
|
try { instrumentHttp(endpoint, debug); } catch (e) { }
|
|
35
49
|
try { instrumentFetch(endpoint, debug); } catch (e) { }
|
|
36
50
|
try { instrumentMongo(debug); } catch (e) { }
|
|
37
51
|
try { instrumentPg(); } catch (e) { }
|
|
38
52
|
|
|
39
|
-
// Task Integrations
|
|
53
|
+
// Task Integrations
|
|
40
54
|
try { instrumentBullMQ(this, debug); } catch (e) { }
|
|
41
55
|
try { instrumentNodeCron(this, debug); } catch (e) { }
|
|
42
56
|
|
|
43
57
|
this.isInstrumented = true;
|
|
44
|
-
if (debug) console.log('[Senzor] Auto-instrumentation
|
|
58
|
+
if (debug) console.log('[Senzor] Auto-instrumentation enabled');
|
|
45
59
|
}
|
|
46
60
|
}
|
|
47
61
|
|
|
62
|
+
// --- Enterprise Auto-Log Interception ---
|
|
63
|
+
private setupLogInterception() {
|
|
64
|
+
if (this.options?.autoLogs === false) return; // Opt-out check
|
|
65
|
+
|
|
66
|
+
const levels = ['log', 'info', 'warn', 'error', 'debug'] as const;
|
|
67
|
+
const originalConsole = {
|
|
68
|
+
log: console.log,
|
|
69
|
+
info: console.info,
|
|
70
|
+
warn: console.warn,
|
|
71
|
+
error: console.error,
|
|
72
|
+
debug: console.debug
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
let isIntercepting = false; // Lock to prevent SDK internal logs from looping infinitely
|
|
76
|
+
|
|
77
|
+
levels.forEach(level => {
|
|
78
|
+
console[level] = (...args: any[]) => {
|
|
79
|
+
// Always execute original console so user's terminal isn't broken
|
|
80
|
+
originalConsole[level].apply(console, args);
|
|
81
|
+
|
|
82
|
+
if (isIntercepting || !this.transport) return;
|
|
83
|
+
isIntercepting = true;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
let message = '';
|
|
87
|
+
let attributes: Record<string, any> = {};
|
|
88
|
+
|
|
89
|
+
args.forEach(arg => {
|
|
90
|
+
if (typeof arg === 'string') {
|
|
91
|
+
message += (message ? ' ' : '') + arg;
|
|
92
|
+
} else if (arg instanceof Error) {
|
|
93
|
+
message += (message ? ' ' : '') + arg.message;
|
|
94
|
+
attributes.errorStack = arg.stack;
|
|
95
|
+
attributes.errorName = arg.name;
|
|
96
|
+
} else if (typeof arg === 'object' && arg !== null) {
|
|
97
|
+
try {
|
|
98
|
+
// New Relic Style Destructuring: Merge all object keys into `attributes`
|
|
99
|
+
const parsed = JSON.parse(safeStringify(arg));
|
|
100
|
+
attributes = { ...attributes, ...parsed };
|
|
101
|
+
} catch (e) {
|
|
102
|
+
attributes.unparseableObject = true;
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
message += (message ? ' ' : '') + String(arg);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Fallback if the user purely logged an object without text e.g., console.log({ user: 123 })
|
|
110
|
+
if (!message && Object.keys(attributes).length > 0) {
|
|
111
|
+
message = 'Object Log';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Attach to Active Context seamlessly (Works for BOTH APM and Tasks!)
|
|
115
|
+
const currentTrace = Context.current();
|
|
116
|
+
const logType = currentTrace?.contextType === 'task' ? 'task' : 'apm';
|
|
117
|
+
|
|
118
|
+
const logPayload: SenzorLog = {
|
|
119
|
+
message: message || 'Empty log',
|
|
120
|
+
level: level === 'log' ? 'info' : level, // Map generic log -> info
|
|
121
|
+
attributes,
|
|
122
|
+
timestamp: new Date().toISOString()
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Attach the specific contextual ID
|
|
126
|
+
if (currentTrace) {
|
|
127
|
+
if (logType === 'task') logPayload.runId = currentTrace.id;
|
|
128
|
+
else logPayload.traceId = currentTrace.id;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.transport.addLog(logPayload, logType);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
// Absolute failure isolation. Never crash host app during logging.
|
|
134
|
+
} finally {
|
|
135
|
+
isIntercepting = false; // Release lock
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
48
141
|
private setupGlobalErrorHandlers() {
|
|
49
142
|
if ((process as any).__senzorGlobalHandlersInstalled) {
|
|
50
143
|
return;
|
|
@@ -97,21 +190,14 @@ export class SenzorClient {
|
|
|
97
190
|
}
|
|
98
191
|
const enrichedMeta = {
|
|
99
192
|
...meta,
|
|
100
|
-
runtime: {
|
|
101
|
-
name: 'node',
|
|
102
|
-
version: process.version
|
|
103
|
-
},
|
|
193
|
+
runtime: { name: 'node', version: process.version },
|
|
104
194
|
process: getProcessContext(),
|
|
105
195
|
memory: getMemoryContext(),
|
|
106
|
-
sdk: {
|
|
107
|
-
name: SDK_META.name,
|
|
108
|
-
version: SDK_META.version
|
|
109
|
-
}
|
|
196
|
+
sdk: { name: SDK_META.name, version: SDK_META.version }
|
|
110
197
|
};
|
|
111
198
|
|
|
112
199
|
this.captureError(parsedError, enrichedMeta);
|
|
113
200
|
} catch (internalFailure) {
|
|
114
|
-
// NEVER allow SDK to crash host app
|
|
115
201
|
try {
|
|
116
202
|
if (this.options?.debug) {
|
|
117
203
|
console.error('[Senzor] Error handler failure:', internalFailure);
|
|
@@ -120,69 +206,14 @@ export class SenzorClient {
|
|
|
120
206
|
}
|
|
121
207
|
};
|
|
122
208
|
|
|
123
|
-
process.on('uncaughtExceptionMonitor', (error) => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
process.on('
|
|
131
|
-
safeCapture(error, {
|
|
132
|
-
type: 'uncaughtException',
|
|
133
|
-
severity: 'fatal'
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
process.on('unhandledRejection', (reason) => {
|
|
138
|
-
safeCapture(reason, {
|
|
139
|
-
type: 'unhandledRejection',
|
|
140
|
-
severity: 'error'
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
process.on('warning', (warning) => {
|
|
145
|
-
safeCapture(warning, {
|
|
146
|
-
type: 'processWarning',
|
|
147
|
-
severity: 'warning'
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
process.on('multipleResolves', (type, promise, reason) => {
|
|
152
|
-
safeCapture(reason || new Error('Multiple promise resolves'), {
|
|
153
|
-
type: 'multipleResolves',
|
|
154
|
-
resolveType: type,
|
|
155
|
-
severity: 'warning'
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
process.on('rejectionHandled', (promise) => {
|
|
160
|
-
if (this.options?.debug) {
|
|
161
|
-
try {
|
|
162
|
-
console.warn('[Senzor] rejectionHandled event detected');
|
|
163
|
-
} catch { }
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
process.on('SIGTERM', () => {
|
|
168
|
-
safeCapture(
|
|
169
|
-
new Error('Process received SIGTERM'),
|
|
170
|
-
{
|
|
171
|
-
type: 'processSignal',
|
|
172
|
-
signal: 'SIGTERM'
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
process.on('SIGINT', () => {
|
|
178
|
-
safeCapture(
|
|
179
|
-
new Error('Process received SIGINT'),
|
|
180
|
-
{
|
|
181
|
-
type: 'processSignal',
|
|
182
|
-
signal: 'SIGINT'
|
|
183
|
-
}
|
|
184
|
-
);
|
|
185
|
-
});
|
|
209
|
+
process.on('uncaughtExceptionMonitor', (error) => safeCapture(error, { type: 'uncaughtExceptionMonitor', severity: 'fatal' }));
|
|
210
|
+
process.on('uncaughtException', (error) => safeCapture(error, { type: 'uncaughtException', severity: 'fatal' }));
|
|
211
|
+
process.on('unhandledRejection', (reason) => safeCapture(reason, { type: 'unhandledRejection', severity: 'error' }));
|
|
212
|
+
process.on('warning', (warning) => safeCapture(warning, { type: 'processWarning', severity: 'warning' }));
|
|
213
|
+
process.on('multipleResolves', (type, promise, reason) => safeCapture(reason || new Error('Multiple promise resolves'), { type: 'multipleResolves', resolveType: type, severity: 'warning' }));
|
|
214
|
+
process.on('rejectionHandled', (promise) => { if (this.options?.debug) { try { console.warn('[Senzor] rejectionHandled event detected'); } catch { } } });
|
|
215
|
+
process.on('SIGTERM', () => safeCapture(new Error('Process received SIGTERM'), { type: 'processSignal', signal: 'SIGTERM' }));
|
|
216
|
+
process.on('SIGINT', () => safeCapture(new Error('Process received SIGINT'), { type: 'processSignal', signal: 'SIGINT' }));
|
|
186
217
|
}
|
|
187
218
|
|
|
188
219
|
public startTrace<T>(data: Partial<ActiveTrace['data']> & { headers?: any }, next: () => T): T {
|
|
@@ -198,7 +229,6 @@ export class SenzorClient {
|
|
|
198
229
|
return undefined;
|
|
199
230
|
};
|
|
200
231
|
|
|
201
|
-
// 1. Prioritize standard W3C Context (e.g., from RUM Frontend)
|
|
202
232
|
const traceparent = getHeader('traceparent');
|
|
203
233
|
const parsedContext = parseTraceparent(traceparent);
|
|
204
234
|
|
|
@@ -206,7 +236,6 @@ export class SenzorClient {
|
|
|
206
236
|
inheritedTraceId = parsedContext.traceId;
|
|
207
237
|
inheritedParentSpanId = parsedContext.parentSpanId;
|
|
208
238
|
} else {
|
|
209
|
-
// 2. Fallback to legacy proprietary headers
|
|
210
239
|
const rawTrace = getHeader('x-senzor-trace-id');
|
|
211
240
|
const rawSpan = getHeader('x-senzor-parent-span-id');
|
|
212
241
|
inheritedTraceId = Array.isArray(rawTrace) ? rawTrace[0] : rawTrace;
|
|
@@ -214,18 +243,13 @@ export class SenzorClient {
|
|
|
214
243
|
}
|
|
215
244
|
}
|
|
216
245
|
|
|
217
|
-
// Crucial: ADOPT the inherited traceId to perfectly link Frontend & Backend
|
|
218
246
|
const activeTraceId = inheritedTraceId || generateW3CTraceId();
|
|
219
247
|
|
|
220
248
|
const trace: ActiveTrace = {
|
|
221
249
|
id: activeTraceId,
|
|
222
|
-
contextType: 'apm',
|
|
250
|
+
contextType: 'apm',
|
|
223
251
|
startTime: performance.now(),
|
|
224
|
-
data: {
|
|
225
|
-
...data,
|
|
226
|
-
parentTraceId: inheritedTraceId,
|
|
227
|
-
parentSpanId: inheritedParentSpanId
|
|
228
|
-
},
|
|
252
|
+
data: { ...data, parentTraceId: inheritedTraceId, parentSpanId: inheritedParentSpanId },
|
|
229
253
|
spans: []
|
|
230
254
|
};
|
|
231
255
|
|
|
@@ -255,7 +279,6 @@ export class SenzorClient {
|
|
|
255
279
|
const currentContext = Context.current();
|
|
256
280
|
const triggerTraceId = currentContext?.contextType === 'apm' ? currentContext.id : undefined;
|
|
257
281
|
|
|
258
|
-
// Snapshot system resources before execution
|
|
259
282
|
const startMemory = process.memoryUsage ? process.memoryUsage().heapUsed : 0;
|
|
260
283
|
const startCpu = process.cpuUsage ? process.cpuUsage() : undefined;
|
|
261
284
|
|
|
@@ -275,14 +298,13 @@ export class SenzorClient {
|
|
|
275
298
|
const task = Context.current();
|
|
276
299
|
if (!task || task.contextType !== 'task' || !this.transport) return;
|
|
277
300
|
|
|
278
|
-
// Calculate resource deltas
|
|
279
301
|
let resourceMetrics;
|
|
280
302
|
if (process.memoryUsage && task.startMemory !== undefined && process.cpuUsage && task.startCpu) {
|
|
281
303
|
const endMemory = process.memoryUsage().heapUsed;
|
|
282
304
|
const cpuDelta = process.cpuUsage(task.startCpu);
|
|
283
305
|
|
|
284
306
|
resourceMetrics = {
|
|
285
|
-
memoryDeltaBytes: endMemory - task.startMemory,
|
|
307
|
+
memoryDeltaBytes: endMemory - task.startMemory,
|
|
286
308
|
cpuUserUs: cpuDelta.user,
|
|
287
309
|
cpuSystemUs: cpuDelta.system
|
|
288
310
|
};
|
|
@@ -295,7 +317,7 @@ export class SenzorClient {
|
|
|
295
317
|
triggerTraceId: task.data.triggerTraceId,
|
|
296
318
|
queueDelay: task.data.queueDelay,
|
|
297
319
|
attempts: task.data.attempts,
|
|
298
|
-
isDeadLetter: task.data.isDeadLetter,
|
|
320
|
+
isDeadLetter: task.data.isDeadLetter,
|
|
299
321
|
metadata: { ...task.data.metadata, ...extraMetadata },
|
|
300
322
|
resourceMetrics,
|
|
301
323
|
status,
|
|
@@ -323,7 +345,6 @@ export class SenzorClient {
|
|
|
323
345
|
}) as unknown as T;
|
|
324
346
|
}
|
|
325
347
|
|
|
326
|
-
// --- MODIFIED: Context-Aware Error Capture ---
|
|
327
348
|
public captureError(error: unknown, context: any = {}) {
|
|
328
349
|
if (!this.transport) return;
|
|
329
350
|
|
|
@@ -360,7 +381,6 @@ export class SenzorClient {
|
|
|
360
381
|
if (!trace) return { end: () => { } };
|
|
361
382
|
const startTime = performance.now() - trace.startTime;
|
|
362
383
|
const spanStartAbs = performance.now();
|
|
363
|
-
// Use 16 char hex for span IDs for W3C compatibility
|
|
364
384
|
const spanId = randomUUID().replace(/-/g, '').slice(0, 16);
|
|
365
385
|
return { end: (meta?: any, status?: number) => { Context.addSpan({ spanId, name, type, startTime, duration: performance.now() - spanStartAbs, status, meta }); } };
|
|
366
386
|
}
|
package/src/core/transport.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { SenzorOptions, Trace, TaskRun, SenzorError } from './types';
|
|
1
|
+
import { SenzorOptions, Trace, TaskRun, SenzorError, SenzorLog } from './types';
|
|
2
2
|
|
|
3
3
|
export class Transport {
|
|
4
4
|
private traceQueue: Trace[] = [];
|
|
5
5
|
private apmErrorQueue: SenzorError[] = [];
|
|
6
|
+
private apmLogQueue: SenzorLog[] = []; // APM Logs
|
|
6
7
|
|
|
7
8
|
private taskQueue: TaskRun[] = [];
|
|
8
9
|
private taskErrorQueue: SenzorError[] = [];
|
|
10
|
+
private taskLogQueue: SenzorLog[] = []; // Task Logs
|
|
9
11
|
|
|
10
12
|
private timer: NodeJS.Timeout | null = null;
|
|
11
13
|
private apmEndpoint: string;
|
|
@@ -41,40 +43,59 @@ export class Transport {
|
|
|
41
43
|
this.checkFlush();
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
// Add captured log to the correct batch queue
|
|
47
|
+
public addLog(log: SenzorLog, type: 'apm' | 'task' = 'apm') {
|
|
48
|
+
if (type === 'task') this.taskLogQueue.push(log);
|
|
49
|
+
else this.apmLogQueue.push(log);
|
|
50
|
+
this.checkFlush();
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
private checkFlush() {
|
|
45
|
-
const totalApm = this.traceQueue.length + this.apmErrorQueue.length;
|
|
46
|
-
const totalTask = this.taskQueue.length + this.taskErrorQueue.length;
|
|
54
|
+
const totalApm = this.traceQueue.length + this.apmErrorQueue.length + this.apmLogQueue.length;
|
|
55
|
+
const totalTask = this.taskQueue.length + this.taskErrorQueue.length + this.taskLogQueue.length;
|
|
47
56
|
if (totalApm >= (this.config.batchSize || 100) || totalTask >= (this.config.batchSize || 100)) {
|
|
48
57
|
this.flush();
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
public async flush() {
|
|
53
|
-
const apmPayload = {
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
const apmPayload = {
|
|
63
|
+
traces: [...this.traceQueue],
|
|
64
|
+
errors: [...this.apmErrorQueue],
|
|
65
|
+
logs: [...this.apmLogQueue]
|
|
66
|
+
};
|
|
67
|
+
const taskPayload = {
|
|
68
|
+
runs: [...this.taskQueue],
|
|
69
|
+
errors: [...this.taskErrorQueue],
|
|
70
|
+
logs: [...this.taskLogQueue]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Reset Queues instantly
|
|
56
74
|
this.traceQueue = [];
|
|
57
75
|
this.apmErrorQueue = [];
|
|
76
|
+
this.apmLogQueue = [];
|
|
58
77
|
this.taskQueue = [];
|
|
59
78
|
this.taskErrorQueue = [];
|
|
79
|
+
this.taskLogQueue = [];
|
|
60
80
|
|
|
61
81
|
const headers = { 'Content-Type': 'application/json', 'x-service-api-key': this.config.apiKey };
|
|
62
82
|
|
|
63
83
|
try {
|
|
64
84
|
const promises = [];
|
|
65
85
|
|
|
66
|
-
|
|
86
|
+
// Piggyback logs onto APM/Task batch ingestion to bypass extra network round-trips
|
|
87
|
+
if (apmPayload.traces.length > 0 || apmPayload.errors.length > 0 || apmPayload.logs.length > 0) {
|
|
67
88
|
promises.push(fetch(this.apmEndpoint, { method: 'POST', headers, body: JSON.stringify(apmPayload), keepalive: true }));
|
|
68
89
|
}
|
|
69
90
|
|
|
70
|
-
if (taskPayload.runs.length > 0 || taskPayload.errors.length > 0) {
|
|
91
|
+
if (taskPayload.runs.length > 0 || taskPayload.errors.length > 0 || taskPayload.logs.length > 0) {
|
|
71
92
|
promises.push(fetch(this.taskEndpoint, { method: 'POST', headers, body: JSON.stringify(taskPayload), keepalive: true }));
|
|
72
93
|
}
|
|
73
94
|
|
|
74
95
|
await Promise.allSettled(promises);
|
|
75
96
|
|
|
76
97
|
if (this.config.debug) {
|
|
77
|
-
console.log(`[Senzor] Flushed: ${apmPayload.traces.length} traces, ${taskPayload.runs.length}
|
|
98
|
+
console.log(`[Senzor] Flushed: APM(${apmPayload.traces.length} traces, ${apmPayload.logs.length} logs), Task(${taskPayload.runs.length} runs, ${taskPayload.logs.length} logs)`);
|
|
78
99
|
}
|
|
79
100
|
} catch (err) {
|
|
80
101
|
if (this.config.debug) console.error('[Senzor] Transport Flush Error:', err);
|
package/src/core/types.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface SenzorOptions {
|
|
|
4
4
|
batchSize?: number;
|
|
5
5
|
flushInterval?: number;
|
|
6
6
|
debug?: boolean;
|
|
7
|
+
autoLogs?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export interface Span {
|
|
@@ -26,6 +27,17 @@ export interface SenzorError {
|
|
|
26
27
|
timestamp: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// NEW: Enterprise Log Payload
|
|
31
|
+
export interface SenzorLog {
|
|
32
|
+
message: string;
|
|
33
|
+
level: 'info' | 'warn' | 'error' | 'debug' | 'fatal';
|
|
34
|
+
attributes: Record<string, any>;
|
|
35
|
+
traceId?: string; // Used if context is APM
|
|
36
|
+
runId?: string; // Used if context is Task
|
|
37
|
+
spanId?: string;
|
|
38
|
+
timestamp: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
export interface Trace {
|
|
30
42
|
traceId: string;
|
|
31
43
|
parentTraceId?: string;
|