@onlineapps/mq-client-core 1.0.43 → 1.0.46
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/config/queueConfig.js +34 -44
- package/src/index.js +12 -0
- package/src/monitoring-publish.js +203 -0
package/package.json
CHANGED
|
@@ -207,14 +207,20 @@ module.exports = {
|
|
|
207
207
|
|
|
208
208
|
/**
|
|
209
209
|
* Monitoring infrastructure queue configurations
|
|
210
|
-
* These are dedicated queues for Monitoring service (
|
|
210
|
+
* These are dedicated queues for Monitoring service (infrastructure service)
|
|
211
|
+
*
|
|
212
|
+
* CRITICAL: Monitoring is an infrastructure service, monitoring queues are infrastructure queues!
|
|
213
|
+
* All services (both infrastructure and business) publish monitoring events here.
|
|
211
214
|
*/
|
|
212
215
|
monitoring: {
|
|
213
216
|
/**
|
|
214
|
-
* monitoring.workflow
|
|
215
|
-
*
|
|
217
|
+
* monitoring.workflow - Unified workflow lifecycle monitoring
|
|
218
|
+
* Delivery Dispatcher publishes 'completed'/'failed' events after processing workflow.completed/workflow.failed
|
|
219
|
+
* Business services publish 'progress' events during workflow step processing
|
|
220
|
+
* Message format includes event_type: 'completed' | 'failed' | 'progress'
|
|
221
|
+
* Includes full cookbook and context snapshots for trace storage
|
|
216
222
|
*/
|
|
217
|
-
'workflow
|
|
223
|
+
'workflow': {
|
|
218
224
|
durable: true,
|
|
219
225
|
arguments: {
|
|
220
226
|
'x-message-ttl': 300000, // 5 minutes TTL
|
|
@@ -223,36 +229,19 @@ module.exports = {
|
|
|
223
229
|
},
|
|
224
230
|
|
|
225
231
|
/**
|
|
226
|
-
* monitoring.
|
|
227
|
-
*
|
|
232
|
+
* monitoring.services - Service lifecycle events monitoring
|
|
233
|
+
* Registry publishes service lifecycle events (registered, validation.completed, version.changed, status.changed)
|
|
234
|
+
* InfrastructureHealthTracker publishes service status changes
|
|
235
|
+
* Message format includes event_type: 'service.registered' | 'service.validation.completed' |
|
|
236
|
+
* 'service.version.changed' | 'service.deregistered' | 'service.status.changed'
|
|
237
|
+
* NOTE: Heartbeats are NOT published here (too frequent, every 10s)
|
|
238
|
+
* NOTE: Healthcheck messages go to infrastructure.health.events exchange
|
|
228
239
|
*/
|
|
229
|
-
'
|
|
240
|
+
'services': {
|
|
230
241
|
durable: true,
|
|
231
242
|
arguments: {
|
|
232
|
-
'x-message-ttl':
|
|
233
|
-
'x-max-length':
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* monitoring.registry.events - Registry events for monitoring
|
|
239
|
-
*/
|
|
240
|
-
'registry.events': {
|
|
241
|
-
durable: true,
|
|
242
|
-
arguments: {
|
|
243
|
-
'x-message-ttl': 60000, // 1 minute TTL
|
|
244
|
-
'x-max-length': 5000
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* monitoring.service.heartbeats - Service heartbeats for monitoring
|
|
250
|
-
*/
|
|
251
|
-
'service.heartbeats': {
|
|
252
|
-
durable: true,
|
|
253
|
-
arguments: {
|
|
254
|
-
'x-message-ttl': 120000, // 2 minutes TTL
|
|
255
|
-
'x-max-length': 10000
|
|
243
|
+
'x-message-ttl': 600000, // 10 minutes TTL (longer for service tracking)
|
|
244
|
+
'x-max-length': 20000
|
|
256
245
|
}
|
|
257
246
|
}
|
|
258
247
|
},
|
|
@@ -262,27 +251,28 @@ module.exports = {
|
|
|
262
251
|
*/
|
|
263
252
|
registry: {
|
|
264
253
|
/**
|
|
265
|
-
* registry.register -
|
|
254
|
+
* registry.register - Main queue for ALL service communication with Registry
|
|
255
|
+
*
|
|
256
|
+
* CRITICAL: This is the ONLY queue used for service-to-Registry communication.
|
|
257
|
+
* Registry listener processes different message types based on msg.type:
|
|
258
|
+
* - type: 'register' - Service registration requests
|
|
259
|
+
* - type: 'heartbeat' - Periodic health check messages (sent every 10s)
|
|
260
|
+
* - type: 'apiDescription' - API specification responses
|
|
261
|
+
*
|
|
262
|
+
* Architecture rationale:
|
|
263
|
+
* - Single consumer on registry.register handles all message types
|
|
264
|
+
* - Simplifies queue management (one queue instead of multiple)
|
|
265
|
+
* - Registry can process messages in order and maintain state consistency
|
|
266
|
+
* - All business services send both registration AND heartbeats to this queue
|
|
266
267
|
*/
|
|
267
268
|
register: {
|
|
268
269
|
durable: true,
|
|
269
270
|
arguments: {
|
|
270
|
-
'x-message-ttl': 60000, // 1 minute TTL
|
|
271
|
+
'x-message-ttl': 60000, // 1 minute TTL (for registration requests)
|
|
271
272
|
'x-max-length': 1000
|
|
272
273
|
}
|
|
273
274
|
},
|
|
274
275
|
|
|
275
|
-
/**
|
|
276
|
-
* registry.heartbeats - Service heartbeats
|
|
277
|
-
*/
|
|
278
|
-
heartbeats: {
|
|
279
|
-
durable: true,
|
|
280
|
-
arguments: {
|
|
281
|
-
'x-message-ttl': 120000, // 2 minutes TTL
|
|
282
|
-
'x-max-length': 5000
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
|
|
286
276
|
/**
|
|
287
277
|
* registry.changes - Registry event fanout exchange
|
|
288
278
|
* (This is an exchange, not a queue, but included for reference)
|
package/src/index.js
CHANGED
|
@@ -25,6 +25,12 @@ const {
|
|
|
25
25
|
QueueNotFoundError,
|
|
26
26
|
classifyPublishError,
|
|
27
27
|
} = require('./utils/publishErrors');
|
|
28
|
+
const {
|
|
29
|
+
publishToMonitoringResilient,
|
|
30
|
+
publishToMonitoringWorkflow,
|
|
31
|
+
publishToMonitoringServices,
|
|
32
|
+
isQueueUnavailableError,
|
|
33
|
+
} = require('./monitoring-publish');
|
|
28
34
|
|
|
29
35
|
// Export BaseClient as default (constructor), with additional named exports
|
|
30
36
|
// NOTE: When destructuring, use: const { BaseClient } = require('@onlineapps/mq-client-core');
|
|
@@ -46,4 +52,10 @@ module.exports.publishErrors = {
|
|
|
46
52
|
QueueNotFoundError,
|
|
47
53
|
classifyPublishError,
|
|
48
54
|
};
|
|
55
|
+
module.exports.monitoring = {
|
|
56
|
+
publishToMonitoringResilient,
|
|
57
|
+
publishToMonitoringWorkflow,
|
|
58
|
+
publishToMonitoringServices,
|
|
59
|
+
isQueueUnavailableError,
|
|
60
|
+
};
|
|
49
61
|
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Monitoring Publish Helper
|
|
3
|
+
*
|
|
4
|
+
* Provides resilient, fail-safe publishing to monitoring queues (monitoring.workflow, monitoring.services).
|
|
5
|
+
*
|
|
6
|
+
* Principles:
|
|
7
|
+
* - Fail-safe: Never throws errors, always logs failures
|
|
8
|
+
* - Resilient: Handles queue unavailability gracefully (queue doesn't exist yet, RabbitMQ down)
|
|
9
|
+
* - Unified: Single API for all monitoring publish operations across all services
|
|
10
|
+
* - Encapsulated: All monitoring publish logic in one place, no duplication
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* const { publishToMonitoringWorkflow, publishToMonitoringServices } = require('@onlineapps/mq-client-core');
|
|
14
|
+
* await publishToMonitoringWorkflow(mqClient, { event_type: 'completed', workflow_id: '...', ... }, logger);
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { PublishError, ConnectionError } = require('./utils/errorHandler');
|
|
18
|
+
const { QueueNotFoundError, classifyPublishError } = require('./utils/publishErrors');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if error indicates queue doesn't exist or RabbitMQ is unavailable
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
function isQueueUnavailableError(error) {
|
|
25
|
+
if (!error) return false;
|
|
26
|
+
|
|
27
|
+
const errorMessage = error.message || String(error);
|
|
28
|
+
const errorCode = error.code || '';
|
|
29
|
+
const errorName = error.name || '';
|
|
30
|
+
|
|
31
|
+
// Connection errors
|
|
32
|
+
if (error instanceof ConnectionError ||
|
|
33
|
+
errorCode === 'ECONNREFUSED' ||
|
|
34
|
+
errorCode === 'ENOTFOUND' ||
|
|
35
|
+
errorCode === 'ETIMEDOUT' ||
|
|
36
|
+
errorMessage.includes('not connected') ||
|
|
37
|
+
errorMessage.includes('Connection closed')) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Queue doesn't exist errors
|
|
42
|
+
if (error instanceof QueueNotFoundError ||
|
|
43
|
+
errorName === 'QueueNotFoundError' ||
|
|
44
|
+
errorMessage.includes('NOT_FOUND') ||
|
|
45
|
+
errorMessage.includes('404') ||
|
|
46
|
+
errorMessage.includes('no queue') ||
|
|
47
|
+
errorMessage.includes('queue does not exist') ||
|
|
48
|
+
errorMessage.includes('Channel closed')) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Classify publish errors
|
|
53
|
+
const classified = classifyPublishError(error);
|
|
54
|
+
if (classified instanceof QueueNotFoundError) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Unified resilient publish to monitoring queue
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} mqClient - BaseClient instance (must be connected)
|
|
65
|
+
* @param {string} queueName - Queue name ('monitoring.workflow' or 'monitoring.services')
|
|
66
|
+
* @param {Object} message - Message to publish (must include event_type)
|
|
67
|
+
* @param {Object} [logger] - Logger instance (optional, for logging)
|
|
68
|
+
* @param {Object} [context] - Additional context for logging (optional)
|
|
69
|
+
* @returns {Promise<boolean>} - true if published successfully, false otherwise (never throws)
|
|
70
|
+
*/
|
|
71
|
+
async function publishToMonitoringResilient(mqClient, queueName, message, logger = null, context = {}) {
|
|
72
|
+
// Validate inputs
|
|
73
|
+
if (!mqClient) {
|
|
74
|
+
if (logger) {
|
|
75
|
+
logger.warn('Monitoring publish skipped: mqClient not provided', context);
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!mqClient.isConnected || typeof mqClient.isConnected !== 'function' || !mqClient.isConnected()) {
|
|
81
|
+
if (logger) {
|
|
82
|
+
logger.warn(`Monitoring publish skipped: mqClient not connected (queue: ${queueName})`, context);
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!message || !message.event_type) {
|
|
88
|
+
if (logger) {
|
|
89
|
+
logger.warn(`Monitoring publish skipped: message missing event_type (queue: ${queueName})`, {
|
|
90
|
+
...context,
|
|
91
|
+
hasMessage: !!message
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Validate queue name
|
|
98
|
+
if (queueName !== 'monitoring.workflow' && queueName !== 'monitoring.services') {
|
|
99
|
+
if (logger) {
|
|
100
|
+
logger.warn(`Monitoring publish skipped: invalid queue name (expected monitoring.workflow or monitoring.services, got: ${queueName})`, context);
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await mqClient.publish(queueName, message);
|
|
107
|
+
|
|
108
|
+
if (logger && logger.debug) {
|
|
109
|
+
logger.debug(`Published to ${queueName}`, {
|
|
110
|
+
...context,
|
|
111
|
+
event_type: message.event_type,
|
|
112
|
+
workflow_id: message.workflow_id,
|
|
113
|
+
service_name: message.service_name
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// Check if this is a queue unavailable error
|
|
120
|
+
if (isQueueUnavailableError(error)) {
|
|
121
|
+
// Queue doesn't exist yet or RabbitMQ is unavailable - log but don't fail
|
|
122
|
+
if (logger && logger.warn) {
|
|
123
|
+
logger.warn(`Monitoring queue ${queueName} not available (queue may not exist yet or RabbitMQ unavailable)`, {
|
|
124
|
+
...context,
|
|
125
|
+
error: error.message,
|
|
126
|
+
errorCode: error.code,
|
|
127
|
+
event_type: message.event_type,
|
|
128
|
+
workflow_id: message.workflow_id,
|
|
129
|
+
service_name: message.service_name
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Other errors - log as warning but don't fail
|
|
136
|
+
if (logger && logger.warn) {
|
|
137
|
+
logger.warn(`Failed to publish to monitoring queue ${queueName}`, {
|
|
138
|
+
...context,
|
|
139
|
+
error: error.message,
|
|
140
|
+
errorCode: error.code,
|
|
141
|
+
errorName: error.name,
|
|
142
|
+
event_type: message.event_type,
|
|
143
|
+
workflow_id: message.workflow_id,
|
|
144
|
+
service_name: message.service_name
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Publish workflow event to monitoring.workflow queue
|
|
154
|
+
*
|
|
155
|
+
* @param {Object} mqClient - BaseClient instance (must be connected)
|
|
156
|
+
* @param {Object} message - Message with event_type ('completed' | 'failed' | 'progress'), workflow_id, etc.
|
|
157
|
+
* @param {Object} [logger] - Logger instance (optional)
|
|
158
|
+
* @param {Object} [context] - Additional context for logging (optional)
|
|
159
|
+
* @returns {Promise<boolean>} - true if published successfully, false otherwise (never throws)
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* await publishToMonitoringWorkflow(mqClient, {
|
|
163
|
+
* event_type: 'completed',
|
|
164
|
+
* workflow_id: 'wf-123',
|
|
165
|
+
* service_name: 'hello-service',
|
|
166
|
+
* cookbook: { ... },
|
|
167
|
+
* context: { ... },
|
|
168
|
+
* timestamp: new Date().toISOString()
|
|
169
|
+
* }, logger);
|
|
170
|
+
*/
|
|
171
|
+
async function publishToMonitoringWorkflow(mqClient, message, logger = null, context = {}) {
|
|
172
|
+
return publishToMonitoringResilient(mqClient, 'monitoring.workflow', message, logger, context);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Publish service lifecycle event to monitoring.services queue
|
|
177
|
+
*
|
|
178
|
+
* @param {Object} mqClient - BaseClient instance (must be connected)
|
|
179
|
+
* @param {Object} message - Message with event_type ('service.registered' | 'service.validation.completed' | etc.), service_name, etc.
|
|
180
|
+
* @param {Object} [logger] - Logger instance (optional)
|
|
181
|
+
* @param {Object} [context] - Additional context for logging (optional)
|
|
182
|
+
* @returns {Promise<boolean>} - true if published successfully, false otherwise (never throws)
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* await publishToMonitoringServices(mqClient, {
|
|
186
|
+
* event_type: 'service.registered',
|
|
187
|
+
* service_name: 'hello-service',
|
|
188
|
+
* version: '1.0.0',
|
|
189
|
+
* status: 'pending',
|
|
190
|
+
* timestamp: new Date().toISOString()
|
|
191
|
+
* }, logger);
|
|
192
|
+
*/
|
|
193
|
+
async function publishToMonitoringServices(mqClient, message, logger = null, context = {}) {
|
|
194
|
+
return publishToMonitoringResilient(mqClient, 'monitoring.services', message, logger, context);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
publishToMonitoringResilient,
|
|
199
|
+
publishToMonitoringWorkflow,
|
|
200
|
+
publishToMonitoringServices,
|
|
201
|
+
isQueueUnavailableError
|
|
202
|
+
};
|
|
203
|
+
|