@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.
- 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/instrumentation/express.ts +12 -2
- package/.claude/worktrees/infallible-chatelet-f3fb36/.claude/settings.local.json +0 -9
- package/.claude/worktrees/infallible-chatelet-f3fb36/CHANGELOG.md +0 -49
- package/.claude/worktrees/infallible-chatelet-f3fb36/README.md +0 -398
- package/.claude/worktrees/infallible-chatelet-f3fb36/package-lock.json +0 -1494
- package/.claude/worktrees/infallible-chatelet-f3fb36/package.json +0 -42
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/client.ts +0 -451
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/context.ts +0 -48
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/normalizer.ts +0 -44
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/sanitizer.ts +0 -203
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/transport.ts +0 -273
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/core/types.ts +0 -106
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/index.ts +0 -36
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/bullmq.ts +0 -195
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/cron.ts +0 -204
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/express.ts +0 -338
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/fastify.ts +0 -296
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/framework.ts +0 -301
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/hook.ts +0 -134
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/http.ts +0 -530
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/koa.ts +0 -173
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongo.ts +0 -202
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mongoose.ts +0 -156
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/mysql.ts +0 -169
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/patch.ts +0 -56
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/pg.ts +0 -131
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/redis.ts +0 -109
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/span.ts +0 -73
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/instrumentation/undici.ts +0 -189
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/middleware/express.ts +0 -48
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/register.ts +0 -58
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/getClientIp.ts +0 -175
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/ids.ts +0 -7
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/internal.ts +0 -1
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/sdkMeta.ts +0 -6
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/utils/traceContext.ts +0 -44
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/fastify.ts +0 -35
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/h3.ts +0 -59
- package/.claude/worktrees/infallible-chatelet-f3fb36/src/wrappers/next.ts +0 -131
- package/.claude/worktrees/infallible-chatelet-f3fb36/tsconfig.json +0 -15
- package/.claude/worktrees/infallible-chatelet-f3fb36/tsup.config.ts +0 -21
- package/.claude/worktrees/infallible-chatelet-f3fb36/wiki.md +0 -852
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { SenzorOptions } from './types';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_MAX_ATTRIBUTES = 64;
|
|
4
|
-
const DEFAULT_MAX_ATTRIBUTE_LENGTH = 2048;
|
|
5
|
-
const MAX_DEPTH = 4;
|
|
6
|
-
const MAX_ARRAY_ITEMS = 20;
|
|
7
|
-
|
|
8
|
-
const SENSITIVE_KEY_PATTERN =
|
|
9
|
-
/(^|[-_.])(authorization|cookie|set-cookie|password|passwd|pwd|secret|token|api[-_.]?key|x-api-key|access[-_.]?token|refresh[-_.]?token|client[-_.]?secret|private[-_.]?key)([-_.]|$)/i;
|
|
10
|
-
|
|
11
|
-
export interface SanitizerOptions {
|
|
12
|
-
maxAttributes?: number;
|
|
13
|
-
maxAttributeLength?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const getLimits = (options?: SanitizerOptions | SenzorOptions) => ({
|
|
17
|
-
maxAttributes: options?.maxAttributes ?? DEFAULT_MAX_ATTRIBUTES,
|
|
18
|
-
maxAttributeLength:
|
|
19
|
-
options?.maxAttributeLength ?? DEFAULT_MAX_ATTRIBUTE_LENGTH
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export const truncate = (
|
|
23
|
-
value: string,
|
|
24
|
-
maxLength = DEFAULT_MAX_ATTRIBUTE_LENGTH
|
|
25
|
-
): string => {
|
|
26
|
-
if (value.length <= maxLength) return value;
|
|
27
|
-
return `${value.slice(0, Math.max(0, maxLength - 15))}...[truncated]`;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const isSensitiveKey = (key: string): boolean =>
|
|
31
|
-
SENSITIVE_KEY_PATTERN.test(key);
|
|
32
|
-
|
|
33
|
-
const sanitizePrimitive = (
|
|
34
|
-
value: unknown,
|
|
35
|
-
maxLength: number
|
|
36
|
-
): string | number | boolean | null | undefined => {
|
|
37
|
-
if (value === null || value === undefined) return value;
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
typeof value === 'number' ||
|
|
41
|
-
typeof value === 'boolean'
|
|
42
|
-
) {
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (typeof value === 'bigint') {
|
|
47
|
-
return value.toString();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (typeof value === 'string') {
|
|
51
|
-
return truncate(value, maxLength);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return undefined;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const sanitizeValue = (
|
|
58
|
-
key: string,
|
|
59
|
-
value: unknown,
|
|
60
|
-
options: Required<SanitizerOptions>,
|
|
61
|
-
depth: number
|
|
62
|
-
): unknown => {
|
|
63
|
-
if (isSensitiveKey(key)) return '[REDACTED]';
|
|
64
|
-
|
|
65
|
-
const primitive =
|
|
66
|
-
sanitizePrimitive(value, options.maxAttributeLength);
|
|
67
|
-
|
|
68
|
-
if (primitive !== undefined || value === undefined) {
|
|
69
|
-
return primitive;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (value instanceof Error) {
|
|
73
|
-
return {
|
|
74
|
-
name: truncate(value.name, options.maxAttributeLength),
|
|
75
|
-
message: truncate(value.message, options.maxAttributeLength),
|
|
76
|
-
stack: value.stack
|
|
77
|
-
? truncate(value.stack, options.maxAttributeLength)
|
|
78
|
-
: undefined
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (depth >= MAX_DEPTH) {
|
|
83
|
-
return '[MaxDepth]';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (Array.isArray(value)) {
|
|
87
|
-
return value
|
|
88
|
-
.slice(0, MAX_ARRAY_ITEMS)
|
|
89
|
-
.map((item) =>
|
|
90
|
-
sanitizeValue(key, item, options, depth + 1)
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (typeof value === 'object') {
|
|
95
|
-
const output: Record<string, unknown> = {};
|
|
96
|
-
let count = 0;
|
|
97
|
-
|
|
98
|
-
for (const [childKey, childValue] of Object.entries(
|
|
99
|
-
value as Record<string, unknown>
|
|
100
|
-
)) {
|
|
101
|
-
if (count >= options.maxAttributes) {
|
|
102
|
-
output.__truncated = true;
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
output[childKey] = sanitizeValue(
|
|
107
|
-
childKey,
|
|
108
|
-
childValue,
|
|
109
|
-
options,
|
|
110
|
-
depth + 1
|
|
111
|
-
);
|
|
112
|
-
count++;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return output;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return truncate(String(value), options.maxAttributeLength);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export const sanitizeAttributes = (
|
|
122
|
-
attributes: Record<string, unknown> = {},
|
|
123
|
-
options?: SanitizerOptions | SenzorOptions
|
|
124
|
-
): Record<string, unknown> => {
|
|
125
|
-
const limits = getLimits(options);
|
|
126
|
-
const normalizedOptions: Required<SanitizerOptions> = {
|
|
127
|
-
maxAttributes: limits.maxAttributes,
|
|
128
|
-
maxAttributeLength: limits.maxAttributeLength
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const output: Record<string, unknown> = {};
|
|
132
|
-
let count = 0;
|
|
133
|
-
|
|
134
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
135
|
-
if (count >= normalizedOptions.maxAttributes) {
|
|
136
|
-
output.__truncated = true;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
output[key] = sanitizeValue(
|
|
141
|
-
key,
|
|
142
|
-
value,
|
|
143
|
-
normalizedOptions,
|
|
144
|
-
0
|
|
145
|
-
);
|
|
146
|
-
count++;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return output;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
export const sanitizeHeaders = (
|
|
153
|
-
headers: unknown,
|
|
154
|
-
options?: SanitizerOptions | SenzorOptions
|
|
155
|
-
): Record<string, unknown> => {
|
|
156
|
-
if (!headers || typeof headers !== 'object') return {};
|
|
157
|
-
|
|
158
|
-
const plainHeaders: Record<string, unknown> = {};
|
|
159
|
-
|
|
160
|
-
if (typeof (headers as any).forEach === 'function') {
|
|
161
|
-
(headers as any).forEach((value: unknown, key: string) => {
|
|
162
|
-
plainHeaders[key.toLowerCase()] = value;
|
|
163
|
-
});
|
|
164
|
-
} else {
|
|
165
|
-
for (const [key, value] of Object.entries(
|
|
166
|
-
headers as Record<string, unknown>
|
|
167
|
-
)) {
|
|
168
|
-
plainHeaders[key.toLowerCase()] = Array.isArray(value)
|
|
169
|
-
? value.join(', ')
|
|
170
|
-
: value;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return sanitizeAttributes(plainHeaders, options);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
export const normalizeSql = (
|
|
178
|
-
sql: unknown,
|
|
179
|
-
options?: SenzorOptions
|
|
180
|
-
): string | undefined => {
|
|
181
|
-
if (typeof sql !== 'string') return undefined;
|
|
182
|
-
|
|
183
|
-
const collapsed = sql.replace(/\s+/g, ' ').trim();
|
|
184
|
-
if (!collapsed) return undefined;
|
|
185
|
-
|
|
186
|
-
const withoutLiterals = collapsed
|
|
187
|
-
.replace(/'(?:''|[^'])*'/g, '?')
|
|
188
|
-
.replace(/"(?:\\"|[^"])*"/g, '?')
|
|
189
|
-
.replace(/\b\d+(\.\d+)?\b/g, '?');
|
|
190
|
-
|
|
191
|
-
return truncate(
|
|
192
|
-
options?.captureDbStatement === false
|
|
193
|
-
? withoutLiterals.split(' ').slice(0, 6).join(' ')
|
|
194
|
-
: withoutLiterals,
|
|
195
|
-
options?.maxAttributeLength ?? DEFAULT_MAX_ATTRIBUTE_LENGTH
|
|
196
|
-
);
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
export const getSqlOperation = (sql: unknown): string | undefined => {
|
|
200
|
-
if (typeof sql !== 'string') return undefined;
|
|
201
|
-
const match = sql.trim().match(/^([a-z]+)/i);
|
|
202
|
-
return match?.[1]?.toUpperCase();
|
|
203
|
-
};
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { SENZOR_INTERNAL_HEADER } from '../utils/internal';
|
|
2
|
-
import { SenzorOptions, Trace, TaskRun, SenzorError, SenzorLog } from './types';
|
|
3
|
-
|
|
4
|
-
interface ApmPayload {
|
|
5
|
-
traces: Trace[];
|
|
6
|
-
errors: SenzorError[];
|
|
7
|
-
logs: SenzorLog[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface TaskPayload {
|
|
11
|
-
runs: TaskRun[];
|
|
12
|
-
errors: SenzorError[];
|
|
13
|
-
logs: SenzorLog[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class Transport {
|
|
17
|
-
private traceQueue: Trace[] = [];
|
|
18
|
-
private apmErrorQueue: SenzorError[] = [];
|
|
19
|
-
private apmLogQueue: SenzorLog[] = [];
|
|
20
|
-
|
|
21
|
-
private taskQueue: TaskRun[] = [];
|
|
22
|
-
private taskErrorQueue: SenzorError[] = [];
|
|
23
|
-
private taskLogQueue: SenzorLog[] = [];
|
|
24
|
-
|
|
25
|
-
private timer: NodeJS.Timeout | null = null;
|
|
26
|
-
private apmEndpoint: string;
|
|
27
|
-
private taskEndpoint: string;
|
|
28
|
-
private isFlushing = false;
|
|
29
|
-
private flushAgain = false;
|
|
30
|
-
private droppedItems = 0;
|
|
31
|
-
|
|
32
|
-
constructor(private config: SenzorOptions) {
|
|
33
|
-
const baseEndpoint = config.endpoint || 'https://api.senzor.dev';
|
|
34
|
-
this.apmEndpoint = baseEndpoint.includes('/api/ingest')
|
|
35
|
-
? baseEndpoint
|
|
36
|
-
: `${baseEndpoint}/api/ingest/apm`;
|
|
37
|
-
this.taskEndpoint = baseEndpoint.includes('/api/ingest')
|
|
38
|
-
? baseEndpoint.replace('/apm', '/task')
|
|
39
|
-
: `${baseEndpoint}/api/ingest/task`;
|
|
40
|
-
|
|
41
|
-
if (typeof setInterval !== 'undefined') {
|
|
42
|
-
this.timer = setInterval(
|
|
43
|
-
() => void this.flush(),
|
|
44
|
-
config.flushInterval || 10000
|
|
45
|
-
);
|
|
46
|
-
if (this.timer && typeof this.timer.unref === 'function') {
|
|
47
|
-
this.timer.unref();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.installShutdownFlush();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public addTrace(trace: any) {
|
|
55
|
-
this.enqueue(this.traceQueue, trace);
|
|
56
|
-
this.checkFlush();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
public addTask(task: TaskRun) {
|
|
60
|
-
this.enqueue(this.taskQueue, task);
|
|
61
|
-
this.checkFlush();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public addError(error: SenzorError, type: 'apm' | 'task' = 'apm') {
|
|
65
|
-
this.enqueue(
|
|
66
|
-
type === 'task' ? this.taskErrorQueue : this.apmErrorQueue,
|
|
67
|
-
error
|
|
68
|
-
);
|
|
69
|
-
this.checkFlush();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public addLog(log: SenzorLog, type: 'apm' | 'task' = 'apm') {
|
|
73
|
-
this.enqueue(
|
|
74
|
-
type === 'task' ? this.taskLogQueue : this.apmLogQueue,
|
|
75
|
-
log
|
|
76
|
-
);
|
|
77
|
-
this.checkFlush();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private enqueue<T>(queue: T[], item: T) {
|
|
81
|
-
queue.push(item);
|
|
82
|
-
|
|
83
|
-
const maxQueueSize = this.config.maxQueueSize ?? 10000;
|
|
84
|
-
while (queue.length > maxQueueSize) {
|
|
85
|
-
queue.shift();
|
|
86
|
-
this.droppedItems++;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private prependWithLimit<T>(queue: T[], items: T[]) {
|
|
91
|
-
if (!items.length) return;
|
|
92
|
-
queue.unshift(...items);
|
|
93
|
-
|
|
94
|
-
const maxQueueSize = this.config.maxQueueSize ?? 10000;
|
|
95
|
-
while (queue.length > maxQueueSize) {
|
|
96
|
-
queue.pop();
|
|
97
|
-
this.droppedItems++;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private checkFlush() {
|
|
102
|
-
const totalApm =
|
|
103
|
-
this.traceQueue.length +
|
|
104
|
-
this.apmErrorQueue.length +
|
|
105
|
-
this.apmLogQueue.length;
|
|
106
|
-
const totalTask =
|
|
107
|
-
this.taskQueue.length +
|
|
108
|
-
this.taskErrorQueue.length +
|
|
109
|
-
this.taskLogQueue.length;
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
totalApm >= (this.config.batchSize || 100) ||
|
|
113
|
-
totalTask >= (this.config.batchSize || 100)
|
|
114
|
-
) {
|
|
115
|
-
void this.flush();
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private takeApmPayload(): ApmPayload {
|
|
120
|
-
const payload = {
|
|
121
|
-
traces: this.traceQueue,
|
|
122
|
-
errors: this.apmErrorQueue,
|
|
123
|
-
logs: this.apmLogQueue
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
this.traceQueue = [];
|
|
127
|
-
this.apmErrorQueue = [];
|
|
128
|
-
this.apmLogQueue = [];
|
|
129
|
-
return payload;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private takeTaskPayload(): TaskPayload {
|
|
133
|
-
const payload = {
|
|
134
|
-
runs: this.taskQueue,
|
|
135
|
-
errors: this.taskErrorQueue,
|
|
136
|
-
logs: this.taskLogQueue
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
this.taskQueue = [];
|
|
140
|
-
this.taskErrorQueue = [];
|
|
141
|
-
this.taskLogQueue = [];
|
|
142
|
-
return payload;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
private restoreApmPayload(payload: ApmPayload) {
|
|
146
|
-
this.prependWithLimit(this.apmLogQueue, payload.logs);
|
|
147
|
-
this.prependWithLimit(this.apmErrorQueue, payload.errors);
|
|
148
|
-
this.prependWithLimit(this.traceQueue, payload.traces);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
private restoreTaskPayload(payload: TaskPayload) {
|
|
152
|
-
this.prependWithLimit(this.taskLogQueue, payload.logs);
|
|
153
|
-
this.prependWithLimit(this.taskErrorQueue, payload.errors);
|
|
154
|
-
this.prependWithLimit(this.taskQueue, payload.runs);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private hasApmPayload(payload: ApmPayload): boolean {
|
|
158
|
-
return (
|
|
159
|
-
payload.traces.length > 0 ||
|
|
160
|
-
payload.errors.length > 0 ||
|
|
161
|
-
payload.logs.length > 0
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private hasTaskPayload(payload: TaskPayload): boolean {
|
|
166
|
-
return (
|
|
167
|
-
payload.runs.length > 0 ||
|
|
168
|
-
payload.errors.length > 0 ||
|
|
169
|
-
payload.logs.length > 0
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private async postJson(endpoint: string, payload: unknown) {
|
|
174
|
-
const controller = new AbortController();
|
|
175
|
-
const timeout = setTimeout(
|
|
176
|
-
() => controller.abort(),
|
|
177
|
-
this.config.flushTimeoutMs ?? 5000
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
if (typeof timeout.unref === 'function') timeout.unref();
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const response = await fetch(endpoint, {
|
|
184
|
-
method: 'POST',
|
|
185
|
-
headers: {
|
|
186
|
-
'Content-Type': 'application/json',
|
|
187
|
-
'x-service-api-key': this.config.apiKey,
|
|
188
|
-
[SENZOR_INTERNAL_HEADER]: 'true'
|
|
189
|
-
},
|
|
190
|
-
body: JSON.stringify(payload),
|
|
191
|
-
keepalive: true,
|
|
192
|
-
signal: controller.signal
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
if (!response.ok) {
|
|
196
|
-
throw new Error(`Senzor ingest failed with status ${response.status}`);
|
|
197
|
-
}
|
|
198
|
-
} finally {
|
|
199
|
-
clearTimeout(timeout);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
public async flush() {
|
|
204
|
-
if (this.isFlushing) {
|
|
205
|
-
this.flushAgain = true;
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
this.isFlushing = true;
|
|
210
|
-
|
|
211
|
-
try {
|
|
212
|
-
do {
|
|
213
|
-
this.flushAgain = false;
|
|
214
|
-
|
|
215
|
-
const apmPayload = this.takeApmPayload();
|
|
216
|
-
const taskPayload = this.takeTaskPayload();
|
|
217
|
-
const sends: Promise<void>[] = [];
|
|
218
|
-
|
|
219
|
-
if (this.hasApmPayload(apmPayload)) {
|
|
220
|
-
sends.push(
|
|
221
|
-
this.postJson(this.apmEndpoint, apmPayload).catch((error) => {
|
|
222
|
-
this.restoreApmPayload(apmPayload);
|
|
223
|
-
throw error;
|
|
224
|
-
})
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (this.hasTaskPayload(taskPayload)) {
|
|
229
|
-
sends.push(
|
|
230
|
-
this.postJson(this.taskEndpoint, taskPayload).catch((error) => {
|
|
231
|
-
this.restoreTaskPayload(taskPayload);
|
|
232
|
-
throw error;
|
|
233
|
-
})
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (!sends.length) continue;
|
|
238
|
-
|
|
239
|
-
const results = await Promise.allSettled(sends);
|
|
240
|
-
const failures = results.filter(
|
|
241
|
-
(result) => result.status === 'rejected'
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
if (this.config.debug) {
|
|
245
|
-
console.log(
|
|
246
|
-
`[Senzor] Flushed: APM(${apmPayload.traces.length} traces, ${apmPayload.logs.length} logs), Task(${taskPayload.runs.length} runs, ${taskPayload.logs.length} logs), failures=${failures.length}, dropped=${this.droppedItems}`
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
} while (this.flushAgain);
|
|
250
|
-
} catch (err) {
|
|
251
|
-
if (this.config.debug) console.error('[Senzor] Transport Flush Error:', err);
|
|
252
|
-
} finally {
|
|
253
|
-
this.isFlushing = false;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
private installShutdownFlush() {
|
|
258
|
-
const key = Symbol.for('senzor.transport.shutdownFlushInstalled');
|
|
259
|
-
const proc = process as unknown as Record<symbol, boolean>;
|
|
260
|
-
if (proc[key]) return;
|
|
261
|
-
|
|
262
|
-
Object.defineProperty(proc, key, {
|
|
263
|
-
value: true,
|
|
264
|
-
enumerable: false
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const flushSyncBestEffort = () => {
|
|
268
|
-
void this.flush();
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
process.once('beforeExit', flushSyncBestEffort);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
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
|
-
status?: number;
|
|
31
|
-
meta?: Record<string, any>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface SenzorError {
|
|
35
|
-
errorClass: string;
|
|
36
|
-
message: string;
|
|
37
|
-
stackTrace?: string;
|
|
38
|
-
traceId?: string; // Maps to APM traceId
|
|
39
|
-
runId?: string; // Maps to Task runId
|
|
40
|
-
context?: any;
|
|
41
|
-
timestamp: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// NEW: Enterprise Log Payload
|
|
45
|
-
export interface SenzorLog {
|
|
46
|
-
message: string;
|
|
47
|
-
level: 'info' | 'warn' | 'error' | 'debug' | 'fatal';
|
|
48
|
-
attributes: Record<string, any>;
|
|
49
|
-
traceId?: string; // Used if context is APM
|
|
50
|
-
runId?: string; // Used if context is Task
|
|
51
|
-
spanId?: string;
|
|
52
|
-
timestamp: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface Trace {
|
|
56
|
-
traceId: string;
|
|
57
|
-
parentTraceId?: string;
|
|
58
|
-
parentSpanId?: string;
|
|
59
|
-
method: string;
|
|
60
|
-
route: string;
|
|
61
|
-
path: string;
|
|
62
|
-
status: number;
|
|
63
|
-
duration: number;
|
|
64
|
-
ip?: string;
|
|
65
|
-
userAgent?: string;
|
|
66
|
-
timestamp: string;
|
|
67
|
-
spans: Span[];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface ResourceMetrics {
|
|
71
|
-
memoryDeltaBytes: number; // Delta of process.memoryUsage().heapUsed
|
|
72
|
-
cpuUserUs: number; // CPU time spent in user space (microseconds)
|
|
73
|
-
cpuSystemUs: number; // CPU time spent in OS system calls (microseconds)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface TaskRun {
|
|
77
|
-
runId: string;
|
|
78
|
-
taskName: string;
|
|
79
|
-
taskType: 'cron' | 'queue' | 'pipeline' | 'custom';
|
|
80
|
-
status: 'success' | 'failed';
|
|
81
|
-
duration: number;
|
|
82
|
-
queueDelay?: number;
|
|
83
|
-
attempts?: number;
|
|
84
|
-
triggerTraceId?: string;
|
|
85
|
-
metadata?: any;
|
|
86
|
-
resourceMetrics?: ResourceMetrics; // Hardware cost profiling
|
|
87
|
-
isDeadLetter?: boolean; // True if the job failed its final retry
|
|
88
|
-
spans: Span[];
|
|
89
|
-
timestamp: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 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
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { client } from './core/client';
|
|
2
|
-
import { expressMiddleware, expressErrorHandler } from './middleware/express';
|
|
3
|
-
import { wrapH3 } from './wrappers/h3';
|
|
4
|
-
import { wrapNextRoute, wrapNextPages } from './wrappers/next';
|
|
5
|
-
import { senzorPlugin } from './wrappers/fastify';
|
|
6
|
-
import { SenzorOptions } from './core/types';
|
|
7
|
-
|
|
8
|
-
const Senzor = {
|
|
9
|
-
preload: (options: Partial<SenzorOptions> = {}) => client.preload(options),
|
|
10
|
-
init: (options: SenzorOptions) => client.init(options),
|
|
11
|
-
flush: () => client.flush(),
|
|
12
|
-
track: client.track.bind(client),
|
|
13
|
-
startSpan: client.startSpan.bind(client),
|
|
14
|
-
captureException: client.captureError.bind(client),
|
|
15
|
-
|
|
16
|
-
// Task Monitoring (NEW)
|
|
17
|
-
wrapTask: client.wrapTask.bind(client),
|
|
18
|
-
startTask: client.startTask.bind(client),
|
|
19
|
-
|
|
20
|
-
// Express
|
|
21
|
-
requestHandler: expressMiddleware,
|
|
22
|
-
errorHandler: expressErrorHandler,
|
|
23
|
-
|
|
24
|
-
// Next
|
|
25
|
-
wrapNextRoute,
|
|
26
|
-
wrapNextPages,
|
|
27
|
-
|
|
28
|
-
// H3
|
|
29
|
-
wrapH3,
|
|
30
|
-
|
|
31
|
-
// Fastify
|
|
32
|
-
fastifyPlugin: senzorPlugin
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export default Senzor;
|
|
36
|
-
export { Senzor };
|