@senzops/apm-node 1.2.8 → 1.3.0
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 +9 -0
- package/README.md +479 -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/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 +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/register.ts +22 -3
- package/src/wrappers/lambda.ts +417 -0
- package/tsup.config.ts +3 -3
- package/wiki.md +1547 -852
|
@@ -0,0 +1,227 @@
|
|
|
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
|
+
// Cohere SDK Instrumentation
|
|
8
|
+
//
|
|
9
|
+
// Instruments the `cohere-ai` npm package for Cohere's NLP models
|
|
10
|
+
// (Command, Embed, Rerank, Classify, Summarize, etc.).
|
|
11
|
+
//
|
|
12
|
+
// Patches CohereClient.prototype (or CohereClientV2.prototype) methods:
|
|
13
|
+
// - chat() — conversational generation (Command R/R+)
|
|
14
|
+
// - chatStream() — streaming chat
|
|
15
|
+
// - generate() — text generation (legacy)
|
|
16
|
+
// - embed() — embedding generation
|
|
17
|
+
// - rerank() — semantic reranking
|
|
18
|
+
// - classify() — text classification
|
|
19
|
+
// - summarize() — text summarization
|
|
20
|
+
// - tokenize() — tokenization
|
|
21
|
+
// - detokenize() — detokenization
|
|
22
|
+
//
|
|
23
|
+
// Captured attributes (OTel GenAI semantic conventions):
|
|
24
|
+
// - gen_ai.system: 'cohere'
|
|
25
|
+
// - gen_ai.request.model: command-r-plus, embed-english-v3.0, etc.
|
|
26
|
+
// - gen_ai.operation.name: chat, generate, embed, rerank, etc.
|
|
27
|
+
// - gen_ai.usage.input_tokens: billed input tokens
|
|
28
|
+
// - gen_ai.usage.output_tokens: billed output tokens
|
|
29
|
+
// - gen_ai.response.finish_reason: COMPLETE, MAX_TOKENS, etc.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/** Methods to instrument with metadata extractors. */
|
|
33
|
+
const METHODS: {
|
|
34
|
+
name: string;
|
|
35
|
+
operation: string;
|
|
36
|
+
getModel: (args: any[]) => string | undefined;
|
|
37
|
+
extractUsage: (result: any) => Record<string, any>;
|
|
38
|
+
}[] = [
|
|
39
|
+
{
|
|
40
|
+
name: 'chat',
|
|
41
|
+
operation: 'chat',
|
|
42
|
+
getModel: (args) => args[0]?.model,
|
|
43
|
+
extractUsage: (result) => {
|
|
44
|
+
const meta: Record<string, any> = {};
|
|
45
|
+
// Cohere v2 chat response
|
|
46
|
+
if (result?.meta?.billedUnits) {
|
|
47
|
+
meta['gen_ai.usage.input_tokens'] = result.meta.billedUnits.inputTokens;
|
|
48
|
+
meta['gen_ai.usage.output_tokens'] = result.meta.billedUnits.outputTokens;
|
|
49
|
+
}
|
|
50
|
+
// Cohere v1 chat response
|
|
51
|
+
if (result?.meta?.tokens) {
|
|
52
|
+
meta['gen_ai.usage.input_tokens'] = meta['gen_ai.usage.input_tokens'] || result.meta.tokens.inputTokens;
|
|
53
|
+
meta['gen_ai.usage.output_tokens'] = meta['gen_ai.usage.output_tokens'] || result.meta.tokens.outputTokens;
|
|
54
|
+
}
|
|
55
|
+
if (result?.finishReason || result?.finish_reason) {
|
|
56
|
+
meta['gen_ai.response.finish_reason'] = result.finishReason || result.finish_reason;
|
|
57
|
+
}
|
|
58
|
+
return meta;
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'chatStream',
|
|
63
|
+
operation: 'chat.stream',
|
|
64
|
+
getModel: (args) => args[0]?.model,
|
|
65
|
+
extractUsage: () => ({}), // Stream — usage comes in final event
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'generate',
|
|
69
|
+
operation: 'generate',
|
|
70
|
+
getModel: (args) => args[0]?.model,
|
|
71
|
+
extractUsage: (result) => {
|
|
72
|
+
const meta: Record<string, any> = {};
|
|
73
|
+
if (result?.meta?.billedUnits) {
|
|
74
|
+
meta['gen_ai.usage.input_tokens'] = result.meta.billedUnits.inputTokens;
|
|
75
|
+
meta['gen_ai.usage.output_tokens'] = result.meta.billedUnits.outputTokens;
|
|
76
|
+
}
|
|
77
|
+
return meta;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'embed',
|
|
82
|
+
operation: 'embed',
|
|
83
|
+
getModel: (args) => args[0]?.model,
|
|
84
|
+
extractUsage: (result) => {
|
|
85
|
+
const meta: Record<string, any> = {};
|
|
86
|
+
if (result?.meta?.billedUnits) {
|
|
87
|
+
meta['gen_ai.usage.input_tokens'] = result.meta.billedUnits.inputTokens;
|
|
88
|
+
}
|
|
89
|
+
return meta;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'rerank',
|
|
94
|
+
operation: 'rerank',
|
|
95
|
+
getModel: (args) => args[0]?.model,
|
|
96
|
+
extractUsage: (result) => {
|
|
97
|
+
const meta: Record<string, any> = {};
|
|
98
|
+
if (result?.meta?.billedUnits) {
|
|
99
|
+
meta['gen_ai.usage.input_tokens'] = result.meta.billedUnits.searchUnits;
|
|
100
|
+
}
|
|
101
|
+
meta['cohere.results_count'] = result?.results?.length;
|
|
102
|
+
return meta;
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'classify',
|
|
107
|
+
operation: 'classify',
|
|
108
|
+
getModel: (args) => args[0]?.model,
|
|
109
|
+
extractUsage: () => ({}),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'summarize',
|
|
113
|
+
operation: 'summarize',
|
|
114
|
+
getModel: (args) => args[0]?.model,
|
|
115
|
+
extractUsage: (result) => {
|
|
116
|
+
const meta: Record<string, any> = {};
|
|
117
|
+
if (result?.meta?.billedUnits) {
|
|
118
|
+
meta['gen_ai.usage.input_tokens'] = result.meta.billedUnits.inputTokens;
|
|
119
|
+
meta['gen_ai.usage.output_tokens'] = result.meta.billedUnits.outputTokens;
|
|
120
|
+
}
|
|
121
|
+
return meta;
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'tokenize',
|
|
126
|
+
operation: 'tokenize',
|
|
127
|
+
getModel: (args) => args[0]?.model,
|
|
128
|
+
extractUsage: (result) => ({
|
|
129
|
+
'cohere.token_count': result?.tokens?.length,
|
|
130
|
+
}),
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'detokenize',
|
|
134
|
+
operation: 'detokenize',
|
|
135
|
+
getModel: (args) => args[0]?.model,
|
|
136
|
+
extractUsage: () => ({}),
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// CohereClient patching
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
const patchCohereClient = (proto: any, clientName: string, options?: SenzorOptions) => {
|
|
145
|
+
if (!proto) return;
|
|
146
|
+
|
|
147
|
+
for (const methodConfig of METHODS) {
|
|
148
|
+
if (typeof proto[methodConfig.name] !== 'function') continue;
|
|
149
|
+
|
|
150
|
+
patchMethod(
|
|
151
|
+
proto,
|
|
152
|
+
methodConfig.name,
|
|
153
|
+
`senzor.cohere.${clientName}.${methodConfig.name}`,
|
|
154
|
+
(original) =>
|
|
155
|
+
function patchedCohereMethod(this: any, ...args: any[]) {
|
|
156
|
+
const model = methodConfig.getModel(args);
|
|
157
|
+
|
|
158
|
+
const spanName = model
|
|
159
|
+
? `Cohere ${methodConfig.operation} ${model}`
|
|
160
|
+
: `Cohere ${methodConfig.operation}`;
|
|
161
|
+
|
|
162
|
+
const span = startCapturedSpan(
|
|
163
|
+
spanName,
|
|
164
|
+
'http',
|
|
165
|
+
{
|
|
166
|
+
'gen_ai.system': 'cohere',
|
|
167
|
+
'gen_ai.operation.name': methodConfig.operation,
|
|
168
|
+
'gen_ai.request.model': model,
|
|
169
|
+
library: 'cohere',
|
|
170
|
+
},
|
|
171
|
+
options
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (!span) return original.apply(this, args);
|
|
175
|
+
|
|
176
|
+
return runWithCapturedSpan(span, () => {
|
|
177
|
+
try {
|
|
178
|
+
const result = original.apply(this, args);
|
|
179
|
+
|
|
180
|
+
if (result && typeof result.then === 'function') {
|
|
181
|
+
return result.then(
|
|
182
|
+
(value: any) => {
|
|
183
|
+
span.end(0, methodConfig.extractUsage(value));
|
|
184
|
+
return value;
|
|
185
|
+
},
|
|
186
|
+
(error: any) => {
|
|
187
|
+
span.end(error?.statusCode || error?.status || 500, {
|
|
188
|
+
'error.message': error?.message,
|
|
189
|
+
'error.type': error?.name || 'CohereError',
|
|
190
|
+
});
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
span.end(0);
|
|
197
|
+
return result;
|
|
198
|
+
} catch (error: any) {
|
|
199
|
+
span.end(500, { 'error.message': error?.message });
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Public API
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
export const instrumentCohere = (options?: SenzorOptions) => {
|
|
213
|
+
hookRequire('cohere-ai', (exports: any) => {
|
|
214
|
+
// CohereClient (v1)
|
|
215
|
+
if (exports?.CohereClient?.prototype) {
|
|
216
|
+
patchCohereClient(exports.CohereClient.prototype, 'client', options);
|
|
217
|
+
}
|
|
218
|
+
// CohereClientV2 (v2)
|
|
219
|
+
if (exports?.CohereClientV2?.prototype) {
|
|
220
|
+
patchCohereClient(exports.CohereClientV2.prototype, 'clientV2', options);
|
|
221
|
+
}
|
|
222
|
+
// Default export
|
|
223
|
+
if (exports?.default?.prototype) {
|
|
224
|
+
patchCohereClient(exports.default.prototype, 'default', options);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
// Connect Instrumentation
|
|
8
|
+
//
|
|
9
|
+
// Instruments the `connect` middleware framework — the foundation that
|
|
10
|
+
// Express was originally built on. Many production apps still use
|
|
11
|
+
// Connect directly for lightweight HTTP services.
|
|
12
|
+
//
|
|
13
|
+
// Patches the connect app's use() method to wrap every middleware
|
|
14
|
+
// function with a span capturing execution time and errors.
|
|
15
|
+
//
|
|
16
|
+
// Connect middleware signature: (req, res, next) or (err, req, res, next)
|
|
17
|
+
//
|
|
18
|
+
// Captured attributes:
|
|
19
|
+
// - connect.type: 'middleware'
|
|
20
|
+
// - connect.name: middleware function name
|
|
21
|
+
// - connect.route: mount path (if provided)
|
|
22
|
+
// - framework: 'connect'
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Middleware wrapping
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
const wrapMiddleware = (
|
|
30
|
+
fn: Function,
|
|
31
|
+
route: string,
|
|
32
|
+
options?: SenzorOptions
|
|
33
|
+
): Function => {
|
|
34
|
+
if (typeof fn !== 'function') return fn;
|
|
35
|
+
if ((fn as any).__senzorWrapped) return fn;
|
|
36
|
+
|
|
37
|
+
const middlewareName = fn.name || 'anonymous';
|
|
38
|
+
const isErrorHandler = fn.length >= 4;
|
|
39
|
+
|
|
40
|
+
let wrapped: Function;
|
|
41
|
+
|
|
42
|
+
if (isErrorHandler) {
|
|
43
|
+
// Error-handling middleware: (err, req, res, next)
|
|
44
|
+
wrapped = function wrappedConnectErrorMiddleware(
|
|
45
|
+
this: any, err: any, req: any, res: any, next: any
|
|
46
|
+
) {
|
|
47
|
+
const span = startCapturedSpan(
|
|
48
|
+
`Connect error ${middlewareName}`,
|
|
49
|
+
'function',
|
|
50
|
+
{
|
|
51
|
+
'connect.type': 'error_middleware',
|
|
52
|
+
'connect.name': middlewareName,
|
|
53
|
+
'connect.route': route,
|
|
54
|
+
framework: 'connect',
|
|
55
|
+
},
|
|
56
|
+
options
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (!span) return fn.call(this, err, req, res, next);
|
|
60
|
+
|
|
61
|
+
return runWithCapturedSpan(span, () => {
|
|
62
|
+
const wrappedNext = function (...args: any[]) {
|
|
63
|
+
const hasError = args.length > 0 && args[0] instanceof Error;
|
|
64
|
+
span.end(hasError ? 500 : 0, hasError ? { 'error.message': args[0].message } : {});
|
|
65
|
+
return next?.(...args);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = fn.call(this, err, req, res, wrappedNext);
|
|
70
|
+
if (result && typeof result.then === 'function') {
|
|
71
|
+
return result.catch((error: any) => {
|
|
72
|
+
span.end(500, { 'error.message': error?.message });
|
|
73
|
+
throw error;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
span.end(500, { 'error.message': error?.message });
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
} else {
|
|
84
|
+
// Standard middleware: (req, res, next)
|
|
85
|
+
wrapped = function wrappedConnectMiddleware(
|
|
86
|
+
this: any, req: any, res: any, next: any
|
|
87
|
+
) {
|
|
88
|
+
const span = startCapturedSpan(
|
|
89
|
+
`Connect ${middlewareName}`,
|
|
90
|
+
'function',
|
|
91
|
+
{
|
|
92
|
+
'connect.type': 'middleware',
|
|
93
|
+
'connect.name': middlewareName,
|
|
94
|
+
'connect.route': route,
|
|
95
|
+
'http.route': route !== '/' ? route : undefined,
|
|
96
|
+
framework: 'connect',
|
|
97
|
+
},
|
|
98
|
+
options
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!span) return fn.call(this, req, res, next);
|
|
102
|
+
|
|
103
|
+
return runWithCapturedSpan(span, () => {
|
|
104
|
+
const wrappedNext = function (...args: any[]) {
|
|
105
|
+
const hasError = args.length > 0 && args[0] instanceof Error;
|
|
106
|
+
span.end(hasError ? 500 : 0, hasError ? { 'error.message': args[0].message } : {});
|
|
107
|
+
return next?.(...args);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const result = fn.call(this, req, res, wrappedNext);
|
|
112
|
+
if (result && typeof result.then === 'function') {
|
|
113
|
+
return result.catch((error: any) => {
|
|
114
|
+
span.end(500, { 'error.message': error?.message });
|
|
115
|
+
throw error;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return result;
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
span.end(500, { 'error.message': error?.message });
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Preserve original function length for Connect's error handler detection
|
|
128
|
+
Object.defineProperty(wrapped, 'length', { value: fn.length });
|
|
129
|
+
(wrapped as any).__senzorWrapped = true;
|
|
130
|
+
|
|
131
|
+
return wrapped;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// app.use() patching
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
const patchConnectApp = (connectModule: any, options?: SenzorOptions) => {
|
|
139
|
+
if (typeof connectModule !== 'function') return;
|
|
140
|
+
|
|
141
|
+
// connect() returns an app. We need to patch the app's prototype.
|
|
142
|
+
// Connect apps have use() on their prototype chain via proto.
|
|
143
|
+
|
|
144
|
+
// Create a probe app to get the prototype
|
|
145
|
+
let appProto: any;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const app = connectModule();
|
|
149
|
+
appProto = Object.getPrototypeOf(app);
|
|
150
|
+
} catch { }
|
|
151
|
+
|
|
152
|
+
if (!appProto) return;
|
|
153
|
+
|
|
154
|
+
// Patch use()
|
|
155
|
+
if (typeof appProto.use === 'function') {
|
|
156
|
+
patchMethod(
|
|
157
|
+
appProto,
|
|
158
|
+
'use',
|
|
159
|
+
'senzor.connect.app.use',
|
|
160
|
+
(original) =>
|
|
161
|
+
function patchedUse(this: any, ...args: any[]) {
|
|
162
|
+
// use() accepts:
|
|
163
|
+
// use(fn)
|
|
164
|
+
// use(route, fn)
|
|
165
|
+
// use(route, fn1, fn2, ...)
|
|
166
|
+
|
|
167
|
+
let route = '/';
|
|
168
|
+
let startIdx = 0;
|
|
169
|
+
|
|
170
|
+
if (typeof args[0] === 'string') {
|
|
171
|
+
route = args[0];
|
|
172
|
+
startIdx = 1;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Wrap all function arguments
|
|
176
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
177
|
+
if (typeof args[i] === 'function') {
|
|
178
|
+
args[i] = wrapMiddleware(args[i], route, options);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return original.apply(this, args);
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Public API
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
export const instrumentConnect = (options?: SenzorOptions) => {
|
|
193
|
+
hookRequire('connect', (exports: any) => {
|
|
194
|
+
patchConnectApp(exports, options);
|
|
195
|
+
|
|
196
|
+
if (exports?.default) {
|
|
197
|
+
patchConnectApp(exports.default, options);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
};
|