@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,352 @@
1
+ import { isNode } from '../core/runtime';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Runtime Metrics Collector
5
+ //
6
+ // Collects Node.js runtime health metrics at configurable intervals:
7
+ // - Event loop lag (delay)
8
+ // - Event loop utilization (ELU) — Node.js 14.10+
9
+ // - Garbage collection duration and frequency
10
+ // - Heap memory usage and allocation
11
+ // - Active handles and requests count
12
+ // - CPU usage (user + system)
13
+ //
14
+ // These metrics are sent as a separate payload type to the APM ingest
15
+ // endpoint, enabling runtime health dashboards.
16
+ //
17
+ // Follows OTel Runtime Metrics semantic conventions where applicable.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface RuntimeMetricsPayload {
21
+ timestamp: string;
22
+ metrics: RuntimeMetrics;
23
+ }
24
+
25
+ export interface RuntimeMetrics {
26
+ // Event Loop
27
+ eventLoop: {
28
+ lagMs: number; // Current event loop lag in milliseconds
29
+ lagP50Ms?: number; // p50 lag (if histogram available)
30
+ lagP99Ms?: number; // p99 lag (if histogram available)
31
+ utilizationPercent?: number; // Event loop utilization 0-100 (Node 14.10+)
32
+ };
33
+
34
+ // Garbage Collection
35
+ gc: {
36
+ totalDurationMs: number; // Total GC time since last report
37
+ totalCount: number; // Total GC pauses since last report
38
+ majorCount: number; // Major (mark-sweep) GC count
39
+ minorCount: number; // Minor (scavenge) GC count
40
+ incrementalCount: number; // Incremental marking count
41
+ weakCallbackCount: number; // Weak callback processing count
42
+ };
43
+
44
+ // Memory
45
+ memory: {
46
+ heapUsedBytes: number;
47
+ heapTotalBytes: number;
48
+ externalBytes: number;
49
+ arrayBuffersBytes: number;
50
+ rssBytes: number;
51
+ heapUsedPercent: number; // heapUsed / heapTotal * 100
52
+ };
53
+
54
+ // Process
55
+ process: {
56
+ activeHandles: number;
57
+ activeRequests: number;
58
+ cpuUserUs: number; // CPU user time delta since last report (microseconds)
59
+ cpuSystemUs: number; // CPU system time delta since last report (microseconds)
60
+ uptimeSeconds: number;
61
+ };
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // GC Observer (uses perf_hooks PerformanceObserver)
66
+ // ---------------------------------------------------------------------------
67
+
68
+ interface GcStats {
69
+ totalDurationMs: number;
70
+ totalCount: number;
71
+ majorCount: number; // kind=2 (MarkSweepCompact)
72
+ minorCount: number; // kind=1 (Scavenge)
73
+ incrementalCount: number; // kind=4 (IncrementalMarking)
74
+ weakCallbackCount: number; // kind=8 (ProcessWeakCallbacks)
75
+ }
76
+
77
+ const GC_KINDS: Record<number, keyof Pick<GcStats, 'majorCount' | 'minorCount' | 'incrementalCount' | 'weakCallbackCount'>> = {
78
+ 1: 'minorCount',
79
+ 2: 'majorCount',
80
+ 4: 'incrementalCount',
81
+ 8: 'weakCallbackCount',
82
+ };
83
+
84
+ class GcObserver {
85
+ private stats: GcStats = {
86
+ totalDurationMs: 0,
87
+ totalCount: 0,
88
+ majorCount: 0,
89
+ minorCount: 0,
90
+ incrementalCount: 0,
91
+ weakCallbackCount: 0,
92
+ };
93
+ private observer: any = null;
94
+
95
+ start() {
96
+ try {
97
+ const { PerformanceObserver } = require('perf_hooks');
98
+
99
+ this.observer = new PerformanceObserver((list: any) => {
100
+ for (const entry of list.getEntries()) {
101
+ this.stats.totalDurationMs += entry.duration;
102
+ this.stats.totalCount++;
103
+
104
+ const kindKey = GC_KINDS[entry.detail?.kind ?? entry.kind];
105
+ if (kindKey) {
106
+ this.stats[kindKey]++;
107
+ }
108
+ }
109
+ });
110
+
111
+ this.observer.observe({ type: 'gc', buffered: true });
112
+ } catch {
113
+ // perf_hooks or gc observation not available
114
+ }
115
+ }
116
+
117
+ /** Take and reset collected GC stats since last call. */
118
+ take(): GcStats {
119
+ const snapshot = { ...this.stats };
120
+ this.stats = {
121
+ totalDurationMs: 0,
122
+ totalCount: 0,
123
+ majorCount: 0,
124
+ minorCount: 0,
125
+ incrementalCount: 0,
126
+ weakCallbackCount: 0,
127
+ };
128
+ return snapshot;
129
+ }
130
+
131
+ stop() {
132
+ try {
133
+ this.observer?.disconnect();
134
+ } catch { }
135
+ this.observer = null;
136
+ }
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // Event Loop Lag Measurement
141
+ // ---------------------------------------------------------------------------
142
+
143
+ class EventLoopLagMeter {
144
+ private lastCheck = 0;
145
+ private lagMs = 0;
146
+ private timer: any = null;
147
+ private monitoringHistogram: any = null;
148
+
149
+ start() {
150
+ // High-resolution lag sampling via setImmediate
151
+ this.lastCheck = performance.now();
152
+ this.scheduleSample();
153
+
154
+ // Try to use monitorEventLoopDelay for histogram (Node 12+)
155
+ try {
156
+ const { monitorEventLoopDelay } = require('perf_hooks');
157
+ this.monitoringHistogram = monitorEventLoopDelay({ resolution: 20 });
158
+ this.monitoringHistogram.enable();
159
+ } catch { }
160
+ }
161
+
162
+ private scheduleSample() {
163
+ this.timer = setTimeout(() => {
164
+ const now = performance.now();
165
+ // Timer was scheduled for ~100ms; anything above that is lag
166
+ const elapsed = now - this.lastCheck;
167
+ this.lagMs = Math.max(0, elapsed - 100);
168
+ this.lastCheck = now;
169
+ this.scheduleSample();
170
+ }, 100);
171
+
172
+ if (this.timer && typeof this.timer.unref === 'function') {
173
+ this.timer.unref();
174
+ }
175
+ }
176
+
177
+ take(): { lagMs: number; lagP50Ms?: number; lagP99Ms?: number } {
178
+ const result: any = { lagMs: Math.round(this.lagMs * 100) / 100 };
179
+
180
+ if (this.monitoringHistogram) {
181
+ try {
182
+ // percentile() returns nanoseconds
183
+ result.lagP50Ms = Math.round(this.monitoringHistogram.percentile(50) / 1e6 * 100) / 100;
184
+ result.lagP99Ms = Math.round(this.monitoringHistogram.percentile(99) / 1e6 * 100) / 100;
185
+ this.monitoringHistogram.reset();
186
+ } catch { }
187
+ }
188
+
189
+ return result;
190
+ }
191
+
192
+ stop() {
193
+ if (this.timer) {
194
+ clearTimeout(this.timer);
195
+ this.timer = null;
196
+ }
197
+ try {
198
+ this.monitoringHistogram?.disable();
199
+ } catch { }
200
+ }
201
+ }
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Event Loop Utilization (Node 14.10+)
205
+ // ---------------------------------------------------------------------------
206
+
207
+ class EventLoopUtilization {
208
+ private elu1: any = null;
209
+ private getELU: (() => any) | null = null;
210
+
211
+ start() {
212
+ try {
213
+ const { performance: perfHooks } = require('perf_hooks');
214
+ if (typeof perfHooks.eventLoopUtilization === 'function') {
215
+ this.getELU = () => perfHooks.eventLoopUtilization();
216
+ this.elu1 = this.getELU();
217
+ }
218
+ } catch { }
219
+ }
220
+
221
+ take(): number | undefined {
222
+ if (!this.getELU || !this.elu1) return undefined;
223
+ try {
224
+ const elu2 = this.getELU();
225
+ const { performance: perfHooks } = require('perf_hooks');
226
+ const util = perfHooks.eventLoopUtilization(this.elu1, elu2);
227
+ this.elu1 = elu2;
228
+ return Math.round(util.utilization * 10000) / 100; // 0-100 with 2 decimal
229
+ } catch {
230
+ return undefined;
231
+ }
232
+ }
233
+ }
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // Runtime Metrics Collector
237
+ // ---------------------------------------------------------------------------
238
+
239
+ export interface RuntimeMetricsOptions {
240
+ /** Collection interval in milliseconds. Default: 15000 (15s). */
241
+ interval?: number;
242
+ /** Callback invoked with each metrics snapshot. */
243
+ onMetrics: (payload: RuntimeMetricsPayload) => void;
244
+ }
245
+
246
+ export class RuntimeMetricsCollector {
247
+ private gcObserver = new GcObserver();
248
+ private lagMeter = new EventLoopLagMeter();
249
+ private eluMeter = new EventLoopUtilization();
250
+ private lastCpu: NodeJS.CpuUsage | undefined;
251
+ private timer: any = null;
252
+ private interval: number;
253
+ private onMetrics: (payload: RuntimeMetricsPayload) => void;
254
+ private started = false;
255
+
256
+ constructor(options: RuntimeMetricsOptions) {
257
+ this.interval = options.interval || 15000;
258
+ this.onMetrics = options.onMetrics;
259
+ }
260
+
261
+ start() {
262
+ if (!isNode() || this.started) return;
263
+ this.started = true;
264
+
265
+ this.gcObserver.start();
266
+ this.lagMeter.start();
267
+ this.eluMeter.start();
268
+
269
+ try {
270
+ this.lastCpu = process.cpuUsage();
271
+ } catch { }
272
+
273
+ this.timer = setInterval(() => {
274
+ try {
275
+ this.collect();
276
+ } catch { }
277
+ }, this.interval);
278
+
279
+ if (this.timer && typeof this.timer.unref === 'function') {
280
+ this.timer.unref();
281
+ }
282
+ }
283
+
284
+ private collect() {
285
+ const mem = process.memoryUsage();
286
+ const gc = this.gcObserver.take();
287
+ const lagInfo = this.lagMeter.take();
288
+ const eluPercent = this.eluMeter.take();
289
+
290
+ // CPU delta
291
+ let cpuUserUs = 0;
292
+ let cpuSystemUs = 0;
293
+ try {
294
+ if (this.lastCpu) {
295
+ const delta = process.cpuUsage(this.lastCpu);
296
+ cpuUserUs = delta.user;
297
+ cpuSystemUs = delta.system;
298
+ }
299
+ this.lastCpu = process.cpuUsage();
300
+ } catch { }
301
+
302
+ // Active handles/requests
303
+ let activeHandles = 0;
304
+ let activeRequests = 0;
305
+ try {
306
+ activeHandles = (process as any)._getActiveHandles?.()?.length ?? 0;
307
+ activeRequests = (process as any)._getActiveRequests?.()?.length ?? 0;
308
+ } catch { }
309
+
310
+ const metrics: RuntimeMetrics = {
311
+ eventLoop: {
312
+ lagMs: lagInfo.lagMs,
313
+ lagP50Ms: lagInfo.lagP50Ms,
314
+ lagP99Ms: lagInfo.lagP99Ms,
315
+ utilizationPercent: eluPercent,
316
+ },
317
+ gc,
318
+ memory: {
319
+ heapUsedBytes: mem.heapUsed,
320
+ heapTotalBytes: mem.heapTotal,
321
+ externalBytes: mem.external,
322
+ arrayBuffersBytes: mem.arrayBuffers || 0,
323
+ rssBytes: mem.rss,
324
+ heapUsedPercent: mem.heapTotal > 0
325
+ ? Math.round((mem.heapUsed / mem.heapTotal) * 10000) / 100
326
+ : 0,
327
+ },
328
+ process: {
329
+ activeHandles,
330
+ activeRequests,
331
+ cpuUserUs,
332
+ cpuSystemUs,
333
+ uptimeSeconds: Math.floor(process.uptime()),
334
+ },
335
+ };
336
+
337
+ this.onMetrics({
338
+ timestamp: new Date().toISOString(),
339
+ metrics,
340
+ });
341
+ }
342
+
343
+ stop() {
344
+ if (this.timer) {
345
+ clearInterval(this.timer);
346
+ this.timer = null;
347
+ }
348
+ this.gcObserver.stop();
349
+ this.lagMeter.stop();
350
+ this.started = false;
351
+ }
352
+ }
@@ -0,0 +1,272 @@
1
+ import { Context } from '../core/context';
2
+ import { SenzorOptions } from '../core/types';
3
+ import { hookRequire } from './hook';
4
+ import { patchMethod, isPatched } from './patch';
5
+ import { runWithCapturedSpan, startCapturedSpan } from './span';
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Socket.IO Instrumentation
9
+ //
10
+ // Instruments socket.io (server-side):
11
+ // - Socket.prototype.emit() — outbound event spans (server → client)
12
+ // - Socket.prototype.on() — wraps event handlers with receive spans
13
+ // - Namespace.prototype.emit() — broadcast event spans
14
+ //
15
+ // Instruments socket.io-client (client-side):
16
+ // - Socket.prototype.emit() — outbound event spans (client → server)
17
+ // - Socket.prototype.on() — wraps event handlers with receive spans
18
+ //
19
+ // Follows OTel messaging semantic conventions:
20
+ // messaging.system = socket.io
21
+ // messaging.destination.name = event name
22
+ // messaging.operation.name = send | receive
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /** Events to never instrument (internal socket.io events). */
26
+ const IGNORED_EVENTS = new Set([
27
+ 'connect',
28
+ 'connect_error',
29
+ 'disconnect',
30
+ 'disconnecting',
31
+ 'newListener',
32
+ 'removeListener',
33
+ 'error',
34
+ 'ping',
35
+ 'pong',
36
+ 'connection',
37
+ ]);
38
+
39
+ /** Check if an event should be instrumented. */
40
+ const shouldInstrument = (event: string): boolean => {
41
+ if (typeof event !== 'string') return false;
42
+ return !IGNORED_EVENTS.has(event);
43
+ };
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Emit patching (outbound events)
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const patchEmit = (
50
+ proto: any,
51
+ patchKey: string,
52
+ side: 'server' | 'client',
53
+ options?: SenzorOptions
54
+ ) => {
55
+ patchMethod(
56
+ proto,
57
+ 'emit',
58
+ patchKey,
59
+ (original) =>
60
+ function patchedEmit(this: any, event: string, ...args: any[]) {
61
+ if (!shouldInstrument(event)) {
62
+ return original.call(this, event, ...args);
63
+ }
64
+
65
+ const trace = Context.current();
66
+ if (!trace) return original.call(this, event, ...args);
67
+
68
+ const namespace = this.nsp?.name || this.name || '/';
69
+
70
+ const span = startCapturedSpan(
71
+ `Socket.IO ${side} emit ${event}`,
72
+ 'messaging',
73
+ {
74
+ 'messaging.system': 'socket.io',
75
+ 'messaging.destination.name': event,
76
+ 'messaging.operation.name': 'send',
77
+ 'messaging.socketio.namespace': namespace,
78
+ 'messaging.socketio.side': side,
79
+ 'messaging.socketio.event': event,
80
+ },
81
+ options
82
+ );
83
+
84
+ if (!span) return original.call(this, event, ...args);
85
+
86
+ // Check if last arg is an acknowledgement callback
87
+ const lastArg = args[args.length - 1];
88
+ const hasAck = typeof lastArg === 'function';
89
+
90
+ if (hasAck) {
91
+ const originalAck = lastArg;
92
+ args[args.length - 1] = function wrappedAck(...ackArgs: any[]) {
93
+ span.end(0, { 'messaging.socketio.acknowledged': true });
94
+ return originalAck.apply(this, ackArgs);
95
+ };
96
+ }
97
+
98
+ return runWithCapturedSpan(span, () => {
99
+ try {
100
+ const result = original.call(this, event, ...args);
101
+
102
+ if (!hasAck) {
103
+ // Fire-and-forget emit — end span immediately
104
+ span.end(0);
105
+ }
106
+
107
+ return result;
108
+ } catch (error: any) {
109
+ span.end(500, {
110
+ 'error.message': error?.message,
111
+ 'error.type': error?.name || 'SocketIOError',
112
+ });
113
+ throw error;
114
+ }
115
+ });
116
+ }
117
+ );
118
+ };
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // On patching (inbound event handlers)
122
+ // ---------------------------------------------------------------------------
123
+
124
+ const patchOn = (
125
+ proto: any,
126
+ patchKey: string,
127
+ side: 'server' | 'client',
128
+ options?: SenzorOptions
129
+ ) => {
130
+ patchMethod(
131
+ proto,
132
+ 'on',
133
+ patchKey,
134
+ (original) =>
135
+ function patchedOn(this: any, event: string, listener: any) {
136
+ if (!shouldInstrument(event) || typeof listener !== 'function') {
137
+ return original.call(this, event, listener);
138
+ }
139
+
140
+ const namespace = this.nsp?.name || this.name || '/';
141
+
142
+ const wrappedListener = function (this: any, ...args: any[]) {
143
+ const span = startCapturedSpan(
144
+ `Socket.IO ${side} receive ${event}`,
145
+ 'messaging',
146
+ {
147
+ 'messaging.system': 'socket.io',
148
+ 'messaging.destination.name': event,
149
+ 'messaging.operation.name': 'receive',
150
+ 'messaging.socketio.namespace': namespace,
151
+ 'messaging.socketio.side': side,
152
+ 'messaging.socketio.event': event,
153
+ },
154
+ options
155
+ );
156
+
157
+ if (!span) return listener.apply(this, args);
158
+
159
+ return runWithCapturedSpan(span, () => {
160
+ try {
161
+ const result = listener.apply(this, args);
162
+
163
+ // Handle async handlers
164
+ if (result && typeof result.then === 'function') {
165
+ return result.then(
166
+ (val: any) => {
167
+ span.end(0);
168
+ return val;
169
+ },
170
+ (error: any) => {
171
+ span.end(500, {
172
+ 'error.message': error?.message,
173
+ 'error.type': error?.name || 'Error',
174
+ });
175
+ throw error;
176
+ }
177
+ );
178
+ }
179
+
180
+ span.end(0);
181
+ return result;
182
+ } catch (error: any) {
183
+ span.end(500, {
184
+ 'error.message': error?.message,
185
+ 'error.type': error?.name || 'Error',
186
+ });
187
+ throw error;
188
+ }
189
+ });
190
+ };
191
+
192
+ // Preserve the original listener reference for removeListener support
193
+ (wrappedListener as any).__senzorOriginal = listener;
194
+
195
+ return original.call(this, event, wrappedListener);
196
+ }
197
+ );
198
+ };
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Server-side patching
202
+ // ---------------------------------------------------------------------------
203
+
204
+ const patchServerSocket = (socketio: any, options?: SenzorOptions) => {
205
+ // Socket.prototype (individual socket)
206
+ const SocketClass = socketio?.Socket;
207
+ if (SocketClass?.prototype) {
208
+ patchEmit(SocketClass.prototype, 'senzor.socketio.server.socket.emit', 'server', options);
209
+ patchOn(SocketClass.prototype, 'senzor.socketio.server.socket.on', 'server', options);
210
+ }
211
+
212
+ // Namespace.prototype (broadcast to namespace/room)
213
+ const NamespaceClass = socketio?.Namespace;
214
+ if (NamespaceClass?.prototype) {
215
+ patchEmit(NamespaceClass.prototype, 'senzor.socketio.server.namespace.emit', 'server', options);
216
+ }
217
+ };
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // Client-side patching
221
+ // ---------------------------------------------------------------------------
222
+
223
+ const patchClientSocket = (clientModule: any, options?: SenzorOptions) => {
224
+ // socket.io-client exports a Socket class or io function
225
+ const SocketClass = clientModule?.Socket || clientModule?.io?.Socket;
226
+ if (SocketClass?.prototype) {
227
+ patchEmit(SocketClass.prototype, 'senzor.socketio.client.emit', 'client', options);
228
+ patchOn(SocketClass.prototype, 'senzor.socketio.client.on', 'client', options);
229
+ }
230
+
231
+ // Some versions export Manager too
232
+ if (clientModule?.Manager?.prototype) {
233
+ // Manager handles reconnection events, but we don't need to patch those
234
+ }
235
+ };
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Public API
239
+ // ---------------------------------------------------------------------------
240
+
241
+ export const instrumentSocketIO = (options?: SenzorOptions) => {
242
+ // Server-side socket.io
243
+ hookRequire('socket.io', (exports: any) => {
244
+ // socket.io exports a Server class. Socket and Namespace are properties of the module
245
+ patchServerSocket(exports, options);
246
+
247
+ // In some versions, Socket is exported from a sub-module
248
+ if (!exports.Socket) {
249
+ try {
250
+ const socketModule = require('socket.io/dist/socket');
251
+ if (socketModule?.Socket?.prototype) {
252
+ patchEmit(socketModule.Socket.prototype, 'senzor.socketio.server.socket.emit', 'server', options);
253
+ patchOn(socketModule.Socket.prototype, 'senzor.socketio.server.socket.on', 'server', options);
254
+ }
255
+ } catch { }
256
+ }
257
+
258
+ if (!exports.Namespace) {
259
+ try {
260
+ const nsModule = require('socket.io/dist/namespace');
261
+ if (nsModule?.Namespace?.prototype) {
262
+ patchEmit(nsModule.Namespace.prototype, 'senzor.socketio.server.namespace.emit', 'server', options);
263
+ }
264
+ } catch { }
265
+ }
266
+ });
267
+
268
+ // Client-side socket.io-client
269
+ hookRequire('socket.io-client', (exports: any) => {
270
+ patchClientSocket(exports, options);
271
+ });
272
+ };