Browse Source

feat(core): Add health check for DB & worker

Relates to #289
Michael Bromley 5 years ago
parent
commit
1b84ea7652

+ 1 - 0
packages/core/package.json

@@ -43,6 +43,7 @@
     "@nestjs/graphql": "7.0.14",
     "@nestjs/graphql": "7.0.14",
     "@nestjs/microservices": "7.0.5",
     "@nestjs/microservices": "7.0.5",
     "@nestjs/platform-express": "7.0.5",
     "@nestjs/platform-express": "7.0.5",
+    "@nestjs/terminus": "7.0.1",
     "@nestjs/testing": "7.0.5",
     "@nestjs/testing": "7.0.5",
     "@nestjs/typeorm": "7.0.0",
     "@nestjs/typeorm": "7.0.0",
     "@types/fs-extra": "^8.0.1",
     "@types/fs-extra": "^8.0.1",

+ 6 - 1
packages/core/src/api/middleware/exception-logger.filter.ts

@@ -1,6 +1,7 @@
 import { ArgumentsHost, ExceptionFilter, HttpException } from '@nestjs/common';
 import { ArgumentsHost, ExceptionFilter, HttpException } from '@nestjs/common';
 
 
 import { Logger, LogLevel } from '../../config';
 import { Logger, LogLevel } from '../../config';
+import { HEALTH_CHECK_ROUTE } from '../../health-check/constants';
 import { I18nError } from '../../i18n/i18n-error';
 import { I18nError } from '../../i18n/i18n-error';
 import { parseContext } from '../common/parse-context';
 import { parseContext } from '../common/parse-context';
 
 
@@ -46,7 +47,11 @@ export class ExceptionLoggerFilter implements ExceptionFilter {
             Logger.error(message, undefined, stack);
             Logger.error(message, undefined, stack);
         }
         }
 
 
