@onlineapps/service-wrapper 2.1.89 → 2.1.90
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.
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
"enabled": true,
|
|
20
20
|
"metrics": ["requests", "errors", "duration"]
|
|
21
21
|
},
|
|
22
|
-
"startupAlerts": {
|
|
23
|
-
"enabled": true,
|
|
24
|
-
"cooldownMs": 600000,
|
|
25
|
-
"stateFile": "/tmp/oa_startup_failure_state.json"
|
|
26
|
-
},
|
|
27
22
|
"logging": {
|
|
28
23
|
"enabled": true,
|
|
29
24
|
"level": "info",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlineapps/service-wrapper",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.90",
|
|
4
4
|
"description": "Thin orchestration layer for microservices - delegates all infrastructure concerns to specialized connectors",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"@onlineapps/conn-infra-error-handler": "1.0.8",
|
|
30
30
|
"@onlineapps/conn-infra-mq": "1.1.66",
|
|
31
31
|
"@onlineapps/conn-orch-api-mapper": "1.0.26",
|
|
32
|
-
"@onlineapps/conn-orch-cookbook": "2.0.
|
|
33
|
-
"@onlineapps/conn-orch-orchestrator": "1.0.
|
|
34
|
-
"@onlineapps/conn-orch-registry": "1.1.
|
|
32
|
+
"@onlineapps/conn-orch-cookbook": "2.0.28",
|
|
33
|
+
"@onlineapps/conn-orch-orchestrator": "1.0.92",
|
|
34
|
+
"@onlineapps/conn-orch-registry": "1.1.44",
|
|
35
35
|
"@onlineapps/conn-orch-validator": "2.0.25",
|
|
36
36
|
"@onlineapps/monitoring-core": "1.0.20",
|
|
37
37
|
"@onlineapps/service-common": "1.0.15",
|
package/src/ServiceWrapper.js
CHANGED
|
@@ -417,20 +417,16 @@ class ServiceWrapper {
|
|
|
417
417
|
/**
|
|
418
418
|
* Handle initialization error with restart strategy
|
|
419
419
|
* @private
|
|
420
|
+
*
|
|
421
|
+
* NOTE: Alerting is NOT done here. Before registration, service is in "dev care" phase.
|
|
422
|
+
* After registration, Registry monitors heartbeats and triggers alerts via Monitoring.
|
|
423
|
+
* This keeps alerting responsibility centralized in Monitoring (Separation of Concerns).
|
|
420
424
|
*/
|
|
421
425
|
async _handleInitializationError(phase, phaseName, error, isTransient = false) {
|
|
422
426
|
this._logPhase(phase, phaseName, 'FAILED', error);
|
|
423
427
|
|
|
424
428
|
// Cleanup before restart
|
|
425
429
|
await this._cleanupBeforeRestart();
|
|
426
|
-
|
|
427
|
-
// Startup failure alert (works without MQ/monitoring)
|
|
428
|
-
try {
|
|
429
|
-
await this._maybeSendStartupFailureAlert({ phase, phaseName, error, isTransient });
|
|
430
|
-
} catch (alertErr) {
|
|
431
|
-
// Alerts must never block restart; log and continue.
|
|
432
|
-
console.warn('[StartupAlerts] Failed to send startup failure alert:', alertErr.message);
|
|
433
|
-
}
|
|
434
430
|
|
|
435
431
|
if (isTransient) {
|
|
436
432
|
// Přechodná chyba → restart může pomoci
|
|
@@ -443,81 +439,6 @@ class ServiceWrapper {
|
|
|
443
439
|
}
|
|
444
440
|
}
|
|
445
441
|
|
|
446
|
-
/**
|
|
447
|
-
* Send startup-failure alert via SMTP (independent of MQ).
|
|
448
|
-
* Uses service-common sendMonitoringFailFallbackEmail (SMTP config via INFRA_REPORT_* env).
|
|
449
|
-
* Throttled by a persistent state file (survives container restarts).
|
|
450
|
-
*
|
|
451
|
-
* @private
|
|
452
|
-
*/
|
|
453
|
-
async _maybeSendStartupFailureAlert({ phase, phaseName, error, isTransient }) {
|
|
454
|
-
const cfg = this.config.wrapper?.startupAlerts;
|
|
455
|
-
if (!cfg || cfg.enabled !== true) {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const cooldownMs = cfg.cooldownMs;
|
|
460
|
-
const stateFile = cfg.stateFile;
|
|
461
|
-
if (typeof cooldownMs !== 'number' || Number.isNaN(cooldownMs) || cooldownMs <= 0) {
|
|
462
|
-
throw new Error(`[StartupAlerts] Invalid configuration - wrapper.startupAlerts.cooldownMs must be a positive number, got: ${cooldownMs}`);
|
|
463
|
-
}
|
|
464
|
-
if (typeof stateFile !== 'string' || stateFile.trim() === '') {
|
|
465
|
-
throw new Error(`[StartupAlerts] Invalid configuration - wrapper.startupAlerts.stateFile must be a non-empty string, got: ${stateFile}`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const fs = require('fs');
|
|
469
|
-
const path = require('path');
|
|
470
|
-
const serviceName = this.config.service?.name || 'unnamed-service';
|
|
471
|
-
const now = Date.now();
|
|
472
|
-
|
|
473
|
-
let state = { lastAlertAt: 0, failureCount: 0, lastFailureAt: 0 };
|
|
474
|
-
try {
|
|
475
|
-
if (fs.existsSync(stateFile)) {
|
|
476
|
-
const raw = fs.readFileSync(stateFile, 'utf8');
|
|
477
|
-
state = { ...state, ...(JSON.parse(raw) || {}) };
|
|
478
|
-
} else {
|
|
479
|
-
// Ensure parent dir exists (best-effort)
|
|
480
|
-
const dir = path.dirname(stateFile);
|
|
481
|
-
try { fs.mkdirSync(dir, { recursive: true }); } catch (_) { /* ignore */ }
|
|
482
|
-
}
|
|
483
|
-
} catch (e) {
|
|
484
|
-
// If state cannot be read, continue with defaults (alerting must not crash init)
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
state.failureCount = (state.failureCount || 0) + 1;
|
|
488
|
-
state.lastFailureAt = now;
|
|
489
|
-
|
|
490
|
-
const shouldSend = !state.lastAlertAt || (now - state.lastAlertAt) >= cooldownMs;
|
|
491
|
-
if (!shouldSend) {
|
|
492
|
-
// Persist updated counters anyway
|
|
493
|
-
try { fs.writeFileSync(stateFile, JSON.stringify(state), 'utf8'); } catch (_) { /* ignore */ }
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const { sendMonitoringFailFallbackEmail } = require('@onlineapps/service-common');
|
|
498
|
-
const transientLabel = isTransient ? 'TRANSIENT' : 'PERMANENT';
|
|
499
|
-
const subject = `[StartupFailure] ${serviceName} phase ${phase} (${transientLabel})`;
|
|
500
|
-
const text = [
|
|
501
|
-
`Service: ${serviceName}`,
|
|
502
|
-
`Phase: ${phase} (${phaseName})`,
|
|
503
|
-
`Type: ${transientLabel}`,
|
|
504
|
-
`Failure count (container): ${state.failureCount}`,
|
|
505
|
-
`Timestamp: ${new Date(now).toISOString()}`,
|
|
506
|
-
`Error: ${error?.message || 'unknown error'}`
|
|
507
|
-
].join('\n');
|
|
508
|
-
const html = `<pre>${text}</pre>`;
|
|
509
|
-
|
|
510
|
-
const sent = await sendMonitoringFailFallbackEmail(subject, text, html);
|
|
511
|
-
state.lastAlertAt = now;
|
|
512
|
-
try { fs.writeFileSync(stateFile, JSON.stringify(state), 'utf8'); } catch (_) { /* ignore */ }
|
|
513
|
-
|
|
514
|
-
if (sent) {
|
|
515
|
-
this.logger?.info('[StartupAlerts] ✓ Startup failure alert sent', { service: serviceName, phase, transient: isTransient });
|
|
516
|
-
} else {
|
|
517
|
-
this.logger?.warn('[StartupAlerts] Startup failure alert not sent (SMTP config missing or send failed)', { service: serviceName, phase });
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
442
|
async initialize() {
|
|
522
443
|
if (this.isInitialized) {
|
|
523
444
|
// Logger might not be initialized yet, use console as fallback
|
|
@@ -911,6 +832,7 @@ class ServiceWrapper {
|
|
|
911
832
|
amqpUrl: mqUrl,
|
|
912
833
|
serviceName: serviceName,
|
|
913
834
|
version: this.config.service?.version,
|
|
835
|
+
specificationEndpoint: this.config.service?.specificationEndpoint || '/api/v1/specification',
|
|
914
836
|
registryQueue: 'registry.register', // Use correct queue name
|
|
915
837
|
registryUrl: registryUrl,
|
|
916
838
|
logger: this.logger || console,
|