@senzops/apm-node 1.2.7 → 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.
Files changed (55) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +479 -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/register.js +1 -1
  12. package/dist/register.js.map +1 -1
  13. package/dist/register.mjs +1 -1
  14. package/dist/register.mjs.map +1 -1
  15. package/package.json +1 -1
  16. package/src/core/client.ts +57 -0
  17. package/src/core/context.ts +71 -9
  18. package/src/core/transport.ts +20 -3
  19. package/src/core/types.ts +5 -1
  20. package/src/index.ts +4 -0
  21. package/src/instrumentation/amqplib.ts +371 -0
  22. package/src/instrumentation/anthropic.ts +245 -0
  23. package/src/instrumentation/aws-sdk.ts +403 -0
  24. package/src/instrumentation/azure-openai.ts +177 -0
  25. package/src/instrumentation/bunyan.ts +93 -0
  26. package/src/instrumentation/cassandra.ts +367 -0
  27. package/src/instrumentation/cohere.ts +227 -0
  28. package/src/instrumentation/connect.ts +200 -0
  29. package/src/instrumentation/dataloader.ts +291 -0
  30. package/src/instrumentation/dns.ts +220 -0
  31. package/src/instrumentation/firebase.ts +445 -0
  32. package/src/instrumentation/fs.ts +260 -0
  33. package/src/instrumentation/generic-pool.ts +317 -0
  34. package/src/instrumentation/google-genai.ts +426 -0
  35. package/src/instrumentation/graphql.ts +434 -0
  36. package/src/instrumentation/grpc.ts +666 -0
  37. package/src/instrumentation/hapi.ts +257 -0
  38. package/src/instrumentation/kafka.ts +360 -0
  39. package/src/instrumentation/knex.ts +249 -0
  40. package/src/instrumentation/lru-memoizer.ts +175 -0
  41. package/src/instrumentation/memcached.ts +190 -0
  42. package/src/instrumentation/mistral.ts +254 -0
  43. package/src/instrumentation/nestjs.ts +243 -0
  44. package/src/instrumentation/net.ts +171 -0
  45. package/src/instrumentation/openai.ts +281 -0
  46. package/src/instrumentation/pino.ts +170 -0
  47. package/src/instrumentation/restify.ts +213 -0
  48. package/src/instrumentation/runtime.ts +352 -0
  49. package/src/instrumentation/socketio.ts +272 -0
  50. package/src/instrumentation/tedious.ts +509 -0
  51. package/src/instrumentation/winston.ts +149 -0
  52. package/src/register.ts +22 -3
  53. package/src/wrappers/lambda.ts +417 -0
  54. package/tsup.config.ts +3 -3
  55. package/wiki.md +1547 -852
