import moment from 'moment'; import type IDisposable from '../lang/IDisposable'; import {Events} from '../lang/Events'; const checkForRecoverabilityTimeout = moment.duration(30, 'seconds'); interface IHealthCheckable { checkForRecoverability(): Promise; } export class HealthCheck { private readonly _environment: string; private readonly _app: string; private readonly _version: string; private readonly _regionName: string; private readonly _zone: string; private readonly _checks: IHealthCheckable[]; private readonly _since: string; private readonly _events: Events; private _status: string; private _enabled: boolean; constructor(environment: string, app: string, version: string, regionName: string, zone: string, ...checkForRecoverability: IHealthCheckable[]) { this._environment = environment; this._app = app; this._version = version; this._regionName = regionName; this._zone = zone; this._checks = checkForRecoverability; this._since = moment.utc().toISOString(); this._status = 'starting'; this._events = new Events(); this._enabled = true; } public start() { this._status = 'starting'; this._events.emit('starting'); this._events.emit('status-changed'); return null; } public getInfo() { return { environment: this._environment, app: this._app, version: this._version, region: this._regionName, zone: this._zone, since: this._since }; } public async checkHealth() { if (this._status !== 'ok') { return { status: this._status, environment: this._environment, app: this._app, version: this._version, zone: this._zone }; } try { const results = await Promise.all( this._checks.map(async check => { const isHealthy = await check.checkForRecoverability(); if (!isHealthy) { console.warn('Health check failed [%s]', check); } else { console.debug('Health check passed [%s]', check); } return isHealthy; }) ); const success = results.every(result => result === true); if (success === true) { let status = this._status; if (status === 'ok' && !this._enabled) { status = 'disabled'; } return { status, environment: this._environment, app: this._app, version: this._version, zone: this._zone }; } this._events.emit('health-check-failed'); return { status: 'health-check-failed', environment: this._environment, app: this._app, version: this._version, zone: this._zone }; } catch (e) { this._events.emit('health-check-failed'); console.warn('Health check failed', e); return { status: 'health-check-failed', environment: this._environment, app: this._app, version: this._version, zone: this._zone, reason: (e as Error).message }; } } public on(event: string, listener: (...args: unknown[]) => void | Promise): IDisposable { return this._events.on(event, listener); } public markReady() { this._status = 'ok'; this._events.emit('ok'); this._events.emit('status-changed'); return null; } public enable(): void { this._enabled = true; } public disable(): void { this._enabled = false; } }