-        if (!isGraphQL) {
+        if (exception instanceof HttpException && req.path.startsWith('/' + HEALTH_CHECK_ROUTE)) {
+            // Special case for the health check error, since we want to display the response only
+            // so it matches the format of the success case.
+            res.status(exception.getStatus()).send(exception.getResponse());
+        } else if (!isGraphQL) {
             // In the GraphQL context, we can let the error pass
             // In the GraphQL context, we can let the error pass
             // through to the next layer, where Apollo Server will
             // through to the next layer, where Apollo Server will
             // return a response for us. But when in the REST context,
             // return a response for us. But when in the REST context,

+ 9 - 1
packages/core/src/app.module.ts

@@ -17,13 +17,21 @@ import { InjectableStrategy } from './common/types/injectable-strategy';
 import { ConfigModule } from './config/config.module';
 import { ConfigModule } from './config/config.module';
 import { ConfigService } from './config/config.service';
 import { ConfigService } from './config/config.service';
 import { Logger } from './config/logger/vendure-logger';
 import { Logger } from './config/logger/vendure-logger';
+import { HealthCheckModule } from './health-check/health-check.module';
 import { I18nModule } from './i18n/i18n.module';
 import { I18nModule } from './i18n/i18n.module';
 import { I18nService } from './i18n/i18n.service';
 import { I18nService } from './i18n/i18n.service';
 import { PluginModule } from './plugin/plugin.module';
 import { PluginModule } from './plugin/plugin.module';
 import { ProcessContextModule } from './process-context/process-context.module';
 import { ProcessContextModule } from './process-context/process-context.module';
 
 
 @Module({
 @Module({
-    imports: [ConfigModule, I18nModule, ApiModule, PluginModule.forRoot(), ProcessContextModule.forRoot()],
+    imports: [
+        ConfigModule,
+        I18nModule,
+        ApiModule,
+        PluginModule.forRoot(),
+        ProcessContextModule.forRoot(),
+        HealthCheckModule,
+    ],
 })
 })
 export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicationShutdown {
 export class AppModule implements NestModule, OnApplicationBootstrap, OnApplicationShutdown {
     constructor(
     constructor(

+ 1 - 0
packages/core/src/health-check/constants.ts

@@ -0,0 +1 @@
+export const HEALTH_CHECK_ROUTE = 'health';

+ 29 - 0
packages/core/src/health-check/health-check-registry.service.ts

@@ -0,0 +1,29 @@
+import { HealthIndicatorFunction } from '@nestjs/terminus';
+
+/**
+ * @description
+ * This service is used to register health indicator functions to be included in the
+ * health check. It wraps the [Nestjs Terminus module](https://docs.nestjs.com/recipes/terminus),
+ * so see those docs for information on creating custom health checks.
+ *
+ * Plugins which rely on external services (web services, databases etc.) can make use of this
+ * service to add a check for that dependency to the Vendure health check.
+ *
+ * @docsCategory health-check
+ */
+export class HealthCheckRegistryService {
+    get healthIndicatorFunctions(): HealthIndicatorFunction[] {
+        return this._healthIndicatorFunctions;
+    }
+    private _healthIndicatorFunctions: HealthIndicatorFunction[] = [];
+
+    /**
+     * @description
+     * Registers one or more `HealthIndicatorFunctions` (see [Nestjs docs](https://docs.nestjs.com/recipes/terminus#custom-health-indicator))
+     * to be added to the health check endpoint.
+     */
+    registerIndicatorFunction(fn: HealthIndicatorFunction | HealthIndicatorFunction[]) {
+        const fnArray = Array.isArray(fn) ? fn : [fn];
+        this._healthIndicatorFunctions.push(...fnArray);
+    }
+}

+ 20 - 0
packages/core/src/health-check/health-check.controller.ts

@@ -0,0 +1,20 @@
+import { Controller, Get } from '@nestjs/common';
+import { DNSHealthIndicator, HealthCheck, HealthCheckService } from '@nestjs/terminus';
+
+import { HEALTH_CHECK_ROUTE } from './constants';
+import { HealthCheckRegistryService } from './health-check-registry.service';
+
+@Controller(HEALTH_CHECK_ROUTE)
+export class HealthController {
+    constructor(
+        private health: HealthCheckService,
+        private dns: DNSHealthIndicator,
+        private healthCheckRegistryService: HealthCheckRegistryService,
+    ) {}
+
+    @Get()
+    @HealthCheck()
+    check() {
+        return this.health.check(this.healthCheckRegistryService.healthIndicatorFunctions);
+    }
+}

+ 33 - 0
packages/core/src/health-check/health-check.module.ts

@@ -0,0 +1,33 @@
+import { Module } from '@nestjs/common';
+import { MicroserviceHealthIndicator, TerminusModule, TypeOrmHealthIndicator } from '@nestjs/terminus';
+
+import { ConfigModule } from '../config/config.module';
+import { ConfigService } from '../config/config.service';
+
+import { HealthCheckRegistryService } from './health-check-registry.service';
+import { HealthController } from './health-check.controller';
+
+@Module({
+    imports: [TerminusModule, ConfigModule],
+    controllers: [HealthController],
+    providers: [HealthCheckRegistryService],
+    exports: [HealthCheckRegistryService],
+})
+export class HealthCheckModule {
+    constructor(
+        private configService: ConfigService,
+        private healthCheckRegistryService: HealthCheckRegistryService,
+        private typeOrm: TypeOrmHealthIndicator,
+        private microservice: MicroserviceHealthIndicator,
+    ) {
+        // Register the default health checks for database and worker
+        this.healthCheckRegistryService.registerIndicatorFunction([
+            () => this.typeOrm.pingCheck('database'),
+            () =>
+                this.microservice.pingCheck('worker', {
+                    transport: this.configService.workerOptions.transport,
+                    options: this.configService.workerOptions.options,
+                }),
+        ]);
+    }
+}

+ 2 - 0
packages/core/src/health-check/index.ts

@@ -0,0 +1,2 @@
+export * from './constants';
+export * from './health-check-registry.service';

+ 1 - 0
packages/core/src/index.ts

@@ -4,6 +4,7 @@ export * from './api/index';
 export * from './common/index';
 export * from './common/index';
 export * from './config/index';
 export * from './config/index';
 export * from './event-bus/index';
 export * from './event-bus/index';
+export * from './health-check/index';
 export * from './job-queue/index';
 export * from './job-queue/index';
 export * from './plugin/index';
 export * from './plugin/index';
 export * from './process-context/index';
 export * from './process-context/index';

+ 18 - 2
packages/core/src/plugin/plugin-common.module.ts

@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
 
 
 import { ConfigModule } from '../config/config.module';
 import { ConfigModule } from '../config/config.module';
 import { EventBusModule } from '../event-bus/event-bus.module';
 import { EventBusModule } from '../event-bus/event-bus.module';
+import { HealthCheckModule } from '../health-check/health-check.module';
 import { JobQueueModule } from '../job-queue/job-queue.module';
 import { JobQueueModule } from '../job-queue/job-queue.module';
 import { ServiceModule } from '../service/service.module';
 import { ServiceModule } from '../service/service.module';
 import { WorkerServiceModule } from '../worker/worker-service.module';
 import { WorkerServiceModule } from '../worker/worker-service.module';
@@ -19,11 +20,26 @@ import { WorkerServiceModule } from '../worker/worker-service.module';
  * * `ConfigModule`, allowing the injection of the ConfigService.
  * * `ConfigModule`, allowing the injection of the ConfigService.
  * * `WorkerServiceModule`, allowing the injection of the {@link WorkerService}.
  * * `WorkerServiceModule`, allowing the injection of the {@link WorkerService}.
  * * `JobQueueModule`, allowing the injection of the {@link JobQueueService}.
  * * `JobQueueModule`, allowing the injection of the {@link JobQueueService}.
+ * * `HealthCheckModule`, allowing the injection of the {@link HealthCheckRegistryService}.
  *
  *
  * @docsCategory plugin
  * @docsCategory plugin
  */
  */
 @Module({
 @Module({
-    imports: [EventBusModule, ConfigModule, ServiceModule.forPlugin(), WorkerServiceModule, JobQueueModule],
-    exports: [EventBusModule, ConfigModule, ServiceModule.forPlugin(), WorkerServiceModule, JobQueueModule],
+    imports: [
+        EventBusModule,
+        ConfigModule,
+        ServiceModule.forPlugin(),
+        WorkerServiceModule,
+        JobQueueModule,
+        HealthCheckModule,
+    ],
+    exports: [
+        EventBusModule,
+        ConfigModule,
+        ServiceModule.forPlugin(),
+        WorkerServiceModule,
+        JobQueueModule,
+        HealthCheckModule,
+    ],
 })
 })
 export class PluginCommonModule {}
 export class PluginCommonModule {}

+ 18 - 0
yarn.lock

@@ -2769,6 +2769,14 @@
     multer "1.4.2"
     multer "1.4.2"
     tslib "1.11.1"
     tslib "1.11.1"
 
 
+"@nestjs/terminus@^7.0.1":
+  version "7.0.1"
+  resolved "https://registry.npmjs.org/@nestjs/terminus/-/terminus-7.0.1.tgz#7d748f8c18973d60023a8ab16760d0adab145b8b"
+  integrity sha512-OKg1QQDb+whHJM3Xt+3RRUPiyZSyD0qLacfldK0TXcFpKyexA0yyY3GKeaBNApf01FEzJgkK3ARCUoELnAfXDA==
+  dependencies:
+    check-disk-space "2.1.0"
+    deprecate "^1.1.1"
+
 "@nestjs/testing@7.0.5":
 "@nestjs/testing@7.0.5":
   version "7.0.5"
   version "7.0.5"
   resolved "https://registry.npmjs.org/@nestjs/testing/-/testing-7.0.5.tgz#b6d6ee4de31669e87e231d18b73f2f76e358719c"
   resolved "https://registry.npmjs.org/@nestjs/testing/-/testing-7.0.5.tgz#b6d6ee4de31669e87e231d18b73f2f76e358719c"
@@ -5814,6 +5822,11 @@ chardet@^0.7.0:
   resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
   integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
 
 
+check-disk-space@2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmjs.org/check-disk-space/-/check-disk-space-2.1.0.tgz#2e77fe62f30d9676dc37a524ea2008f40c780295"
+  integrity sha512-f0nx9oJF/AVF8nhSYlF1EBvMNnO+CXyLwKhPvN1943iOMI9TWhQigLZm80jAf0wzQhwKkzA8XXjyvuVUeGGcVQ==
+
 cheerio@^0.22.0:
 cheerio@^0.22.0:
   version "0.22.0"
   version "0.22.0"
   resolved "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
   resolved "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
@@ -7305,6 +7318,11 @@ dependency-graph@^0.7.2:
   resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz#91db9de6eb72699209d88aea4c1fd5221cac1c49"
   resolved "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz#91db9de6eb72699209d88aea4c1fd5221cac1c49"
   integrity sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==
   integrity sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==
 
 
+deprecate@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.npmjs.org/deprecate/-/deprecate-1.1.1.tgz#4632e981fc815eeaf00be945a40359c0f8bf9913"
+  integrity sha512-ZGDXefq1xknT292LnorMY5s8UVU08/WKdzDZCUT6t9JzsiMSP4uzUhgpqugffNVcT5WC6wMBiSQ+LFjlv3v7iQ==
+
 deprecated-decorator@^0.1.6:
 deprecated-decorator@^0.1.6:
   version "0.1.6"
   version "0.1.6"
   resolved "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37"
   resolved "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37"