@@ -0,0 +1,257 @@
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
+ // Hapi Instrumentation
8
+ //
9
+ // Instruments @hapi/hapi at the route registration layer:
10
+ // - Server.prototype.route() — wraps route handlers at registration time
11
+ // so every request generates a span with:
12
+ // hapi.route, hapi.method, http.route
13
+ //
14
+ // Captures the full handler execution including any route-level validation,
15
+ // payload parsing, and response formatting done by Hapi.
16
+ //
17
+ // The handler signature is always: (request, h) => response
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /** HTTP method enum to string mapping for older Hapi versions. */
21
+ const METHOD_MAP: Record<number, string> = {
22
+ 0: 'GET', 1: 'HEAD', 2: 'POST', 3: 'PUT',
23
+ 4: 'DELETE', 5: 'OPTIONS', 6: 'PATCH',
24
+ };
25
+
26
+ const normalizeMethod = (method: any): string => {
27
+ if (typeof method === 'string') return method.toUpperCase();
28
+ if (typeof method === 'number') return METHOD_MAP[method] || 'UNKNOWN';
29
+ return 'UNKNOWN';
30
+ };
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Handler wrapping
34
+ // ---------------------------------------------------------------------------
35
+
36
+ const wrapHandler = (
37
+ handler: Function,
38
+ routePath: string,
39
+ routeMethod: string,
40
+ options?: SenzorOptions
41
+ ): Function => {
42
+ if (typeof handler !== 'function') return handler;
43
+
44
+ return function wrappedHapiHandler(this: any, request: any, h: any) {
45
+ const method = normalizeMethod(routeMethod);
46
+ const path = routePath || request?.route?.path || request?.path || '/';
47
+
48
+ const span = startCapturedSpan(
49
+ `Hapi ${method} ${path}`,
50
+ 'function',
51
+ {
52
+ 'hapi.type': 'route_handler',
53
+ 'hapi.route': path,
54
+ 'hapi.method': method,
55
+ 'http.route': path,
56
+ framework: 'hapi',
57
+ },
58
+ options
59
+ );
60
+
61
+ if (!span) return handler.call(this, request, h);
62
+
63
+ return runWithCapturedSpan(span, () => {
64
+ try {
65
+ const result = handler.call(this, request, h);
66
+
67
+ if (result && typeof result.then === 'function') {
68
+ return result.then(
69
+ (val: any) => {
70
+ const statusCode = val?.statusCode || request?.response?.statusCode || 200;
71
+ span.end(statusCode >= 400 ? statusCode : 0, {
72
+ 'http.response.status_code': statusCode,
73
+ });
74
+ return val;
75
+ },
76
+ (error: any) => {
77
+ const statusCode = error?.output?.statusCode || 500;
78
+ span.end(statusCode, {
79
+ 'error.message': error?.message,
80
+ 'error.type': error?.name || 'Error',
81
+ 'http.response.status_code': statusCode,
82
+ });
83
+ throw error;
84
+ }
85
+ );
86
+ }
87
+
88
+ span.end(0);
89
+ return result;
90
+ } catch (error: any) {
91
+ const statusCode = error?.output?.statusCode || 500;
92
+ span.end(statusCode, {
93
+ 'error.message': error?.message,
94
+ 'error.type': error?.name || 'Error',
95
+ 'http.response.status_code': statusCode,
96
+ });
97
+ throw error;
98
+ }
99
+ });
100
+ };
101
+ };
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Route config processing
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /**
108
+ * Process a single route configuration object and wrap its handler.
109
+ * Hapi route configs can have handler at config.handler or config.options.handler.
110
+ */
111
+ const processRouteConfig = (config: any, options?: SenzorOptions): any => {
112
+ if (!config) return config;
113
+
114
+ const method = config.method || 'GET';
115
+ const path = config.path || '/';
116
+
117
+ // Clone config to avoid mutating user's original object
118
+ const wrapped = { ...config };
119
+
120
+ // Handler can be at config.handler or config.options.handler
121
+ if (typeof wrapped.handler === 'function') {
122
+ wrapped.handler = wrapHandler(wrapped.handler, path, method, options);
123
+ } else if (wrapped.options && typeof wrapped.options.handler === 'function') {
124
+ wrapped.options = { ...wrapped.options };
125
+ wrapped.options.handler = wrapHandler(wrapped.options.handler, path, method, options);
126
+ } else if (wrapped.config && typeof wrapped.config.handler === 'function') {
127
+ // Legacy Hapi config location
128
+ wrapped.config = { ...wrapped.config };
129
+ wrapped.config.handler = wrapHandler(wrapped.config.handler, path, method, options);
130
+ }
131
+
132
+ return wrapped;
133
+ };
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Server.route() patching
137
+ // ---------------------------------------------------------------------------
138
+
139
+ const patchServerRoute = (serverProto: any, options?: SenzorOptions) => {
140
+ if (!serverProto) return;
141
+
142
+ patchMethod(
143
+ serverProto,
144
+ 'route',
145
+ 'senzor.hapi.server.route',
146
+ (original) =>
147
+ function patchedRoute(this: any, routeConfig: any) {
148
+ // server.route() accepts a single config or an array of configs
149
+ if (Array.isArray(routeConfig)) {
150
+ const wrappedConfigs = routeConfig.map((config: any) =>
151
+ processRouteConfig(config, options)
152
+ );
153
+ return original.call(this, wrappedConfigs);
154
+ }
155
+
156
+ const wrappedConfig = processRouteConfig(routeConfig, options);
157
+ return original.call(this, wrappedConfig);
158
+ }
159
+ );
160
+ };
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Extension point patching (for lifecycle span visibility)
164
+ // ---------------------------------------------------------------------------
165
+
166
+ const patchServerExt = (serverProto: any, options?: SenzorOptions) => {
167
+ if (!serverProto) return;
168
+
169
+ patchMethod(
170
+ serverProto,
171
+ 'ext',
172
+ 'senzor.hapi.server.ext',
173
+ (original) =>
174
+ function patchedExt(this: any, event: any, method?: any, extOptions?: any) {
175
+ // ext() can be called as:
176
+ // ext(event, method, options) — single extension
177
+ // ext([{ type, method, options }]) — array of extensions
178
+ // ext({ type, method, options }) — single object
179
+
180
+ if (typeof event === 'string' && typeof method === 'function') {
181
+ const eventName = event;
182
+ const originalMethod = method;
183
+
184
+ const wrappedMethod = function (this: any, request: any, h: any) {
185
+ const span = startCapturedSpan(
186
+ `Hapi ext ${eventName}`,
187
+ 'function',
188
+ {
189
+ 'hapi.type': 'lifecycle_hook',
190
+ 'hapi.hook': eventName,
191
+ framework: 'hapi',
192
+ },
193
+ options
194
+ );
195
+
196
+ if (!span) return originalMethod.call(this, request, h);
197
+
198
+ return runWithCapturedSpan(span, () => {
199
+ try {
200
+ const result = originalMethod.call(this, request, h);
201
+ if (result && typeof result.then === 'function') {
202
+ return result.then(
203
+ (val: any) => { span.end(0); return val; },
204
+ (err: any) => {
205
+ span.end(500, { 'error.message': err?.message });
206
+ throw err;
207
+ }
208
+ );
209
+ }
210
+ span.end(0);
211
+ return result;
212
+ } catch (err: any) {
213
+ span.end(500, { 'error.message': err?.message });
214
+ throw err;
215
+ }
216
+ });
217
+ };
218
+
219
+ return original.call(this, event, wrappedMethod, extOptions);
220
+ }
221
+
222
+ // For array/object form, pass through unchanged (avoid complex nesting)
223
+ return original.call(this, event, method, extOptions);
224
+ }
225
+ );
226
+ };
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // Public API
230
+ // ---------------------------------------------------------------------------
231
+
232
+ export const instrumentHapi = (options?: SenzorOptions) => {
233
+ hookRequire('@hapi/hapi', (exports: any) => {
234
+ // Hapi exports a Server class
235
+ const Server = exports?.Server || exports?.server?.Server;
236
+
237
+ if (Server?.prototype) {
238
+ patchServerRoute(Server.prototype, options);
239
+
240
+ // Only patch ext if framework spans are enabled
241
+ if (options?.frameworkSpans !== false) {
242
+ patchServerExt(Server.prototype, options);
243
+ }
244
+ }
245
+ });
246
+
247
+ // Also try the legacy 'hapi' package name (pre-scoped)
248
+ hookRequire('hapi', (exports: any) => {
249
+ const Server = exports?.Server || exports?.server?.Server;
250
+ if (Server?.prototype) {
251
+ patchServerRoute(Server.prototype, options);
252
+ if (options?.frameworkSpans !== false) {
253
+ patchServerExt(Server.prototype, options);
254
+ }
255
+ }
256
+ });
257
+ };
@@ -0,0 +1,360 @@
1
+ import { Context } from '../core/context';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { hookRequire } from './hook';
4
+ import { patchMethod } from './patch';
5
+ import { runWithCapturedSpan, startCapturedSpan } from './span';
6
+ import { generateTraceparent, parseTraceparent } from '../utils/traceContext';
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Kafka (kafkajs) Instrumentation
10
+ //
11
+ // Instruments the kafkajs library:
12
+ // - Producer: send(), sendBatch() — outbound publish spans
13
+ // - Consumer: run() — wraps eachMessage/eachBatch callbacks with spans
14
+ // - Context propagation via message headers (traceparent, x-senzor-*)
15
+ //
16
+ // Follows OTel messaging semantic conventions:
17
+ // messaging.system = kafka
18
+ // messaging.destination.name = topic
19
+ // messaging.operation.name = publish | receive
20
+ // messaging.kafka.consumer.group
21
+ // messaging.kafka.message.offset
22
+ // messaging.batch.message_count
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const TRACEPARENT_KEY = 'traceparent';
26
+ const SENZOR_TRACE_KEY = 'x-senzor-trace-id';
27
+ const SENZOR_SPAN_KEY = 'x-senzor-parent-span-id';
28
+
29
+ /** Inject trace context into Kafka message headers. */
30
+ const injectHeaders = (
31
+ headers: Record<string, any> | undefined,
32
+ traceId: string,
33
+ spanId: string
34
+ ): Record<string, any> => {
35
+ const h = headers ? { ...headers } : {};
36
+ h[TRACEPARENT_KEY] = Buffer.from(generateTraceparent(traceId, spanId));
37
+ h[SENZOR_TRACE_KEY] = Buffer.from(traceId);
38
+ h[SENZOR_SPAN_KEY] = Buffer.from(spanId);
39
+ return h;
40
+ };
41
+
42
+ /** Extract trace context from incoming Kafka message headers. */
43
+ const extractHeaders = (
44
+ headers: Record<string, any> | undefined
45
+ ): { traceId?: string; parentSpanId?: string } => {
46
+ if (!headers) return {};
47
+
48
+ try {
49
+ const tp = headers[TRACEPARENT_KEY];
50
+ if (tp) {
51
+ const tpStr = Buffer.isBuffer(tp) ? tp.toString() : String(tp);
52
+ const parsed = parseTraceparent(tpStr);
53
+ if (parsed) return parsed;
54
+ }
55
+
56
+ const traceId = headers[SENZOR_TRACE_KEY];
57
+ const spanId = headers[SENZOR_SPAN_KEY];
58
+ return {
59
+ traceId: traceId ? (Buffer.isBuffer(traceId) ? traceId.toString() : String(traceId)) : undefined,
60
+ parentSpanId: spanId ? (Buffer.isBuffer(spanId) ? spanId.toString() : String(spanId)) : undefined,
61
+ };
62
+ } catch {
63
+ return {};
64
+ }
65
+ };
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Producer patching
69
+ // ---------------------------------------------------------------------------
70
+
71
+ const patchProducer = (producer: any, options?: SenzorOptions) => {
72
+ // send({ topic, messages, ... })
73
+ patchMethod(
74
+ producer,
75
+ 'send',
76
+ 'senzor.kafka.producer.send',
77
+ (original) =>
78
+ function patchedSend(this: any, payload: any) {
79
+ const trace = Context.current();
80
+ if (!trace) return original.call(this, payload);
81
+
82
+ const topic = payload?.topic || 'unknown';
83
+ const messageCount = payload?.messages?.length || 0;
84
+
85
+ const span = startCapturedSpan(
86
+ `Kafka publish ${topic}`,
87
+ 'messaging',
88
+ {
89
+ 'messaging.system': 'kafka',
90
+ 'messaging.destination.name': topic,
91
+ 'messaging.operation.name': 'publish',
92
+ 'messaging.batch.message_count': messageCount,
93
+ },
94
+ options
95
+ );
96
+
97
+ if (!span) return original.call(this, payload);
98
+
99
+ // Inject trace context into each message's headers
100
+ if (payload?.messages && Array.isArray(payload.messages)) {
101
+ payload = {
102
+ ...payload,
103
+ messages: payload.messages.map((msg: any) => ({
104
+ ...msg,
105
+ headers: injectHeaders(msg.headers, trace.id, span.spanId),
106
+ })),
107
+ };
108
+ }
109
+
110
+ return runWithCapturedSpan(span, () => {
111
+ const result = original.call(this, payload);
112
+
113
+ if (result && typeof result.then === 'function') {
114
+ return result.then(
115
+ (res: any) => {
116
+ span.end(0, {
117
+ 'messaging.kafka.partitions': Array.isArray(res)
118
+ ? res.map((r: any) => r.partition).join(',')
119
+ : undefined,
120
+ });
121
+ return res;
122
+ },
123
+ (error: any) => {
124
+ span.end(500, {
125
+ 'error.message': error?.message,
126
+ 'error.type': error?.name || 'KafkaError',
127
+ });
128
+ throw error;
129
+ }
130
+ );
131
+ }
132
+
133
+ span.end(0);
134
+ return result;
135
+ });
136
+ }
137
+ );
138
+
139
+ // sendBatch({ topicMessages: [{ topic, messages }], ... })
140
+ patchMethod(
141
+ producer,
142
+ 'sendBatch',
143
+ 'senzor.kafka.producer.sendBatch',
144
+ (original) =>
145
+ function patchedSendBatch(this: any, payload: any) {
146
+ const trace = Context.current();
147
+ if (!trace) return original.call(this, payload);
148
+
149
+ const topicMessages = payload?.topicMessages || [];
150
+ const topics = topicMessages.map((tm: any) => tm.topic).join(',');
151
+ const totalMessages = topicMessages.reduce(
152
+ (sum: number, tm: any) => sum + (tm.messages?.length || 0),
153
+ 0
154
+ );
155
+
156
+ const span = startCapturedSpan(
157
+ `Kafka publishBatch ${topics}`,
158
+ 'messaging',
159
+ {
160
+ 'messaging.system': 'kafka',
161
+ 'messaging.destination.name': topics,
162
+ 'messaging.operation.name': 'publish',
163
+ 'messaging.batch.message_count': totalMessages,
164
+ 'messaging.kafka.batch_topic_count': topicMessages.length,
165
+ },
166
+ options
167
+ );
168
+
169
+ if (!span) return original.call(this, payload);
170
+
171
+ // Inject trace context into all messages
172
+ if (topicMessages.length > 0) {
173
+ payload = {
174
+ ...payload,
175
+ topicMessages: topicMessages.map((tm: any) => ({
176
+ ...tm,
177
+ messages: (tm.messages || []).map((msg: any) => ({
178
+ ...msg,
179
+ headers: injectHeaders(msg.headers, trace.id, span.spanId),
180
+ })),
181
+ })),
182
+ };
183
+ }
184
+
185
+ return runWithCapturedSpan(span, () => {
186
+ const result = original.call(this, payload);
187
+
188
+ if (result && typeof result.then === 'function') {
189
+ return result.then(
190
+ (res: any) => {
191
+ span.end(0);
192
+ return res;
193
+ },
194
+ (error: any) => {
195
+ span.end(500, {
196
+ 'error.message': error?.message,
197
+ 'error.type': error?.name || 'KafkaError',
198
+ });
199
+ throw error;
200
+ }
201
+ );
202
+ }
203
+
204
+ span.end(0);
205
+ return result;
206
+ });
207
+ }
208
+ );
209
+ };
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Consumer patching
213
+ // ---------------------------------------------------------------------------
214
+
215
+ const patchConsumer = (consumer: any, options?: SenzorOptions) => {
216
+ patchMethod(
217
+ consumer,
218
+ 'run',
219
+ 'senzor.kafka.consumer.run',
220
+ (original) =>
221
+ function patchedRun(this: any, config: any) {
222
+ if (!config) return original.call(this, config);
223
+
224
+ const wrappedConfig = { ...config };
225
+
226
+ // Wrap eachMessage
227
+ if (typeof config.eachMessage === 'function') {
228
+ const originalHandler = config.eachMessage;
229
+ wrappedConfig.eachMessage = async (payload: any) => {
230
+ const { topic, partition, message } = payload;
231
+ const parentCtx = extractHeaders(message?.headers);
232
+
233
+ const span = startCapturedSpan(
234
+ `Kafka receive ${topic}`,
235
+ 'messaging',
236
+ {
237
+ 'messaging.system': 'kafka',
238
+ 'messaging.destination.name': topic,
239
+ 'messaging.operation.name': 'receive',
240
+ 'messaging.kafka.partition': partition,
241
+ 'messaging.kafka.message.offset': message?.offset,
242
+ 'messaging.kafka.message.key': message?.key?.toString(),
243
+ ...(parentCtx.traceId ? { 'messaging.parent_trace_id': parentCtx.traceId } : {}),
244
+ },
245
+ options
246
+ );
247
+
248
+ if (!span) return originalHandler(payload);
249
+
250
+ return runWithCapturedSpan(span, async () => {
251
+ try {
252
+ const result = await originalHandler(payload);
253
+ span.end(0);
254
+ return result;
255
+ } catch (error: any) {
256
+ span.end(500, {
257
+ 'error.message': error?.message,
258
+ 'error.type': error?.name || 'Error',
259
+ });
260
+ throw error;
261
+ }
262
+ });
263
+ };
264
+ }
265
+
266
+ // Wrap eachBatch
267
+ if (typeof config.eachBatch === 'function') {
268
+ const originalBatchHandler = config.eachBatch;
269
+ wrappedConfig.eachBatch = async (payload: any) => {
270
+ const { batch } = payload;
271
+ const topic = batch?.topic || 'unknown';
272
+ const messageCount = batch?.messages?.length || 0;
273
+
274
+ const span = startCapturedSpan(
275
+ `Kafka receiveBatch ${topic}`,
276
+ 'messaging',
277
+ {
278
+ 'messaging.system': 'kafka',
279
+ 'messaging.destination.name': topic,
280
+ 'messaging.operation.name': 'receive',
281
+ 'messaging.kafka.partition': batch?.partition,
282
+ 'messaging.batch.message_count': messageCount,
283
+ 'messaging.kafka.first_offset': batch?.messages?.[0]?.offset,
284
+ 'messaging.kafka.last_offset': batch?.messages?.[messageCount - 1]?.offset,
285
+ },
286
+ options
287
+ );
288
+
289
+ if (!span) return originalBatchHandler(payload);
290
+
291
+ return runWithCapturedSpan(span, async () => {
292
+ try {
293
+ const result = await originalBatchHandler(payload);
294
+ span.end(0);
295
+ return result;
296
+ } catch (error: any) {
297
+ span.end(500, {
298
+ 'error.message': error?.message,
299
+ 'error.type': error?.name || 'Error',
300
+ });
301
+ throw error;
302
+ }
303
+ });
304
+ };
305
+ }
306
+
307
+ return original.call(this, wrappedConfig);
308
+ }
309
+ );
310
+ };
311
+
312
+ // ---------------------------------------------------------------------------
313
+ // Kafka class patching (wraps factory methods)
314
+ // ---------------------------------------------------------------------------
315
+
316
+ const patchKafkaClass = (kafkaModule: any, options?: SenzorOptions) => {
317
+ const KafkaClass = kafkaModule?.Kafka;
318
+ if (!KafkaClass?.prototype) return;
319
+
320
+ // Wrap producer() factory
321
+ patchMethod(
322
+ KafkaClass.prototype,
323
+ 'producer',
324
+ 'senzor.kafka.producer',
325
+ (original) =>
326
+ function patchedProducerFactory(this: any, ...args: any[]) {
327
+ const producer = original.apply(this, args);
328
+ if (producer) patchProducer(producer, options);
329
+ return producer;
330
+ }
331
+ );
332
+
333
+ // Wrap consumer() factory
334
+ patchMethod(
335
+ KafkaClass.prototype,
336
+ 'consumer',
337
+ 'senzor.kafka.consumer',
338
+ (original) =>
339
+ function patchedConsumerFactory(this: any, ...args: any[]) {
340
+ const consumer = original.apply(this, args);
341
+ if (consumer) patchConsumer(consumer, options);
342
+ return consumer;
343
+ }
344
+ );
345
+ };
346
+
347
+ // ---------------------------------------------------------------------------
348
+ // Public API
349
+ // ---------------------------------------------------------------------------
350
+
351
+ export const instrumentKafka = (options?: SenzorOptions) => {
352
+ hookRequire('kafkajs', (exports: any) => {
353
+ patchKafkaClass(exports, options);
354
+
355
+ // Also handle default export
356
+ if (exports?.default?.Kafka) {
357
+ patchKafkaClass(exports.default, options);
358
+ }
359
+ });
360
+ };