@onlineapps/infrastructure-tools 1.0.5 → 1.0.7
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/README.md
CHANGED
|
@@ -67,6 +67,7 @@ const { initInfrastructureQueues } = require('@onlineapps/infrastructure-tools')
|
|
|
67
67
|
await initInfrastructureQueues(channel, {
|
|
68
68
|
queues: ['workflow.init'], // Only create specific queues
|
|
69
69
|
connection: connection,
|
|
70
|
+
serviceName: 'api-gateway',
|
|
70
71
|
logger: logger
|
|
71
72
|
});
|
|
72
73
|
```
|
|
@@ -94,6 +95,8 @@ Initializes infrastructure queues with correct parameters from `queueConfig`.
|
|
|
94
95
|
- `connection` (Object): RabbitMQ connection (for channel recreation)
|
|
95
96
|
- `logger` (Object): Logger instance (default: console)
|
|
96
97
|
- `queueConfig` (Object): Queue config instance (default: from mq-client-core)
|
|
98
|
+
- `serviceName` (string): Used in queue mismatch alerts (default: `unknown-service`)
|
|
99
|
+
- `alertOnMismatch` (boolean): Disable automatic 406 alerts (default: `true`)
|
|
97
100
|
|
|
98
101
|
**Returns:** `Promise<void>`
|
|
99
102
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/infrastructure-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Infrastructure orchestration utilities for OA Drive infrastructure services (health tracking, queue initialization, service discovery)",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@onlineapps/mq-client-core": "^1.0.25",
|
|
22
|
-
"@onlineapps/service-common": "^1.0.
|
|
22
|
+
"@onlineapps/service-common": "^1.0.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"jest": "^29.7.0"
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
// Re-export infrastructure readiness utilities from service-common (no duplication)
|
|
14
|
-
const { waitForInfrastructureReady, waitForHealthCheckQueueReady } = require('@onlineapps/service-common');
|
|
14
|
+
const { waitForInfrastructureReady, waitForHealthCheckQueueReady, sendMonitoringFailFallbackEmail } = require('@onlineapps/service-common');
|
|
15
|
+
|
|
16
|
+
// Re-export MQ client core utilities
|
|
17
|
+
const BaseClient = require('@onlineapps/mq-client-core');
|
|
18
|
+
const queueConfig = require('@onlineapps/mq-client-core/src/config/queueConfig');
|
|
15
19
|
|
|
16
20
|
const { initInfrastructureQueues } = require('./orchestration/initInfrastructureQueues');
|
|
17
21
|
const {
|
|
@@ -20,12 +24,20 @@ const {
|
|
|
20
24
|
createAmqplibAdapter
|
|
21
25
|
} = require('./health/healthPublisher');
|
|
22
26
|
const { createLogger } = require('./utils/logger');
|
|
27
|
+
const { sendQueueMismatchAlert } = require('./monitoring/queueMismatchReporter');
|
|
23
28
|
|
|
24
29
|
module.exports = {
|
|
25
|
-
//
|
|
26
|
-
|
|
30
|
+
// MQ Client Core (re-exported for convenience)
|
|
31
|
+
BaseClient,
|
|
32
|
+
MQClient: BaseClient, // Alias for compatibility
|
|
33
|
+
queueConfig,
|
|
34
|
+
|
|
35
|
+
// Service Common utilities (re-exported for convenience)
|
|
27
36
|
waitForInfrastructureReady,
|
|
28
37
|
waitForHealthCheckQueueReady,
|
|
38
|
+
sendMonitoringFailFallbackEmail,
|
|
39
|
+
|
|
40
|
+
// Orchestration utilities
|
|
29
41
|
initInfrastructureQueues,
|
|
30
42
|
|
|
31
43
|
// Health tracking utilities
|
|
@@ -34,6 +46,9 @@ module.exports = {
|
|
|
34
46
|
createAmqplibAdapter,
|
|
35
47
|
|
|
36
48
|
// Logger utility (consistent with conn-infra-mq pattern)
|
|
37
|
-
createLogger
|
|
49
|
+
createLogger,
|
|
50
|
+
|
|
51
|
+
// Monitoring utilities
|
|
52
|
+
sendQueueMismatchAlert
|
|
38
53
|
};
|
|
39
54
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { sendMonitoringFailFallbackEmail } = require('@onlineapps/service-common');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
|
|
6
|
+
const lastAlertMap = new Map();
|
|
7
|
+
|
|
8
|
+
function shouldThrottle(key, cooldownMs) {
|
|
9
|
+
const now = Date.now();
|
|
10
|
+
const last = lastAlertMap.get(key) || 0;
|
|
11
|
+
if (now - last < cooldownMs) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
lastAlertMap.set(key, now);
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Send structured alert when queue precondition fails (406 mismatch).
|
|
20
|
+
* Falls back to email via service-common if Slack/webhook unavailable.
|
|
21
|
+
* @param {Object} options
|
|
22
|
+
* @param {string} options.serviceName
|
|
23
|
+
* @param {string} options.queueName
|
|
24
|
+
* @param {Object} [options.expectedArguments]
|
|
25
|
+
* @param {string} [options.action]
|
|
26
|
+
* @param {Error} [options.error]
|
|
27
|
+
* @param {Object} [options.logger]
|
|
28
|
+
* @param {number} [options.cooldownMs]
|
|
29
|
+
*/
|
|
30
|
+
async function sendQueueMismatchAlert(options = {}) {
|
|
31
|
+
const {
|
|
32
|
+
serviceName = 'unknown-service',
|
|
33
|
+
queueName = 'unknown-queue',
|
|
34
|
+
expectedArguments = {},
|
|
35
|
+
action = 'assertQueue',
|
|
36
|
+
error,
|
|
37
|
+
logger = console,
|
|
38
|
+
cooldownMs = DEFAULT_COOLDOWN_MS,
|
|
39
|
+
extra = {}
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
const key = `${serviceName}:${queueName}`;
|
|
43
|
+
if (shouldThrottle(key, cooldownMs)) {
|
|
44
|
+
logger.warn('[QueueMismatchAlert] Throttling duplicate alert', { serviceName, queueName });
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const event = {
|
|
49
|
+
serviceName,
|
|
50
|
+
queueName,
|
|
51
|
+
action,
|
|
52
|
+
expectedArguments,
|
|
53
|
+
errorMessage: error?.message,
|
|
54
|
+
errorCode: error?.code,
|
|
55
|
+
timestamp: new Date().toISOString(),
|
|
56
|
+
...extra
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
logger.error('[QueueMismatchAlert] 406 PRECONDITION_FAILED detected', event);
|
|
60
|
+
|
|
61
|
+
const subject = `[Infra][QueueMismatch] ${serviceName} → ${queueName}`;
|
|
62
|
+
const textLines = [
|
|
63
|
+
`Service: ${serviceName}`,
|
|
64
|
+
`Queue: ${queueName}`,
|
|
65
|
+
`Action: ${action}`,
|
|
66
|
+
`Error: ${error?.message || 'Unknown'}`,
|
|
67
|
+
`Code: ${error?.code || 'N/A'}`,
|
|
68
|
+
`Expected arguments: ${JSON.stringify(expectedArguments, null, 2)}`,
|
|
69
|
+
`Timestamp: ${event.timestamp}`
|
|
70
|
+
];
|
|
71
|
+
const text = textLines.join('\n');
|
|
72
|
+
const html = `<p><strong>Infrastructure Queue Mismatch</strong></p><pre>${text}</pre>`;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const sent = await sendMonitoringFailFallbackEmail(subject, text, html);
|
|
76
|
+
if (!sent) {
|
|
77
|
+
logger.warn('[QueueMismatchAlert] Fallback email skipped (missing config)');
|
|
78
|
+
}
|
|
79
|
+
return sent;
|
|
80
|
+
} catch (emailError) {
|
|
81
|
+
logger.error('[QueueMismatchAlert] Failed to send fallback email', { error: emailError.message });
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
sendQueueMismatchAlert,
|
|
88
|
+
_internal: {
|
|
89
|
+
shouldThrottle,
|
|
90
|
+
lastAlertMap
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { sendQueueMismatchAlert } = require('../monitoring/queueMismatchReporter');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* initInfrastructureQueues.js
|
|
5
7
|
*
|
|
@@ -18,6 +20,8 @@
|
|
|
18
20
|
* @param {Object} [options.logger] - Logger instance (default: console)
|
|
19
21
|
* @param {Object} [options.connection] - RabbitMQ connection (for channel recreation)
|
|
20
22
|
* @param {Object} [options.queueConfig] - Queue config instance (default: from mq-client-core)
|
|
23
|
+
* @param {string} [options.serviceName] - Name of service initializing queues (for alerting context)
|
|
24
|
+
* @param {boolean} [options.alertOnMismatch=true] - Whether to send queue mismatch alerts
|
|
21
25
|
* @returns {Promise<void>}
|
|
22
26
|
*/
|
|
23
27
|
async function initInfrastructureQueues(channel, options = {}) {
|
|
@@ -27,6 +31,8 @@ async function initInfrastructureQueues(channel, options = {}) {
|
|
|
27
31
|
|
|
28
32
|
const logger = options.logger || console;
|
|
29
33
|
const connection = options.connection || channel.connection;
|
|
34
|
+
const serviceName = options.serviceName || process.env.SERVICE_NAME || 'unknown-service';
|
|
35
|
+
const alertOnMismatch = options.alertOnMismatch !== false;
|
|
30
36
|
|
|
31
37
|
// Load queueConfig from mq-client-core
|
|
32
38
|
// NOTE: queueConfig is part of mq-client-core because it's used by both:
|
|
@@ -102,6 +108,20 @@ async function initInfrastructureQueues(channel, options = {}) {
|
|
|
102
108
|
logger.log(`[QueueInit] ✓ Created/verified infrastructure queue: ${queueName}`);
|
|
103
109
|
} catch (assertError) {
|
|
104
110
|
if (assertError.code === 406) {
|
|
111
|
+
if (alertOnMismatch) {
|
|
112
|
+
try {
|
|
113
|
+
await sendQueueMismatchAlert({
|
|
114
|
+
serviceName,
|
|
115
|
+
queueName,
|
|
116
|
+
expectedArguments: config.arguments || {},
|
|
117
|
+
action: 'initInfrastructureQueues.assertQueue',
|
|
118
|
+
error: assertError,
|
|
119
|
+
logger
|
|
120
|
+
});
|
|
121
|
+
} catch (alertError) {
|
|
122
|
+
logger.warn('[QueueInit] Failed to send queue mismatch alert', alertError.message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
105
125
|
logger.warn(
|
|
106
126
|
`[QueueInit] ⚠ Queue ${queueName} exists with different arguments - deleting and recreating...`
|
|
107
127
|
);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
jest.mock('@onlineapps/service-common', () => ({
|
|
4
|
+
sendMonitoringFailFallbackEmail: jest.fn().mockResolvedValue(true)
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
sendQueueMismatchAlert,
|
|
9
|
+
_internal
|
|
10
|
+
} = require('../src/monitoring/queueMismatchReporter');
|
|
11
|
+
const { sendMonitoringFailFallbackEmail } = require('@onlineapps/service-common');
|
|
12
|
+
|
|
13
|
+
describe('queueMismatchReporter', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
_internal.lastAlertMap.clear();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('sends fallback email for queue mismatch', async () => {
|
|
20
|
+
await sendQueueMismatchAlert({
|
|
21
|
+
serviceName: 'test-service',
|
|
22
|
+
queueName: 'workflow.init',
|
|
23
|
+
expectedArguments: { 'x-message-ttl': 60000 },
|
|
24
|
+
error: new Error('PRECONDITION_FAILED')
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(sendMonitoringFailFallbackEmail).toHaveBeenCalledTimes(1);
|
|
28
|
+
const [subject, text] = sendMonitoringFailFallbackEmail.mock.calls[0];
|
|
29
|
+
expect(subject).toContain('test-service');
|
|
30
|
+
expect(text).toContain('workflow.init');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('throttles duplicate alerts within cooldown', async () => {
|
|
34
|
+
await sendQueueMismatchAlert({
|
|
35
|
+
serviceName: 'test-service',
|
|
36
|
+
queueName: 'workflow.init',
|
|
37
|
+
cooldownMs: 60000
|
|
38
|
+
});
|
|
39
|
+
await sendQueueMismatchAlert({
|
|
40
|
+
serviceName: 'test-service',
|
|
41
|
+
queueName: 'workflow.init',
|
|
42
|
+
cooldownMs: 60000
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(sendMonitoringFailFallbackEmail).toHaveBeenCalledTimes(1);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
|