@loopback/health
Version:
An extension exposes health check related endpoints with LoopBack 4
89 lines (78 loc) • 2.98 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
// Node module: @loopback/health
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {
HealthChecker,
LivenessCheck,
ReadinessCheck,
ShutdownCheck,
StartupCheck,
} from '@cloudnative/health';
import {
ContextView,
filterByTag,
inject,
LifeCycleObserver,
} from '@loopback/core';
import {EventEmitter, once as onceGeneric} from 'events';
import {HealthBindings, HealthTags} from '../keys';
import {LiveCheck, ReadyCheck} from '../types';
export class HealthObserver implements LifeCycleObserver {
private eventEmitter = new EventEmitter();
private startupCheck: Promise<void>;
private shutdownCheck: ShutdownCheck;
constructor(
(HealthBindings.HEALTH_CHECKER) private healthChecker: HealthChecker,
.view(filterByTag(HealthTags.LIVE_CHECK))
private liveChecks: ContextView<LiveCheck>,
.view(filterByTag(HealthTags.READY_CHECK))
private readyChecks: ContextView<ReadyCheck>,
) {
const startup = once(
this.eventEmitter,
'startup',
) as unknown as Promise<void>;
const startupCheck = new StartupCheck('startup', () => startup);
this.startupCheck = this.healthChecker.registerStartupCheck(startupCheck);
const shutdown = once(this.eventEmitter, 'shutdown');
this.shutdownCheck = new ShutdownCheck('shutdown', () => shutdown);
}
async start() {
this.healthChecker.registerShutdownCheck(this.shutdownCheck);
const liveChecks = await this.liveChecks.values();
const liveCheckBindings = this.liveChecks.bindings;
let index = 0;
for (const lc of liveChecks) {
const name = liveCheckBindings[index].key;
const check = new LivenessCheck(name, lc);
this.healthChecker.registerLivenessCheck(check);
index++;
}
const readyChecks = await this.readyChecks.values();
const readyCheckBindings = this.readyChecks.bindings;
index = 0;
for (const rc of readyChecks) {
const name = readyCheckBindings[index].key;
const check = new ReadinessCheck(name, rc);
this.healthChecker.registerReadinessCheck(check);
index++;
}
this.eventEmitter.emit('startup');
await this.startupCheck;
}
stop() {
this.eventEmitter.emit('shutdown');
// Fix a potential memory leak caused by
// https://github.com/CloudNativeJS/cloud-health/blob/2.1.2/src/healthcheck/HealthChecker.ts#L118
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onShutdownRequest = (this.healthChecker as any).onShutdownRequest;
if (onShutdownRequest != null) {
// Remove the listener from the current process
process.removeListener('SIGTERM', onShutdownRequest);
}
}
}
function once(emitter: EventEmitter, event: string | symbol): Promise<void> {
return onceGeneric(emitter, event) as unknown as Promise<void>;
}