@onlineapps/mq-client-core 1.0.34 → 1.0.35

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/mq-client-core",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -42,6 +42,21 @@ class RabbitMQClient extends EventEmitter {
42
42
  // Auto-reconnect flags
43
43
  this._reconnecting = false;
44
44
  this._reconnectAttempts = 0;
45
+
46
+ // Channel close hooks - called when channel closes with detailed information
47
+ this._channelCloseHooks = []; // Array of { type: 'publisher'|'queue'|'consumer', callback: (details) => void }
48
+
49
+ // Health monitoring
50
+ this._healthCheckInterval = null;
51
+ this._healthCheckIntervalMs = this._config.healthCheckInterval || 30000; // Default 30s
52
+ this._healthCheckEnabled = this._config.healthCheckEnabled !== false;
53
+
54
+ // Health reporting callbacks
55
+ this._healthReportCallback = this._config.healthReportCallback || null; // (health) => Promise<void>
56
+ this._healthCriticalCallback = this._config.healthCriticalCallback || null; // (health) => Promise<void>
57
+ this._criticalHealthShutdown = this._config.criticalHealthShutdown !== false; // Default: true - shutdown on critical health
58
+ this._criticalHealthShutdownDelay = this._config.criticalHealthShutdownDelay || 60000; // Default: 60s delay before shutdown
59
+ this._criticalHealthStartTime = null; // Track when critical health started
45
60
  }
46
61
 
