From 545862eab07688cce26a583c7a34a986f13ab574 Mon Sep 17 00:00:00 2001 From: Livio Brunner Date: Wed, 31 Jan 2024 22:23:43 +0100 Subject: [PATCH] refactor: All remaining health indicators to new internal api --- .../database/mikro-orm.health.ts | 80 +++++++-------- .../database/mongoose.health.ts | 45 +++------ .../database/prisma.health.ts | 27 ++--- .../database/sequelize.health.ts | 45 +++------ .../database/typeorm.health.ts | 6 +- lib/health-indicator/disk/disk.health.ts | 33 +++---- .../health-indicator.service.ts | 2 +- lib/health-indicator/http/http.health.ts | 60 ++++++----- lib/health-indicator/memory/memory.health.ts | 38 ++++--- .../microservice/grpc.health.ts | 99 +++++++++---------- .../microservice/microservice.health.ts | 63 +++--------- 11 files changed, 209 insertions(+), 289 deletions(-) diff --git a/lib/health-indicator/database/mikro-orm.health.ts b/lib/health-indicator/database/mikro-orm.health.ts index 4023fd6ba0..c7d1cd9bfb 100644 --- a/lib/health-indicator/database/mikro-orm.health.ts +++ b/lib/health-indicator/database/mikro-orm.health.ts @@ -38,46 +38,6 @@ export class MikroOrmHealthIndicator extends HealthIndicator { this.checkDependantPackages(); } - /** - * Checks if responds in (default) 1000ms and - * returns a result object corresponding to the result - * @param key The key which will be used for the result object - * @param options The options for the ping - * - * @example - * MikroOrmHealthIndicator.pingCheck('database', { timeout: 1500 }); - */ - public async pingCheck( - key: string, - options: MikroOrmPingCheckSettings = {}, - ): Promise { - this.checkDependantPackages(); - const check = this.healthIndicatorService.check(key); - - const timeout = options.timeout || 1000; - const connection = options.connection || this.getContextConnection(); - - if (!connection) { - return check.down(); - } - - try { - await this.pingDb(connection, timeout); - } catch (error) { - // Check if the error is a timeout error - if (error instanceof PromiseTimeoutError) { - return check.down(`timeout of ${timeout}ms exceeded`); - } - if (error instanceof DatabaseNotConnectedError) { - return check.down(error.message); - } - - return check.down(); - } - - return check.up(); - } - private checkDependantPackages() { checkPackages( ['@mikro-orm/nestjs', '@mikro-orm/core'], @@ -118,4 +78,44 @@ export class MikroOrmHealthIndicator extends HealthIndicator { return await promiseTimeout(timeout, checker()); } + + /** + * Checks if responds in (default) 1000ms and + * returns a result object corresponding to the result + * @param key The key which will be used for the result object + * @param options The options for the ping + * + * @example + * MikroOrmHealthIndicator.pingCheck('database', { timeout: 1500 }); + */ + public async pingCheck( + key: Key, + options: MikroOrmPingCheckSettings = {}, + ): Promise> { + this.checkDependantPackages(); + const check = this.healthIndicatorService.check(key); + + const timeout = options.timeout || 1000; + const connection = options.connection || this.getContextConnection(); + + if (!connection) { + return check.down(); + } + + try { + await this.pingDb(connection, timeout); + } catch (error) { + // Check if the error is a timeout error + if (error instanceof PromiseTimeoutError) { + return check.down(`timeout of ${timeout}ms exceeded`); + } + if (error instanceof DatabaseNotConnectedError) { + return check.down(error.message); + } + + return check.down(); + } + + return check.up(); + } } diff --git a/lib/health-indicator/database/mongoose.health.ts b/lib/health-indicator/database/mongoose.health.ts index a381fb6dac..71b2537222 100644 --- a/lib/health-indicator/database/mongoose.health.ts +++ b/lib/health-indicator/database/mongoose.health.ts @@ -1,18 +1,14 @@ import { Injectable, Scope } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import type * as NestJSMongoose from '@nestjs/mongoose'; -import { - type HealthIndicatorResult, - TimeoutError, - ConnectionNotFoundError, -} from '../..'; -import { HealthCheckError } from '../../health-check/health-check.error'; +import { type HealthIndicatorResult } from '../..'; import { promiseTimeout, TimeoutError as PromiseTimeoutError, checkPackages, } from '../../utils'; import { HealthIndicator } from '../health-indicator'; +import { HealthIndicatorService } from '../health-indicator.service'; export interface MongoosePingCheckSettings { /** @@ -34,12 +30,10 @@ export interface MongoosePingCheckSettings { */ @Injectable({ scope: Scope.TRANSIENT }) export class MongooseHealthIndicator extends HealthIndicator { - /** - * Initializes the MongooseHealthIndicator - * - * @param {ModuleRef} moduleRef The NestJS module reference - */ - constructor(private moduleRef: ModuleRef) { + constructor( + private readonly moduleRef: ModuleRef, + private readonly healthIndicatorService: HealthIndicatorService, + ) { super(); this.checkDependantPackages(); } @@ -92,41 +86,30 @@ export class MongooseHealthIndicator extends HealthIndicator { * @example * mongooseHealthIndicator.pingCheck('mongodb', { timeout: 1500 }); */ - public async pingCheck( - key: string, + public async pingCheck( + key: Key, options: MongoosePingCheckSettings = {}, - ): Promise { + ): Promise> { this.checkDependantPackages(); + const check = this.healthIndicatorService.check(key); const connection = options.connection || this.getContextConnection(); const timeout = options.timeout || 1000; if (!connection) { - throw new ConnectionNotFoundError( - this.getStatus(key, false, { - message: 'Connection provider not found in application context', - }), - ); + return check.down('Connection provider not found in application context'); } try { await this.pingDb(connection, timeout); } catch (err) { if (err instanceof PromiseTimeoutError) { - throw new TimeoutError( - timeout, - this.getStatus(key, false, { - message: `timeout of ${timeout}ms exceeded`, - }), - ); + return check.down(`timeout of ${timeout}ms exceeded`); } - throw new HealthCheckError( - `${key} is not available`, - this.getStatus(key, false), - ); + return check.down(); } - return this.getStatus(key, true); + return check.up(); } } diff --git a/lib/health-indicator/database/prisma.health.ts b/lib/health-indicator/database/prisma.health.ts index 6dbbb2887f..ffe93893d8 100644 --- a/lib/health-indicator/database/prisma.health.ts +++ b/lib/health-indicator/database/prisma.health.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { TimeoutError } from '../../errors'; -import { HealthCheckError } from '../../health-check'; import { promiseTimeout, TimeoutError as PromiseTimeoutError, } from '../../utils'; import { HealthIndicator } from '../health-indicator'; +import { type HealthIndicatorResult } from '../health-indicator-result.interface'; +import { HealthIndicatorService } from '../health-indicator.service'; type PingCommandSignature = { [Key in string]?: number }; @@ -35,7 +35,7 @@ export interface PrismaClientPingCheckSettings { */ @Injectable() export class PrismaHealthIndicator extends HealthIndicator { - constructor() { + constructor(private readonly healthIndicatorService: HealthIndicatorService) { super(); } @@ -69,31 +69,24 @@ export class PrismaHealthIndicator extends HealthIndicator { * @param prismaClient PrismaClient * @param options The options for the ping */ - public async pingCheck( - key: string, + public async pingCheck( + key: Key, prismaClient: PrismaClient, options: PrismaClientPingCheckSettings = {}, - ): Promise { + ): Promise> { + const check = this.healthIndicatorService.check(key); const timeout = options.timeout || 1000; try { await this.pingDb(timeout, prismaClient); } catch (error) { if (error instanceof PromiseTimeoutError) { - throw new TimeoutError( - timeout, - this.getStatus(key, false, { - message: `timeout of ${timeout}ms exceeded`, - }), - ); + return check.down(`timeout of ${timeout}ms exceeded`); } - throw new HealthCheckError( - `${key} is not available`, - this.getStatus(key, false), - ); + return check.down(); } - return this.getStatus(key, true); + return check.up(); } } diff --git a/lib/health-indicator/database/sequelize.health.ts b/lib/health-indicator/database/sequelize.health.ts index 44bb0ef971..882890937c 100644 --- a/lib/health-indicator/database/sequelize.health.ts +++ b/lib/health-indicator/database/sequelize.health.ts @@ -1,18 +1,14 @@ import { Injectable, Scope } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import type * as NestJSSequelize from '@nestjs/sequelize'; -import { - type HealthIndicatorResult, - TimeoutError, - ConnectionNotFoundError, -} from '../..'; -import { HealthCheckError } from '../../health-check/health-check.error'; +import { type HealthIndicatorResult } from '../..'; import { promiseTimeout, TimeoutError as PromiseTimeoutError, checkPackages, } from '../../utils'; import { HealthIndicator } from '../health-indicator'; +import { HealthIndicatorService } from '../health-indicator.service'; export interface SequelizePingCheckSettings { /** @@ -34,12 +30,10 @@ export interface SequelizePingCheckSettings { */ @Injectable({ scope: Scope.TRANSIENT }) export class SequelizeHealthIndicator extends HealthIndicator { - /** - * Initializes the SequelizeHealthIndicator - * - * @param {ModuleRef} moduleRef The NestJS module reference - */ - constructor(private moduleRef: ModuleRef) { + constructor( + private readonly moduleRef: ModuleRef, + private readonly healthIndicatorService: HealthIndicatorService, + ) { super(); this.checkDependantPackages(); } @@ -88,41 +82,30 @@ export class SequelizeHealthIndicator extends HealthIndicator { * @example * sequelizeHealthIndicator.pingCheck('database', { timeout: 1500 }); */ - public async pingCheck( - key: string, + public async pingCheck( + key: Key, options: SequelizePingCheckSettings = {}, - ): Promise { + ): Promise> { this.checkDependantPackages(); + const check = this.healthIndicatorService.check(key); const connection = options.connection || this.getContextConnection(); const timeout = options.timeout || 1000; if (!connection) { - throw new ConnectionNotFoundError( - this.getStatus(key, false, { - message: 'Connection provider not found in application context', - }), - ); + return check.down('Connection provider not found in application context'); } try { await this.pingDb(connection, timeout); } catch (err) { if (err instanceof PromiseTimeoutError) { - throw new TimeoutError( - timeout, - this.getStatus(key, false, { - message: `timeout of ${timeout}ms exceeded`, - }), - ); + return check.down(`timeout of ${timeout}ms exceeded`); } - throw new HealthCheckError( - `${key} is not available`, - this.getStatus(key, false), - ); + return check.down(); } - return this.getStatus(key, true); + return check.up(); } } diff --git a/lib/health-indicator/database/typeorm.health.ts b/lib/health-indicator/database/typeorm.health.ts index 13c51253fa..fac48e35ed 100644 --- a/lib/health-indicator/database/typeorm.health.ts +++ b/lib/health-indicator/database/typeorm.health.ts @@ -2,7 +2,7 @@ import { Injectable, Scope } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import type * as NestJSTypeOrm from '@nestjs/typeorm'; import type * as TypeOrm from 'typeorm'; -import { HealthIndicator } from '../'; +import { HealthIndicator, type HealthIndicatorResult } from '../'; import { MongoConnectionError } from '../../errors'; import { TimeoutError as PromiseTimeoutError, @@ -124,7 +124,7 @@ export class TypeOrmHealthIndicator extends HealthIndicator { async pingCheck( key: Key, options: TypeOrmPingCheckSettings = {}, - ) { + ): Promise> { const check = this.healthIndicatorService.check(key); this.checkDependantPackages(); @@ -146,7 +146,7 @@ export class TypeOrmHealthIndicator extends HealthIndicator { return check.down(err.message); } - return check.down(`${key} is not available`); + return check.down(); } return check.up(); diff --git a/lib/health-indicator/disk/disk.health.ts b/lib/health-indicator/disk/disk.health.ts index b08d0d19a6..ef2b8ecdda 100644 --- a/lib/health-indicator/disk/disk.health.ts +++ b/lib/health-indicator/disk/disk.health.ts @@ -6,9 +6,9 @@ import { type DiskOptionsWithThresholdPercent, } from './disk-health-options.type'; import { HealthIndicator, type HealthIndicatorResult } from '../'; -import { StorageExceededError } from '../../errors'; import { STORAGE_EXCEEDED } from '../../errors/messages.constant'; import { CHECK_DISK_SPACE_LIB } from '../../terminus.constants'; +import { HealthIndicatorService } from '../health-indicator.service'; type CheckDiskSpace = typeof checkDiskSpace; @@ -21,16 +21,10 @@ type CheckDiskSpace = typeof checkDiskSpace; */ @Injectable() export class DiskHealthIndicator extends HealthIndicator { - /** - * Initializes the health indicator - * - * @param {CheckDiskSpace} checkDiskSpace The check-disk-space library - * - * @internal - */ constructor( @Inject(CHECK_DISK_SPACE_LIB) - private checkDiskSpace: CheckDiskSpace, + private readonly checkDiskSpace: CheckDiskSpace, + private readonly healthIndicatorService: HealthIndicatorService, ) { super(); } @@ -70,13 +64,19 @@ export class DiskHealthIndicator extends HealthIndicator { * // The used disk storage should not exceed 50% of the full disk size * diskHealthIndicator.checkStorage('storage', { thresholdPercent: 0.5, path: 'C:\\' }); */ - public async checkStorage( - key: string, + public async checkStorage( + key: Key, options: DiskHealthIndicatorOptions, - ): Promise { + ): Promise> { + const check = this.healthIndicatorService.check(key); const { free, size } = await this.checkDiskSpace(options.path); const used = size - free; + // Prevent division by zero + if (isNaN(size) || size === 0) { + return check.down(STORAGE_EXCEEDED('disk storage')); + } + let isHealthy = false; if (this.isOptionThresholdPercent(options)) { isHealthy = options.thresholdPercent >= used / size; @@ -85,13 +85,8 @@ export class DiskHealthIndicator extends HealthIndicator { } if (!isHealthy) { - throw new StorageExceededError( - 'disk storage', - this.getStatus(key, false, { - message: STORAGE_EXCEEDED('disk storage'), - }), - ); + return check.down(STORAGE_EXCEEDED('disk storage')); } - return this.getStatus(key, true); + return check.up(); } } diff --git a/lib/health-indicator/health-indicator.service.ts b/lib/health-indicator/health-indicator.service.ts index eb71d6706a..9c9f1b447d 100644 --- a/lib/health-indicator/health-indicator.service.ts +++ b/lib/health-indicator/health-indicator.service.ts @@ -16,7 +16,7 @@ export class HealthIndicatorService { type AdditionalData = Record; -class HealthIndicatorSession> { +export class HealthIndicatorSession = string> { constructor(private readonly key: Key) {} /** diff --git a/lib/health-indicator/http/http.health.ts b/lib/health-indicator/http/http.health.ts index f909ba344c..45aae0d96c 100644 --- a/lib/health-indicator/http/http.health.ts +++ b/lib/health-indicator/http/http.health.ts @@ -9,9 +9,12 @@ import { } from './axios.interfaces'; import { HealthIndicator, type HealthIndicatorResult } from '..'; import { type AxiosError } from '../../errors/axios.error'; -import { HealthCheckError } from '../../health-check/health-check.error'; import { TERMINUS_LOGGER } from '../../health-check/logger/logger.provider'; import { checkPackages, isAxiosError } from '../../utils'; +import { + HealthIndicatorService, + type HealthIndicatorSession, +} from '../health-indicator.service'; interface HttpClientLike { request(config: any): Observable>; @@ -34,6 +37,7 @@ export class HttpHealthIndicator extends HealthIndicator { private readonly moduleRef: ModuleRef, @Inject(TERMINUS_LOGGER) private readonly logger: ConsoleLogger, + private readonly healthIndicatorService: HealthIndicatorService, ) { super(); if (this.logger instanceof ConsoleLogger) { @@ -74,11 +78,10 @@ export class HttpHealthIndicator extends HealthIndicator { * * @throws {HealthCheckError} */ - private generateHttpError(key: string, error: AxiosError | any) { - if (!isAxiosError(error)) { - return; - } - + private generateHttpError( + check: HealthIndicatorSession, + error: AxiosError | any, + ) { const response: { [key: string]: any } = { message: error.message, }; @@ -88,10 +91,7 @@ export class HttpHealthIndicator extends HealthIndicator { response.statusText = error.response.statusText; } - throw new HealthCheckError( - error.message, - this.getStatus(key, false, response), - ); + return check.down(response); } /** @@ -106,15 +106,16 @@ export class HttpHealthIndicator extends HealthIndicator { * @example * httpHealthIndicator.pingCheck('google', 'https://google.com', { timeout: 800 }) */ - async pingCheck( - key: string, + async pingCheck( + key: Key, url: string, { httpClient, ...options }: AxiosRequestConfig & { httpClient?: HttpClientLike } = {}, - ): Promise { - let isHealthy = false; + ): Promise> { + const check = this.healthIndicatorService.check(key); + // In case the user has a preconfigured HttpService (see `HttpModule.register`) // we just let him/her pass in this HttpService so that he/she does not need to // reconfigure it. @@ -123,23 +124,27 @@ export class HttpHealthIndicator extends HealthIndicator { try { await lastValueFrom(httpService.request({ url, ...options })); - isHealthy = true; } catch (err) { - this.generateHttpError(key, err); + if (isAxiosError(err)) { + return this.generateHttpError(check, err); + } + + throw err; } - return this.getStatus(key, isHealthy); + return check.up(); } - async responseCheck( - key: string, + async responseCheck( + key: Key, url: URL | string, callback: (response: AxiosResponse) => boolean | Promise, { httpClient, ...options }: AxiosRequestConfig & { httpClient?: HttpClientLike } = {}, - ): Promise { + ): Promise> { + const check = this.healthIndicatorService.check(key); const httpService = httpClient || this.getHttpService(); let response: AxiosResponse; @@ -153,10 +158,14 @@ export class HttpHealthIndicator extends HealthIndicator { if (!isAxiosError(error)) { throw error; } + // We received an Axios Error but no response for unknown reasons. if (!error.response) { - throw this.generateHttpError(key, error); + return check.down(error.message); } + // We store the response no matter if the http request was successful or not. + // So that we can pass it to the callback function and the user can decide + // if the response is healthy or not. response = error.response; axiosError = error; } @@ -165,15 +174,12 @@ export class HttpHealthIndicator extends HealthIndicator { if (!isHealthy) { if (axiosError) { - throw this.generateHttpError(key, axiosError); + return this.generateHttpError(check, axiosError); } - throw new HealthCheckError( - `${key} is not available`, - this.getStatus(key, false), - ); + return check.down(); } - return this.getStatus(key, true); + return check.up(); } } diff --git a/lib/health-indicator/memory/memory.health.ts b/lib/health-indicator/memory/memory.health.ts index f9b434af17..a58482e36e 100644 --- a/lib/health-indicator/memory/memory.health.ts +++ b/lib/health-indicator/memory/memory.health.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { HealthIndicator, type HealthIndicatorResult } from '../'; -import { StorageExceededError } from '../../errors'; import { STORAGE_EXCEEDED } from '../../errors/messages.constant'; +import { HealthIndicatorService } from '../health-indicator.service'; /** * The MemoryHealthIndicator contains checks which are related @@ -12,6 +12,10 @@ import { STORAGE_EXCEEDED } from '../../errors/messages.constant'; */ @Injectable() export class MemoryHealthIndicator extends HealthIndicator { + constructor(private readonly healthIndicatorService: HealthIndicatorService) { + super(); + } + /** * Checks the heap space and returns the status * @@ -27,22 +31,18 @@ export class MemoryHealthIndicator extends HealthIndicator { * // The process should not use more than 150MB memory * memoryHealthIndicator.checkHeap('memory_heap', 150 * 1024 * 1024); */ - public async checkHeap( - key: string, + public async checkHeap( + key: Key, heapUsedThreshold: number, - ): Promise { + ): Promise> { + const check = this.healthIndicatorService.check(key); const { heapUsed } = process.memoryUsage(); if (heapUsedThreshold < heapUsed) { - throw new StorageExceededError( - 'heap', - this.getStatus(key, false, { - message: STORAGE_EXCEEDED('heap'), - }), - ); + return check.down(STORAGE_EXCEEDED('heap')); } - return this.getStatus(key, true); + return check.up(); } /** @@ -59,21 +59,17 @@ export class MemoryHealthIndicator extends HealthIndicator { * // The process should not have more than 150MB allocated * memoryHealthIndicator.checkRSS('memory_rss', 150 * 1024 * 1024); */ - public async checkRSS( - key: string, + public async checkRSS( + key: Key, rssThreshold: number, - ): Promise { + ): Promise> { + const check = this.healthIndicatorService.check(key); const { rss } = process.memoryUsage(); if (rssThreshold < rss) { - throw new StorageExceededError( - 'rss', - this.getStatus(key, false, { - message: STORAGE_EXCEEDED('rss'), - }), - ); + return check.down(STORAGE_EXCEEDED('rss')); } - return this.getStatus(key, true); + return check.up(); } } diff --git a/lib/health-indicator/microservice/grpc.health.ts b/lib/health-indicator/microservice/grpc.health.ts index 8d85f4a907..304e759ed4 100644 --- a/lib/health-indicator/microservice/grpc.health.ts +++ b/lib/health-indicator/microservice/grpc.health.ts @@ -2,12 +2,7 @@ import { join } from 'path'; import { Injectable, Scope } from '@nestjs/common'; import type * as NestJSMicroservices from '@nestjs/microservices'; import { type Observable } from 'rxjs'; -import { - type HealthIndicatorResult, - TimeoutError, - UnhealthyResponseCodeError, -} from '../..'; -import { HealthCheckError } from '../../health-check/health-check.error'; +import { type HealthIndicatorResult } from '../..'; import { checkPackages, isError, @@ -16,6 +11,7 @@ import { TimeoutError as PromiseTimeoutError, } from '../../utils'; import { HealthIndicator } from '../health-indicator'; +import { HealthIndicatorService } from '../health-indicator.service'; /** * The status of the request service @@ -100,7 +96,7 @@ export class GRPCHealthIndicator extends HealthIndicator { /** * Initializes the health indicator */ - constructor() { + constructor(private readonly healthIndicatorService: HealthIndicatorService) { super(); this.checkDependantPackages(); } @@ -142,6 +138,23 @@ export class GRPCHealthIndicator extends HealthIndicator { }); } + getHealthService( + service: string, + settings: CheckGRPCServiceOptions, + ) { + if (this.openChannels.has(service)) { + return this.openChannels.get(service)!; + } + + const client = this.createClient(settings); + const healthService = client.getService( + settings.healthServiceName as string, + ); + + this.openChannels.set(service, healthService); + return healthService; + } + /** * Checks if the given service is up using the standard health check * specification of GRPC. @@ -180,11 +193,14 @@ export class GRPCHealthIndicator extends HealthIndicator { */ async checkService< GrpcOptions extends GrpcClientOptionsLike = GrpcClientOptionsLike, + Key extends string = string, >( - key: string, + key: Key, service: string, options: CheckGRPCServiceOptions = {}, - ): Promise { + ): Promise> { + const check = this.healthIndicatorService.check(key); + const defaultOptions: CheckGRPCServiceOptions = { package: 'grpc.health.v1', protoPath: join(__dirname, './protos/health.proto'), @@ -199,31 +215,19 @@ export class GRPCHealthIndicator extends HealthIndicator { let healthService: GRPCHealthService; try { - if (this.openChannels.has(service)) { - healthService = this.openChannels.get(service)!; - } else { - const client = this.createClient(settings); - - healthService = client.getService( - settings.healthServiceName as string, - ); - - this.openChannels.set(service, healthService); - } + healthService = this.getHealthService(service, settings); } catch (err) { if (err instanceof TypeError) { throw err; } if (isError(err)) { - throw new HealthCheckError( - err.message, - this.getStatus(key, false, { message: err.message }), - ); + return check.down(err.message); } - throw new HealthCheckError( - err as any, - this.getStatus(key, false, { message: err as any }), - ); + if (typeof err === 'string') { + return check.down(err); + } + + return check.down(); } let response: HealthCheckResponse; @@ -238,39 +242,30 @@ export class GRPCHealthIndicator extends HealthIndicator { ); } catch (err) { if (err instanceof PromiseTimeoutError) { - throw new TimeoutError( - settings.timeout as number, - this.getStatus(key, false, { - message: `timeout of ${settings.timeout}ms exceeded`, - }), - ); + return check.down(`timeout of ${settings.timeout}ms exceeded`); } if (isError(err)) { - throw new HealthCheckError( - err.message, - this.getStatus(key, false, { message: err.message }), - ); + return check.down(err.message); } - throw new HealthCheckError( - err as any, - this.getStatus(key, false, { message: err as any }), - ); + if (typeof err === 'string') { + return check.down(err); + } + + return check.down(); } const isHealthy = response.status === ServingStatus.SERVING; - const status = this.getStatus(key, isHealthy, { - statusCode: response.status, - servingStatus: ServingStatus[response.status], - }); - if (!isHealthy) { - throw new UnhealthyResponseCodeError( - `${response.status}, ${ServingStatus[response.status]}`, - status, - ); + return check.down({ + statusCode: response.status, + servingStatus: ServingStatus[response.status], + }); } - return status; + return check.up({ + statusCode: response.status, + servingStatus: ServingStatus[response.status], + }); } } diff --git a/lib/health-indicator/microservice/microservice.health.ts b/lib/health-indicator/microservice/microservice.health.ts index b4eb552716..9541fe64d1 100644 --- a/lib/health-indicator/microservice/microservice.health.ts +++ b/lib/health-indicator/microservice/microservice.health.ts @@ -1,8 +1,6 @@ import { Injectable, Scope } from '@nestjs/common'; import type * as NestJSMicroservices from '@nestjs/microservices'; import { HealthIndicator, type HealthIndicatorResult } from '../'; -import { TimeoutError } from '../../errors'; -import { HealthCheckError } from '../../health-check/health-check.error'; import { checkPackages, promiseTimeout, @@ -10,6 +8,7 @@ import { type PropType, isError, } from '../../utils'; +import { HealthIndicatorService } from '../health-indicator.service'; // Since @nestjs/microservices is lazily loaded we are not able to use // its types. It would end up in the d.ts file if we would use the types. @@ -45,10 +44,8 @@ export type MicroserviceHealthIndicatorOptions< @Injectable({ scope: Scope.TRANSIENT }) export class MicroserviceHealthIndicator extends HealthIndicator { private nestJsMicroservices!: typeof NestJSMicroservices; - /** - * Initializes the health indicator - */ - constructor() { + + constructor(private readonly healthIndicatorService: HealthIndicatorService) { super(); this.checkDependantPackages(); } @@ -76,34 +73,6 @@ export class MicroserviceHealthIndicator extends HealthIndicator { return await checkConnection(); } - /** - * Prepares and throw a HealthCheckError - * @param key The key which will be used for the result object - * @param error The thrown error - * @param timeout The timeout in ms - * - * @throws {HealthCheckError} - */ - private generateError(key: string, error: Error, timeout: number) { - if (!error) { - return; - } - if (error instanceof PromiseTimeoutError) { - throw new TimeoutError( - timeout, - this.getStatus(key, false, { - message: `timeout of ${timeout}ms exceeded`, - }), - ); - } - throw new HealthCheckError( - error.message, - this.getStatus(key, false, { - message: error.message, - }), - ); - } - /** * Checks if the given microservice is up * @param key The key which will be used for the result object @@ -117,11 +86,14 @@ export class MicroserviceHealthIndicator extends HealthIndicator { * options: { host: 'localhost', port: 3001 }, * }) */ - async pingCheck( - key: string, + async pingCheck< + MicroserviceClientOptions extends MicroserviceOptionsLike, + Key extends string, + >( + key: Key, options: MicroserviceHealthIndicatorOptions, - ): Promise { - let isHealthy = false; + ): Promise> { + const check = this.healthIndicatorService.check(key); const timeout = options.timeout || 1000; if (options.transport === this.nestJsMicroservices.Transport.KAFKA) { @@ -135,20 +107,17 @@ export class MicroserviceHealthIndicator extends HealthIndicator { try { await promiseTimeout(timeout, this.pingMicroservice(options)); - isHealthy = true; } catch (err) { + if (err instanceof PromiseTimeoutError) { + return check.down(`timeout of ${timeout}ms exceeded`); + } if (isError(err)) { - this.generateError(key, err, timeout); + return check.down(err.message); } - const errorMsg = `${key} is not available`; - - throw new HealthCheckError( - errorMsg, - this.getStatus(key, false, { message: errorMsg }), - ); + return check.down(); } - return this.getStatus(key, isHealthy); + return check.up(); } }