144 lines
3.4 KiB
TypeScript
144 lines
3.4 KiB
TypeScript
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<boolean>;
|
|
}
|
|
|
|
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<void>): 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;
|
|
}
|
|
}
|