47
62
  /**
@@ -66,6 +81,50 @@ class RabbitMQClient extends EventEmitter {
66
81
  get queueChannel() {
67
82
  return this._queueChannel || this._channel; // Fallback to main channel if queueChannel not available
68
83
  }
84
+
85
+ /**
86
+ * Register a hook to be called when a channel closes
87
+ * @param {string} type - Channel type: 'publisher', 'queue', 'consumer', or 'all'
88
+ * @param {Function} callback - Callback function receiving close details: { type, channel, error, reason, timestamp, stack }
89
+ */
90
+ onChannelClose(type, callback) {
91
+ this._channelCloseHooks.push({ type, callback });
92
+ }
93
+
94
+ /**
95
+ * Call all registered channel close hooks
96
+ * @private
97
+ * @param {string} channelType - 'publisher', 'queue', or 'consumer'
98
+ * @param {Object} channel - The closed channel
99
+ * @param {Error} error - Error that caused closure (if any)
100
+ * @param {string} reason - Human-readable reason
101
+ */
102
+ _callChannelCloseHooks(channelType, channel, error, reason) {
103
+ const details = {
104
+ type: channelType,
105
+ channel: channel,
106
+ error: error,
107
+ reason: reason,
108
+ timestamp: new Date().toISOString(),
109
+ stack: error?.stack || new Error().stack,
110
+ connectionState: this._connection ? (this._connection.closed ? 'closed' : 'open') : 'null',
111
+ channelCreatedAt: channel?._createdAt || 'unknown',
112
+ lastOperation: channel?._lastOperation || 'unknown'
113
+ };
114
+
115
+ for (const hook of this._channelCloseHooks) {
116
+ if (hook.type === 'all' || hook.type === channelType) {
117
+ try {
118
+ hook.callback(details);
119
+ } catch (hookErr) {
120
+ console.error(`[RabbitMQClient] [mq-client-core] Channel close hook error:`, hookErr.message);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Emit event for external listeners
126
+ this.emit('channel:close', details);
127
+ }
69
128
 
70
129
  /**
71
130
  * Connects to RabbitMQ server and creates a confirm channel.
@@ -101,17 +160,37 @@ class RabbitMQClient extends EventEmitter {
101
160
 
102
161
  // Use ConfirmChannel to enable publisher confirms for publish operations
103
162
  this._channel = await this._connection.createConfirmChannel();
163
+
164
+ // Track channel metadata for debugging
165
+ this._channel._createdAt = new Date().toISOString();
166
+ this._channel._closeReason = null;
167
+ this._channel._lastOperation = 'Initial creation';
168
+ this._channel._type = 'publisher';
169
+
104
170
  // Enable publisher confirms explicitly
105
171
  this._channel.on('error', async (err) => {
106
- console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
172
+ const reason = `Publisher channel error: ${err.message} (code: ${err.code || 'unknown'})`;
173
+ this._channel._closeReason = reason;
174
+ console.error(`[RabbitMQClient] [mq-client-core] ${reason}`);
175
+
176
+ // Call close hooks with detailed information
177
+ this._callChannelCloseHooks('publisher', this._channel, err, reason);
178
+
107
179
  // Auto-recreate channel if connection is still alive
108
180
  await this._ensurePublisherChannel();
109
181
  this.emit('error', err);
110
182
  });
111
183
  this._channel.on('close', async () => {
112
- console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
184
+ const reason = this._channel._closeReason || 'Publisher channel closed unexpectedly';
185
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next publish`);
186
+
187
+ // Call close hooks with detailed information
188
+ this._callChannelCloseHooks('publisher', this._channel, null, reason);
189
+
113
190
  // Mark channel as null - will be recreated on next publish
191
+ const closedChannel = this._channel;
114
192
  this._channel = null;
193
+
115
194
  // Try to recreate immediately if connection is alive
116
195
  await this._ensurePublisherChannel();
117
196
  });
@@ -123,16 +202,35 @@ class RabbitMQClient extends EventEmitter {
123
202
  // Create a separate regular channel for queue operations (assertQueue, checkQueue)
124
203
  // This avoids RPC reply queue issues with ConfirmChannel.assertQueue()
125
204
  this._queueChannel = await this._connection.createChannel();
205
+
206
+ // Track channel metadata for debugging
207
+ this._queueChannel._createdAt = new Date().toISOString();
208
+ this._queueChannel._closeReason = null;
209
+ this._queueChannel._lastOperation = 'Initial creation';
210
+ this._queueChannel._type = 'queue';
211
+
126
212
  this._queueChannel.on('error', async (err) => {
127
- // Log but don't emit - queue channel errors are less critical
128
- console.warn('[RabbitMQClient] Queue channel error:', err.message);
213
+ const reason = `Queue channel error: ${err.message} (code: ${err.code || 'unknown'})`;
214
+ this._queueChannel._closeReason = reason;
215
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
216
+
217
+ // Call close hooks with detailed information
218
+ this._callChannelCloseHooks('queue', this._queueChannel, err, reason);
219
+
129
220
  // Auto-recreate channel if connection is still alive
130
221
  await this._ensureQueueChannel();
131
222
  });
132
223
  this._queueChannel.on('close', async () => {
133
- console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
224
+ const reason = this._queueChannel._closeReason || 'Queue channel closed unexpectedly';
225
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next operation`);
226
+
227
+ // Call close hooks with detailed information
228
+ this._callChannelCloseHooks('queue', this._queueChannel, null, reason);
229
+
134
230
  // Mark channel as null - will be recreated on next queue operation
231
+ const closedChannel = this._queueChannel;
135
232
  this._queueChannel = null;
233
+
136
234
  // Try to recreate immediately if connection is alive
137
235
  await this._ensureQueueChannel();
138
236
  });
@@ -140,19 +238,44 @@ class RabbitMQClient extends EventEmitter {
140
238
  // Create a dedicated channel for consume operations
141
239
  // This prevents channel conflicts between publish (ConfirmChannel) and consume operations
142
240
  this._consumerChannel = await this._connection.createChannel();
241
+
242
+ // Track channel metadata for debugging
243
+ this._consumerChannel._createdAt = new Date().toISOString();
244
+ this._consumerChannel._closeReason = null;
245
+ this._consumerChannel._lastOperation = 'Initial creation';
246
+ this._consumerChannel._type = 'consumer';
247
+
143
248
  this._consumerChannel.on('error', async (err) => {
144
- console.warn('[RabbitMQClient] Consumer channel error:', err.message);
249
+ const reason = `Consumer channel error: ${err.message} (code: ${err.code || 'unknown'})`;
250
+ this._consumerChannel._closeReason = reason;
251
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
252
+
253
+ // Call close hooks with detailed information
254
+ this._callChannelCloseHooks('consumer', this._consumerChannel, err, reason);
255
+
145
256
  // Auto-recreate channel and re-register consumers
146
257
  await this._ensureConsumerChannel();
147
258
  this.emit('error', err);
148
259
  });
149
260
  this._consumerChannel.on('close', async () => {
150
- console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
261
+ const reason = this._consumerChannel._closeReason || 'Consumer channel closed unexpectedly';
262
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate and re-register consumers`);
263
+
264
+ // Call close hooks with detailed information
265
+ this._callChannelCloseHooks('consumer', this._consumerChannel, null, reason);
266
+
151
267
  // Mark channel as null - will be recreated and consumers re-registered
268
+ const closedChannel = this._consumerChannel;
152
269
  this._consumerChannel = null;
270
+
153
271
  // Try to recreate and re-register consumers immediately if connection is alive
154
272
  await this._ensureConsumerChannel();
155
273
  });
274
+
275
+ // Start health monitoring if enabled
276
+ if (this._healthCheckEnabled) {
277
+ this._startHealthMonitoring();
278
+ }
156
279
  } catch (err) {
157
280
  // Cleanup partially created resources
158
281
  if (this._connection) {
@@ -194,13 +317,32 @@ class RabbitMQClient extends EventEmitter {
194
317
 
195
318
  // Create new ConfirmChannel
196
319
  this._channel = await this._connection.createConfirmChannel();
320
+
321
+ // Track channel metadata for debugging
322
+ this._channel._createdAt = new Date().toISOString();
323
+ this._channel._closeReason = null;
324
+ this._channel._lastOperation = 'Recreated via _ensurePublisherChannel()';
325
+ this._channel._type = 'publisher';
326
+
197
327
  this._channel.on('error', async (err) => {
198
- console.error('[RabbitMQClient] [mq-client-core] Publisher channel error:', err.message);
328
+ const reason = `Publisher channel error: ${err.message} (code: ${err.code || 'unknown'})`;
329
+ this._channel._closeReason = reason;
330
+ console.error(`[RabbitMQClient] [mq-client-core] ${reason}`);
331
+
332
+ // Call close hooks
333
+ this._callChannelCloseHooks('publisher', this._channel, err, reason);
334
+
199
335
  await this._ensurePublisherChannel();
200
336
  this.emit('error', err);
201
337
  });
202
338
  this._channel.on('close', async () => {
203
- console.warn('[RabbitMQClient] [mq-client-core] Publisher channel closed - will auto-recreate on next publish');
339
+ const reason = this._channel._closeReason || 'Publisher channel closed unexpectedly';
340
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next publish`);
341
+
342
+ // Call close hooks
343
+ this._callChannelCloseHooks('publisher', this._channel, null, reason);
344
+
345
+ const closedChannel = this._channel;
204
346
  this._channel = null;
205
347
  await this._ensurePublisherChannel();
206
348
  });
@@ -242,12 +384,31 @@ class RabbitMQClient extends EventEmitter {
242
384
 
243
385
  // Create new regular channel
244
386
  this._queueChannel = await this._connection.createChannel();
387
+
388
+ // Track channel metadata for debugging
389
+ this._queueChannel._createdAt = new Date().toISOString();
390
+ this._queueChannel._closeReason = null;
391
+ this._queueChannel._lastOperation = 'Recreated via _ensureQueueChannel()';
392
+ this._queueChannel._type = 'queue';
393
+
245
394
  this._queueChannel.on('error', async (err) => {
246
- console.warn('[RabbitMQClient] Queue channel error:', err.message);
395
+ const reason = `Queue channel error: ${err.message} (code: ${err.code || 'unknown'})`;
396
+ this._queueChannel._closeReason = reason;
397
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
398
+
399
+ // Call close hooks
400
+ this._callChannelCloseHooks('queue', this._queueChannel, err, reason);
401
+
247
402
  await this._ensureQueueChannel();
248
403
  });
249
404
  this._queueChannel.on('close', async () => {
250
- console.warn('[RabbitMQClient] Queue channel closed - will auto-recreate on next operation');
405
+ const reason = this._queueChannel._closeReason || 'Queue channel closed unexpectedly';
406
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate on next operation`);
407
+
408
+ // Call close hooks
409
+ this._callChannelCloseHooks('queue', this._queueChannel, null, reason);
410
+
411
+ const closedChannel = this._queueChannel;
251
412
  this._queueChannel = null;
252
413
  await this._ensureQueueChannel();
253
414
  });
@@ -286,13 +447,32 @@ class RabbitMQClient extends EventEmitter {
286
447
 
287
448
  // Create new regular channel
288
449
  this._consumerChannel = await this._connection.createChannel();
450
+
451
+ // Track channel metadata for debugging
452
+ this._consumerChannel._createdAt = new Date().toISOString();
453
+ this._consumerChannel._closeReason = null;
454
+ this._consumerChannel._lastOperation = 'Recreated via _ensureConsumerChannel()';
455
+ this._consumerChannel._type = 'consumer';
456
+
289
457
  this._consumerChannel.on('error', async (err) => {
290
- console.warn('[RabbitMQClient] Consumer channel error:', err.message);
458
+ const reason = `Consumer channel error: ${err.message} (code: ${err.code || 'unknown'})`;
459
+ this._consumerChannel._closeReason = reason;
460
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason}`);
461
+
462
+ // Call close hooks
463
+ this._callChannelCloseHooks('consumer', this._consumerChannel, err, reason);
464
+
291
465
  await this._ensureConsumerChannel();
292
466
  this.emit('error', err);
293
467
  });
294
468
  this._consumerChannel.on('close', async () => {
295
- console.warn('[RabbitMQClient] Consumer channel closed - will auto-recreate and re-register consumers');
469
+ const reason = this._consumerChannel._closeReason || 'Consumer channel closed unexpectedly';
470
+ console.warn(`[RabbitMQClient] [mq-client-core] ${reason} - will auto-recreate and re-register consumers`);
471
+
472
+ // Call close hooks
473
+ this._callChannelCloseHooks('consumer', this._consumerChannel, null, reason);
474
+
475
+ const closedChannel = this._consumerChannel;
296
476
  this._consumerChannel = null;
297
477
  await this._ensureConsumerChannel();
298
478
  });
@@ -306,6 +486,7 @@ class RabbitMQClient extends EventEmitter {
306
486
  if (consumerInfo.options.queueOptions) {
307
487
  try {
308
488
  await this._ensureQueueChannel();
489
+ this._trackChannelOperation(this._queueChannel, `assertQueue ${queue} (re-register)`);
309
490
  await this._queueChannel.assertQueue(queue, consumerInfo.options.queueOptions);
310
491
  } catch (assertErr) {
311
492
  if (assertErr.code === 404) {
@@ -349,7 +530,203 @@ class RabbitMQClient extends EventEmitter {
349
530
  * Disconnects: closes channel and connection.
350
531
  * @returns {Promise<void>}
351
532
  */
533
+ /**
534
+ * Start health monitoring - periodically checks channels, queues, and consumers
535
+ * @private
536
+ */
537
+ _startHealthMonitoring() {
538
+ if (this._healthCheckInterval) {
539
+ return; // Already started
540
+ }
541
+
542
+ this._healthCheckInterval = setInterval(async () => {
543
+ try {
544
+ await this._performHealthCheck();
545
+ } catch (err) {
546
+ console.error(`[RabbitMQClient] [mq-client-core] Health check error:`, err.message);
547
+ // Emit event for external monitoring
548
+ this.emit('health:check:error', err);
549
+ }
550
+ }, this._healthCheckIntervalMs);
551
+
552
+ console.log(`[RabbitMQClient] [mq-client-core] Health monitoring started (interval: ${this._healthCheckIntervalMs}ms)`);
553
+ }
554
+
555
+ /**
556
+ * Stop health monitoring
557
+ * @private
558
+ */
559
+ _stopHealthMonitoring() {
560
+ if (this._healthCheckInterval) {
561
+ clearInterval(this._healthCheckInterval);
562
+ this._healthCheckInterval = null;
563
+ console.log('[RabbitMQClient] [mq-client-core] Health monitoring stopped');
564
+ }
565
+ }
566
+
567
+ /**
568
+ * Perform health check - verify channels, queues, and consumers
569
+ * @private
570
+ * @returns {Promise<Object>} Health check results
571
+ */
572
+ async _performHealthCheck() {
573
+ const health = {
574
+ timestamp: new Date().toISOString(),
575
+ connection: {
576
+ exists: !!this._connection,
577
+ closed: this._connection ? this._connection.closed : true
578
+ },
579
+ channels: {
580
+ publisher: {
581
+ exists: !!this._channel,
582
+ closed: this._channel ? this._channel.closed : true
583
+ },
584
+ queue: {
585
+ exists: !!this._queueChannel,
586
+ closed: this._queueChannel ? this._queueChannel.closed : true
587
+ },
588
+ consumer: {
589
+ exists: !!this._consumerChannel,
590
+ closed: this._consumerChannel ? this._consumerChannel.closed : true
591
+ }
592
+ },
593
+ consumers: {
594
+ tracked: this._activeConsumers.size,
595
+ active: 0,
596
+ inactive: []
597
+ },
598
+ queues: {
599
+ checked: 0,
600
+ missing: [],
601
+ noConsumer: []
602
+ },
603
+ healthy: true,
604
+ issues: []
605
+ };
606
+
607
+ // Check connection
608
+ if (!health.connection.exists || health.connection.closed) {
609
+ health.healthy = false;
610
+ health.issues.push('Connection is closed or missing');
611
+ }
612
+
613
+ // Check channels
614
+ if (health.channels.publisher.closed || !health.channels.publisher.exists) {
615
+ health.healthy = false;
616
+ health.issues.push('Publisher channel is closed or missing');
617
+ }
618
+ if (health.channels.queue.closed || !health.channels.queue.exists) {
619
+ health.healthy = false;
620
+ health.issues.push('Queue channel is closed or missing');
621
+ }
622
+ if (health.channels.consumer.closed || !health.channels.consumer.exists) {
623
+ health.healthy = false;
624
+ health.issues.push('Consumer channel is closed or missing');
625
+ }
626
+
627
+ // Check tracked consumers - verify queues exist and have active consumers
628
+ if (this._connection && !this._connection.closed && this._queueChannel && !this._queueChannel.closed) {
629
+ for (const [queue, consumerInfo] of this._activeConsumers.entries()) {
630
+ health.queues.checked++;
631
+ try {
632
+ // Check if queue exists
633
+ this._trackChannelOperation(this._queueChannel, `checkQueue ${queue}`);
634
+ const queueInfo = await this._queueChannel.checkQueue(queue);
635
+ if (queueInfo) {
636
+ // Queue exists - check if consumer is active
637
+ // Note: RabbitMQ doesn't provide direct API to check if consumer is active
638
+ // We can only verify that consumerTag is set
639
+ if (consumerInfo.consumerTag) {
640
+ health.consumers.active++;
641
+ } else {
642
+ health.consumers.inactive.push(queue);
643
+ health.queues.noConsumer.push(queue);
644
+ health.healthy = false;
645
+ health.issues.push(`Queue ${queue} has no active consumer (consumerTag missing)`);
646
+ }
647
+ } else {
648
+ health.queues.missing.push(queue);
649
+ health.healthy = false;
650
+ health.issues.push(`Queue ${queue} does not exist`);
651
+ }
652
+ } catch (err) {
653
+ if (err.code === 404) {
654
+ health.queues.missing.push(queue);
655
+ health.healthy = false;
656
+ health.issues.push(`Queue ${queue} does not exist (404)`);
657
+ } else {
658
+ health.healthy = false;
659
+ health.issues.push(`Error checking queue ${queue}: ${err.message}`);
660
+ }
661
+ }
662
+ }
663
+ }
664
+
665
+ // Emit health check result
666
+ this.emit('health:check', health);
667
+
668
+ // If unhealthy, log and potentially trigger shutdown
669
+ if (!health.healthy) {
670
+ console.error(`[RabbitMQClient] [mq-client-core] Health check FAILED:`, health.issues);
671
+ console.error(`[RabbitMQClient] [mq-client-core] Health details:`, JSON.stringify(health, null, 2));
672
+
673
+ // Track critical health start time
674
+ if (!this._criticalHealthStartTime) {
675
+ this._criticalHealthStartTime = Date.now();
676
+ console.error(`[RabbitMQClient] [mq-client-core] Critical health detected at ${new Date(this._criticalHealthStartTime).toISOString()}`);
677
+ }
678
+
679
+ // Report to monitoring if callback provided
680
+ if (this._healthReportCallback) {
681
+ try {
682
+ await this._healthReportCallback(health);
683
+ } catch (reportErr) {
684
+ console.error(`[RabbitMQClient] [mq-client-core] Health report callback failed:`, reportErr.message);
685
+ }
686
+ }
687
+
688
+ // Call critical health callback if provided
689
+ if (this._healthCriticalCallback) {
690
+ try {
691
+ await this._healthCriticalCallback(health);
692
+ } catch (criticalErr) {
693
+ console.error(`[RabbitMQClient] [mq-client-core] Critical health callback failed:`, criticalErr.message);
694
+ }
695
+ }
696
+
697
+ // Emit critical health event
698
+ this.emit('health:critical', health);
699
+
700
+ // Check if we should shutdown after delay
701
+ if (this._criticalHealthShutdown) {
702
+ const timeSinceCritical = Date.now() - this._criticalHealthStartTime;
703
+ if (timeSinceCritical >= this._criticalHealthShutdownDelay) {
704
+ console.error(`[RabbitMQClient] [mq-client-core] Critical health persisted for ${timeSinceCritical}ms (threshold: ${this._criticalHealthShutdownDelay}ms) - triggering shutdown`);
705
+ this.emit('health:shutdown', health);
706
+ // Note: Actual shutdown should be handled by the application using this event
707
+ } else {
708
+ const remaining = this._criticalHealthShutdownDelay - timeSinceCritical;
709
+ console.warn(`[RabbitMQClient] [mq-client-core] Critical health will trigger shutdown in ${remaining}ms if not resolved`);
710
+ }
711
+ }
712
+ } else {
713
+ // Health is OK - reset critical health tracking
714
+ if (this._criticalHealthStartTime) {
715
+ const duration = Date.now() - this._criticalHealthStartTime;
716
+ console.log(`[RabbitMQClient] [mq-client-core] Health recovered after ${duration}ms of critical state`);
717
+ this._criticalHealthStartTime = null;
718
+ }
719
+
720
+ console.log(`[RabbitMQClient] [mq-client-core] Health check OK: ${health.consumers.active}/${health.consumers.tracked} consumers active, ${health.queues.checked} queues checked`);
721
+ }
722
+
723
+ return health;
724
+ }
725
+
352
726
  async disconnect() {
727
+ // Stop health monitoring
728
+ this._stopHealthMonitoring();
729
+
353
730
  // Clear active consumers tracking
354
731
  this._activeConsumers.clear();
355
732
 
@@ -400,6 +777,9 @@ class RabbitMQClient extends EventEmitter {
400
777
  // Ensure publisher channel exists and is open (auto-recreates if closed)
401
778
  await this._ensurePublisherChannel();
402
779
 
780
+ // Track operation for debugging
781
+ this._trackChannelOperation(this._channel, `publish to ${queue}`);
782
+
403
783
  // Log publish attempt for debugging
404
784
  console.log(`[RabbitMQClient] [mq-client-core] [PUBLISH] Attempting to publish to queue "${queue}"`);
405
785
 
@@ -418,6 +798,7 @@ class RabbitMQClient extends EventEmitter {
418
798
  try {
419
799
  // Ensure queue channel exists and is open (auto-recreates if closed)
420
800
  await this._ensureQueueChannel();
801
+ this._trackChannelOperation(this._queueChannel, `checkQueue ${queue}`);
421
802
  await this._queueChannel.checkQueue(queue);
422
803
  // Queue exists - proceed to publish
423
804
  } catch (checkErr) {
@@ -445,6 +826,7 @@ class RabbitMQClient extends EventEmitter {
445
826
  // For non-infrastructure queues, allow auto-creation with default options
446
827
  const queueOptions = options.queueOptions || { durable: this._config.durable };
447
828
  console.warn(`[RabbitMQClient] [mq-client-core] [PUBLISH] Auto-creating non-infrastructure queue ${queue} with default options (no TTL). This should be avoided for production.`);
829
+ this._trackChannelOperation(this._queueChannel, `assertQueue ${queue}`);
448
830
  await this._queueChannel.assertQueue(queue, queueOptions);
449
831
  } else {
450
832
  // Other error - rethrow
@@ -519,6 +901,9 @@ class RabbitMQClient extends EventEmitter {
519
901
  async consume(queue, onMessage, options = {}) {
520
902
  // Ensure consumer channel exists and is open (auto-recreates if closed and re-registers consumers)
521
903
  await this._ensureConsumerChannel();
904
+
905
+ // Track operation for debugging
906
+ this._trackChannelOperation(this._consumerChannel, `consume from ${queue}`);
522
907
 
523
908
  const durable = options.durable !== undefined ? options.durable : this._config.durable;
524
909
  const prefetch = options.prefetch !== undefined ? options.prefetch : this._config.prefetch;
@@ -597,6 +982,7 @@ class RabbitMQClient extends EventEmitter {
597
982
 
598
983
  try {
599
984
  console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] About to call assertQueue(${queue}, ${JSON.stringify(queueOptions)})`);
985
+ this._trackChannelOperation(this._queueChannel, `assertQueue ${queue}`);
600
986
  await this._queueChannel.assertQueue(queue, queueOptions);
601
987
  console.log(`[RabbitMQClient] [mq-client-core] [CONSUMER] ✓ Queue ${queue} asserted successfully`);
602
988
  } catch (assertErr) {