@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 +1 -1
- package/src/transports/rabbitmqClient.js +399 -13
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|