@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +527 -398
  3. package/dist/index.d.mts +5 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.global.js +1 -1
  6. package/dist/index.global.js.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +1 -1
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/lambda-handler.d.mts +13 -0
  12. package/dist/lambda-handler.d.ts +13 -0
  13. package/dist/lambda-handler.js +2 -0
  14. package/dist/lambda-handler.js.map +1 -0
  15. package/dist/lambda-handler.mjs +2 -0
  16. package/dist/lambda-handler.mjs.map +1 -0
  17. package/dist/register.js +1 -1
  18. package/dist/register.js.map +1 -1
  19. package/dist/register.mjs +1 -1
  20. package/dist/register.mjs.map +1 -1
  21. package/package.json +6 -1
  22. package/src/core/client.ts +57 -0
  23. package/src/core/transport.ts +20 -3
  24. package/src/core/types.ts +5 -1
  25. package/src/index.ts +4 -0
  26. package/src/instrumentation/amqplib.ts +371 -0
  27. package/src/instrumentation/anthropic.ts +245 -0
  28. package/src/instrumentation/aws-sdk.ts +403 -0
  29. package/src/instrumentation/azure-openai.ts +177 -0
  30. package/src/instrumentation/bunyan.ts +93 -0
  31. package/src/instrumentation/cassandra.ts +367 -0
  32. package/src/instrumentation/cohere.ts +227 -0
  33. package/src/instrumentation/connect.ts +200 -0
  34. package/src/instrumentation/dataloader.ts +291 -0
  35. package/src/instrumentation/dns.ts +220 -0
  36. package/src/instrumentation/firebase.ts +445 -0
  37. package/src/instrumentation/fs.ts +260 -0
  38. package/src/instrumentation/generic-pool.ts +317 -0
  39. package/src/instrumentation/google-genai.ts +426 -0
  40. package/src/instrumentation/graphql.ts +434 -0
  41. package/src/instrumentation/grpc.ts +666 -0
  42. package/src/instrumentation/hapi.ts +257 -0
  43. package/src/instrumentation/kafka.ts +360 -0
  44. package/src/instrumentation/knex.ts +249 -0
  45. package/src/instrumentation/lru-memoizer.ts +175 -0
  46. package/src/instrumentation/memcached.ts +190 -0
  47. package/src/instrumentation/mistral.ts +254 -0
  48. package/src/instrumentation/nestjs.ts +243 -0
  49. package/src/instrumentation/net.ts +171 -0
  50. package/src/instrumentation/openai.ts +281 -0
  51. package/src/instrumentation/pino.ts +170 -0
  52. package/src/instrumentation/restify.ts +213 -0
  53. package/src/instrumentation/runtime.ts +352 -0
  54. package/src/instrumentation/socketio.ts +272 -0
  55. package/src/instrumentation/tedious.ts +509 -0
  56. package/src/instrumentation/winston.ts +149 -0
  57. package/src/lambda-handler.ts +262 -0
  58. package/src/register.ts +22 -3
  59. package/src/wrappers/lambda.ts +417 -0
  60. package/tsup.config.ts +4 -4
  61. package/wiki.md +1693 -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
+ };