@senzops/apm-node 1.2.8 → 1.3.1
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 +13 -0
- package/README.md +527 -398
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -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/dist/lambda-handler.d.mts +13 -0
- package/dist/lambda-handler.d.ts +13 -0
- package/dist/lambda-handler.js +2 -0
- package/dist/lambda-handler.js.map +1 -0
- package/dist/lambda-handler.mjs +2 -0
- package/dist/lambda-handler.mjs.map +1 -0
- 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 +6 -1
- package/src/core/client.ts +57 -0
- package/src/core/transport.ts +20 -3
- package/src/core/types.ts +5 -1
- package/src/index.ts +4 -0
- package/src/instrumentation/amqplib.ts +371 -0
- package/src/instrumentation/anthropic.ts +245 -0
- package/src/instrumentation/aws-sdk.ts +403 -0
- package/src/instrumentation/azure-openai.ts +177 -0
- package/src/instrumentation/bunyan.ts +93 -0
- package/src/instrumentation/cassandra.ts +367 -0
- package/src/instrumentation/cohere.ts +227 -0
- package/src/instrumentation/connect.ts +200 -0
- package/src/instrumentation/dataloader.ts +291 -0
- package/src/instrumentation/dns.ts +220 -0
- package/src/instrumentation/firebase.ts +445 -0
- package/src/instrumentation/fs.ts +260 -0
- package/src/instrumentation/generic-pool.ts +317 -0
- package/src/instrumentation/google-genai.ts +426 -0
- package/src/instrumentation/graphql.ts +434 -0
- package/src/instrumentation/grpc.ts +666 -0
- package/src/instrumentation/hapi.ts +257 -0
- package/src/instrumentation/kafka.ts +360 -0
- package/src/instrumentation/knex.ts +249 -0
- package/src/instrumentation/lru-memoizer.ts +175 -0
- package/src/instrumentation/memcached.ts +190 -0
- package/src/instrumentation/mistral.ts +254 -0
- package/src/instrumentation/nestjs.ts +243 -0
- package/src/instrumentation/net.ts +171 -0
- package/src/instrumentation/openai.ts +281 -0
- package/src/instrumentation/pino.ts +170 -0
- package/src/instrumentation/restify.ts +213 -0
- package/src/instrumentation/runtime.ts +352 -0
- package/src/instrumentation/socketio.ts +272 -0
- package/src/instrumentation/tedious.ts +509 -0
- package/src/instrumentation/winston.ts +149 -0
- package/src/lambda-handler.ts +262 -0
- package/src/register.ts +22 -3
- package/src/wrappers/lambda.ts +417 -0
- package/tsup.config.ts +4 -4
- package/wiki.md +1693 -852
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { SenzorOptions } from '../core/types';
|
|
2
|
+
import { hookRequire } from './hook';
|
|
3
|
+
import { patchMethod } from './patch';
|
|
4
|
+
import { runWithCapturedSpan, startCapturedSpan } from './span';
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Google Generative AI (Gemini) Instrumentation
|
|
8
|
+
//
|
|
9
|
+
// Instruments both the client-side `@google/generative-ai` package and the
|
|
10
|
+
// server-side `@google-cloud/vertexai` package for Google's Gemini models.
|
|
11
|
+
//
|
|
12
|
+
// @google/generative-ai patches:
|
|
13
|
+
// - GenerativeModel.prototype.generateContent()
|
|
14
|
+
// - GenerativeModel.prototype.generateContentStream()
|
|
15
|
+
// - GenerativeModel.prototype.countTokens()
|
|
16
|
+
// - GenerativeModel.prototype.embedContent()
|
|
17
|
+
// - GenerativeModel.prototype.batchEmbedContents()
|
|
18
|
+
// - ChatSession.prototype.sendMessage()
|
|
19
|
+
// - ChatSession.prototype.sendMessageStream()
|
|
20
|
+
//
|
|
21
|
+
// @google-cloud/vertexai patches:
|
|
22
|
+
// - GenerativeModel.prototype.generateContent()
|
|
23
|
+
// - GenerativeModel.prototype.generateContentStream()
|
|
24
|
+
//
|
|
25
|
+
// Captured attributes (OTel GenAI semantic conventions):
|
|
26
|
+
// - gen_ai.system: 'google_ai' | 'vertex_ai'
|
|
27
|
+
// - gen_ai.request.model: gemini-1.5-pro, etc.
|
|
28
|
+
// - gen_ai.operation.name: generateContent, chat, embedContent, etc.
|
|
29
|
+
// - gen_ai.usage.input_tokens: promptTokenCount
|
|
30
|
+
// - gen_ai.usage.output_tokens: candidatesTokenCount
|
|
31
|
+
// - gen_ai.response.finish_reason: from candidates[0].finishReason
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/** Extract token usage from Gemini response. */
|
|
35
|
+
const extractUsage = (response: any): Record<string, any> => {
|
|
36
|
+
const meta: Record<string, any> = {};
|
|
37
|
+
const usage = response?.usageMetadata;
|
|
38
|
+
|
|
39
|
+
if (usage) {
|
|
40
|
+
if (usage.promptTokenCount !== undefined) {
|
|
41
|
+
meta['gen_ai.usage.input_tokens'] = usage.promptTokenCount;
|
|
42
|
+
}
|
|
43
|
+
if (usage.candidatesTokenCount !== undefined) {
|
|
44
|
+
meta['gen_ai.usage.output_tokens'] = usage.candidatesTokenCount;
|
|
45
|
+
}
|
|
46
|
+
if (usage.totalTokenCount !== undefined) {
|
|
47
|
+
meta['gen_ai.usage.total_tokens'] = usage.totalTokenCount;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Extract finish reason from first candidate
|
|
52
|
+
const finishReason = response?.candidates?.[0]?.finishReason;
|
|
53
|
+
if (finishReason) {
|
|
54
|
+
meta['gen_ai.response.finish_reason'] = finishReason;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return meta;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Extract usage from a GenerateContentResponse (may be wrapped). */
|
|
61
|
+
const extractResponseUsage = (result: any): Record<string, any> => {
|
|
62
|
+
// result could be GenerateContentResult { response } or the response directly
|
|
63
|
+
const response = result?.response || result;
|
|
64
|
+
return extractUsage(response);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// @google/generative-ai patching
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
const patchGoogleGenAI = (genaiModule: any, options?: SenzorOptions) => {
|
|
72
|
+
// Patch GenerativeModel
|
|
73
|
+
const GenerativeModel = genaiModule?.GenerativeModel;
|
|
74
|
+
if (GenerativeModel?.prototype) {
|
|
75
|
+
const proto = GenerativeModel.prototype;
|
|
76
|
+
|
|
77
|
+
// --- generateContent ---
|
|
78
|
+
patchMethod(
|
|
79
|
+
proto,
|
|
80
|
+
'generateContent',
|
|
81
|
+
'senzor.google-genai.generateContent',
|
|
82
|
+
(original) =>
|
|
83
|
+
function patchedGenerateContent(this: any, request: any) {
|
|
84
|
+
const modelName = this.model || this.modelName || 'unknown';
|
|
85
|
+
|
|
86
|
+
const span = startCapturedSpan(
|
|
87
|
+
`Gemini generateContent ${modelName}`,
|
|
88
|
+
'http',
|
|
89
|
+
{
|
|
90
|
+
'gen_ai.system': 'google_ai',
|
|
91
|
+
'gen_ai.operation.name': 'generateContent',
|
|
92
|
+
'gen_ai.request.model': modelName,
|
|
93
|
+
'gen_ai.request.temperature': this.generationConfig?.temperature,
|
|
94
|
+
'gen_ai.request.max_tokens': this.generationConfig?.maxOutputTokens,
|
|
95
|
+
'gen_ai.request.top_p': this.generationConfig?.topP,
|
|
96
|
+
library: 'google-genai',
|
|
97
|
+
},
|
|
98
|
+
options
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!span) return original.call(this, request);
|
|
102
|
+
|
|
103
|
+
return runWithCapturedSpan(span, () => {
|
|
104
|
+
try {
|
|
105
|
+
const result = original.call(this, request);
|
|
106
|
+
|
|
107
|
+
if (result && typeof result.then === 'function') {
|
|
108
|
+
return result.then(
|
|
109
|
+
(value: any) => {
|
|
110
|
+
span.end(0, extractResponseUsage(value));
|
|
111
|
+
return value;
|
|
112
|
+
},
|
|
113
|
+
(error: any) => {
|
|
114
|
+
span.end(error?.status || 500, {
|
|
115
|
+
'error.message': error?.message,
|
|
116
|
+
'error.type': error?.name || 'GoogleGenAIError',
|
|
117
|
+
});
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
span.end(0);
|
|
124
|
+
return result;
|
|
125
|
+
} catch (error: any) {
|
|
126
|
+
span.end(500, { 'error.message': error?.message });
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// --- generateContentStream ---
|
|
134
|
+
if (typeof proto.generateContentStream === 'function') {
|
|
135
|
+
patchMethod(
|
|
136
|
+
proto,
|
|
137
|
+
'generateContentStream',
|
|
138
|
+
'senzor.google-genai.generateContentStream',
|
|
139
|
+
(original) =>
|
|
140
|
+
function patchedGenerateContentStream(this: any, request: any) {
|
|
141
|
+
const modelName = this.model || this.modelName || 'unknown';
|
|
142
|
+
|
|
143
|
+
const span = startCapturedSpan(
|
|
144
|
+
`Gemini generateContentStream ${modelName}`,
|
|
145
|
+
'http',
|
|
146
|
+
{
|
|
147
|
+
'gen_ai.system': 'google_ai',
|
|
148
|
+
'gen_ai.operation.name': 'generateContentStream',
|
|
149
|
+
'gen_ai.request.model': modelName,
|
|
150
|
+
library: 'google-genai',
|
|
151
|
+
},
|
|
152
|
+
options
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!span) return original.call(this, request);
|
|
156
|
+
|
|
157
|
+
return runWithCapturedSpan(span, () => {
|
|
158
|
+
try {
|
|
159
|
+
const result = original.call(this, request);
|
|
160
|
+
|
|
161
|
+
if (result && typeof result.then === 'function') {
|
|
162
|
+
return result.then(
|
|
163
|
+
(streamResult: any) => {
|
|
164
|
+
// Stream result has a .response promise for final aggregated response
|
|
165
|
+
if (streamResult?.response && typeof streamResult.response.then === 'function') {
|
|
166
|
+
streamResult.response.then(
|
|
167
|
+
(resp: any) => span.end(0, extractUsage(resp)),
|
|
168
|
+
() => span.end(0)
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
span.end(0);
|
|
172
|
+
}
|
|
173
|
+
return streamResult;
|
|
174
|
+
},
|
|
175
|
+
(error: any) => {
|
|
176
|
+
span.end(error?.status || 500, { 'error.message': error?.message });
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
span.end(0);
|
|
183
|
+
return result;
|
|
184
|
+
} catch (error: any) {
|
|
185
|
+
span.end(500, { 'error.message': error?.message });
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- countTokens ---
|
|
194
|
+
if (typeof proto.countTokens === 'function') {
|
|
195
|
+
patchMethod(
|
|
196
|
+
proto,
|
|
197
|
+
'countTokens',
|
|
198
|
+
'senzor.google-genai.countTokens',
|
|
199
|
+
(original) =>
|
|
200
|
+
function patchedCountTokens(this: any, request: any) {
|
|
201
|
+
const modelName = this.model || this.modelName || 'unknown';
|
|
202
|
+
|
|
203
|
+
const span = startCapturedSpan(
|
|
204
|
+
`Gemini countTokens ${modelName}`,
|
|
205
|
+
'http',
|
|
206
|
+
{
|
|
207
|
+
'gen_ai.system': 'google_ai',
|
|
208
|
+
'gen_ai.operation.name': 'countTokens',
|
|
209
|
+
'gen_ai.request.model': modelName,
|
|
210
|
+
library: 'google-genai',
|
|
211
|
+
},
|
|
212
|
+
options
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (!span) return original.call(this, request);
|
|
216
|
+
|
|
217
|
+
return runWithCapturedSpan(span, () => {
|
|
218
|
+
try {
|
|
219
|
+
const result = original.call(this, request);
|
|
220
|
+
if (result && typeof result.then === 'function') {
|
|
221
|
+
return result.then(
|
|
222
|
+
(value: any) => {
|
|
223
|
+
span.end(0, {
|
|
224
|
+
'gen_ai.usage.total_tokens': value?.totalTokens,
|
|
225
|
+
});
|
|
226
|
+
return value;
|
|
227
|
+
},
|
|
228
|
+
(error: any) => {
|
|
229
|
+
span.end(500, { 'error.message': error?.message });
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
span.end(0);
|
|
235
|
+
return result;
|
|
236
|
+
} catch (error: any) {
|
|
237
|
+
span.end(500, { 'error.message': error?.message });
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// --- embedContent ---
|
|
246
|
+
if (typeof proto.embedContent === 'function') {
|
|
247
|
+
patchMethod(
|
|
248
|
+
proto,
|
|
249
|
+
'embedContent',
|
|
250
|
+
'senzor.google-genai.embedContent',
|
|
251
|
+
(original) =>
|
|
252
|
+
function patchedEmbedContent(this: any, request: any) {
|
|
253
|
+
const modelName = this.model || this.modelName || 'unknown';
|
|
254
|
+
|
|
255
|
+
const span = startCapturedSpan(
|
|
256
|
+
`Gemini embedContent ${modelName}`,
|
|
257
|
+
'http',
|
|
258
|
+
{
|
|
259
|
+
'gen_ai.system': 'google_ai',
|
|
260
|
+
'gen_ai.operation.name': 'embedContent',
|
|
261
|
+
'gen_ai.request.model': modelName,
|
|
262
|
+
library: 'google-genai',
|
|
263
|
+
},
|
|
264
|
+
options
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (!span) return original.call(this, request);
|
|
268
|
+
|
|
269
|
+
return runWithCapturedSpan(span, () => {
|
|
270
|
+
try {
|
|
271
|
+
const result = original.call(this, request);
|
|
272
|
+
if (result && typeof result.then === 'function') {
|
|
273
|
+
return result.then(
|
|
274
|
+
(value: any) => { span.end(0); return value; },
|
|
275
|
+
(error: any) => {
|
|
276
|
+
span.end(500, { 'error.message': error?.message });
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
span.end(0);
|
|
282
|
+
return result;
|
|
283
|
+
} catch (error: any) {
|
|
284
|
+
span.end(500, { 'error.message': error?.message });
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Patch ChatSession
|
|
294
|
+
const ChatSession = genaiModule?.ChatSession;
|
|
295
|
+
if (ChatSession?.prototype) {
|
|
296
|
+
const chatProto = ChatSession.prototype;
|
|
297
|
+
|
|
298
|
+
for (const method of ['sendMessage', 'sendMessageStream'] as const) {
|
|
299
|
+
if (typeof chatProto[method] !== 'function') continue;
|
|
300
|
+
|
|
301
|
+
patchMethod(
|
|
302
|
+
chatProto,
|
|
303
|
+
method,
|
|
304
|
+
`senzor.google-genai.chat.${method}`,
|
|
305
|
+
(original) =>
|
|
306
|
+
function patchedChatMethod(this: any, ...args: any[]) {
|
|
307
|
+
const modelName = this.model || this._model || 'unknown';
|
|
308
|
+
const isStream = method === 'sendMessageStream';
|
|
309
|
+
|
|
310
|
+
const span = startCapturedSpan(
|
|
311
|
+
`Gemini chat.${method} ${modelName}`,
|
|
312
|
+
'http',
|
|
313
|
+
{
|
|
314
|
+
'gen_ai.system': 'google_ai',
|
|
315
|
+
'gen_ai.operation.name': isStream ? 'chat.stream' : 'chat',
|
|
316
|
+
'gen_ai.request.model': modelName,
|
|
317
|
+
library: 'google-genai',
|
|
318
|
+
},
|
|
319
|
+
options
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (!span) return original.apply(this, args);
|
|
323
|
+
|
|
324
|
+
return runWithCapturedSpan(span, () => {
|
|
325
|
+
try {
|
|
326
|
+
const result = original.apply(this, args);
|
|
327
|
+
if (result && typeof result.then === 'function') {
|
|
328
|
+
return result.then(
|
|
329
|
+
(value: any) => {
|
|
330
|
+
span.end(0, extractResponseUsage(value));
|
|
331
|
+
return value;
|
|
332
|
+
},
|
|
333
|
+
(error: any) => {
|
|
334
|
+
span.end(500, { 'error.message': error?.message });
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
span.end(0);
|
|
340
|
+
return result;
|
|
341
|
+
} catch (error: any) {
|
|
342
|
+
span.end(500, { 'error.message': error?.message });
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// @google-cloud/vertexai patching (same model classes, different system tag)
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
const patchVertexAI = (vertexModule: any, options?: SenzorOptions) => {
|
|
357
|
+
const GenerativeModel = vertexModule?.GenerativeModel;
|
|
358
|
+
if (!GenerativeModel?.prototype) return;
|
|
359
|
+
|
|
360
|
+
const proto = GenerativeModel.prototype;
|
|
361
|
+
|
|
362
|
+
for (const method of ['generateContent', 'generateContentStream'] as const) {
|
|
363
|
+
if (typeof proto[method] !== 'function') continue;
|
|
364
|
+
|
|
365
|
+
patchMethod(
|
|
366
|
+
proto,
|
|
367
|
+
method,
|
|
368
|
+
`senzor.vertexai.${method}`,
|
|
369
|
+
(original) =>
|
|
370
|
+
function patchedVertexMethod(this: any, request: any) {
|
|
371
|
+
const modelName = this.model || this.modelName || 'unknown';
|
|
372
|
+
|
|
373
|
+
const span = startCapturedSpan(
|
|
374
|
+
`VertexAI ${method} ${modelName}`,
|
|
375
|
+
'http',
|
|
376
|
+
{
|
|
377
|
+
'gen_ai.system': 'vertex_ai',
|
|
378
|
+
'gen_ai.operation.name': method,
|
|
379
|
+
'gen_ai.request.model': modelName,
|
|
380
|
+
library: 'vertex-ai',
|
|
381
|
+
},
|
|
382
|
+
options
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (!span) return original.call(this, request);
|
|
386
|
+
|
|
387
|
+
return runWithCapturedSpan(span, () => {
|
|
388
|
+
try {
|
|
389
|
+
const result = original.call(this, request);
|
|
390
|
+
if (result && typeof result.then === 'function') {
|
|
391
|
+
return result.then(
|
|
392
|
+
(value: any) => {
|
|
393
|
+
span.end(0, extractResponseUsage(value));
|
|
394
|
+
return value;
|
|
395
|
+
},
|
|
396
|
+
(error: any) => {
|
|
397
|
+
span.end(500, { 'error.message': error?.message });
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
span.end(0);
|
|
403
|
+
return result;
|
|
404
|
+
} catch (error: any) {
|
|
405
|
+
span.end(500, { 'error.message': error?.message });
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
// Public API
|
|
416
|
+
// ---------------------------------------------------------------------------
|
|
417
|
+
|
|
418
|
+
export const instrumentGoogleGenAI = (options?: SenzorOptions) => {
|
|
419
|
+
hookRequire('@google/generative-ai', (exports: any) => {
|
|
420
|
+
patchGoogleGenAI(exports, options);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
hookRequire('@google-cloud/vertexai', (exports: any) => {
|
|
424
|
+
patchVertexAI(exports, options);
|
|
425
|
+
});
|
|
426
|
+
};
|