UNPKG

@qrvey/health-checker

Version:

![install size](https://packagephobia.com/badge?p=@qrvey/health-checker) ![coverage](https://img.shields.io/badge/unit_test_coverage-87%25-brightgreen)

480 lines (380 loc) 13.8 kB
# @qrvey/health-checker ![install size](https://packagephobia.com/badge?p=@qrvey/health-checker) ![coverage](https://img.shields.io/badge/unit_test_coverage-87%25-brightgreen) An health check library for validating the availability of core service dependencies like Redis, PostgreSQL, RabbitMQ, and Elasticsearch. ## Installation ```bash npm install @qrvey/health-checker ``` Or with yarn: ```bash yarn add @qrvey/health-checker ``` ## Supported Health Check Types | Service | Dependency Key | | ------------- | -------------- | | PostgreSQL | `database` | | Redis | `cache` | | RabbitMQ | `eventBroker` | | Elasticsearch | `warehouse` | ## Health Status Values | Status | Description | | -------- | ----------- | | `OK` | The dependency is healthy and operating normally. | | `FAILED` | The dependency is unhealthy. This is the only status that causes the overall health check to report `FAILED`. | | `VOID` | The dependency is not part of the infrastructure for the current product license and is skipped. `VOID` dependencies are ignored when computing the overall status. | The overall status is determined solely by the presence of `FAILED` dependencies. If all dependencies are `OK` or `VOID`, the overall status is `OK`. A `VOID` dependency never causes the overall status to become `FAILED`. ## Usage ```js const { HealthCheckService } = require('@qrvey/health-checker'); HealthCheckService.check([ 'cache', 'database', 'eventBroker', 'warehouse', ]).then((result) => { console.log(result); /* { status: 'OK', details: { cache: 'OK', database: 'OK', eventBroker: 'OK', warehouse: 'OK' } } */ }); ``` You can also check specific dependencies only: ```js HealthCheckService.check(['cache']).then((result) => { console.log(result); /* { status: 'OK', details: { cache: 'OK' } } */ }); ``` ## Usage with Fastify You can expose the health check as a simple route in your Fastify app. ### Basic Example: `health.routes.js` ```js const { HealthCheckService } = require('@qrvey/health-checker'); const Fastify = require('fastify'); async function healthRoutes(fastify, _options) { fastify.get('/health', async (_request, reply) => { const dependencies = ['database', 'eventBroker', 'warehouse']; const result = await HealthCheckService.check(dependencies); const httpStatus = result.status === 'FAILED' ? 503 : 200; return reply.code(httpStatus).send(result); }); } const app = Fastify({ logger: true }); app.register(healthRoutes); app.listen({ port: 3000 }); ``` ### Basic Example Output ```json GET /health { "status": "OK", "details": { "database": "OK", "eventBroker": "OK", "warehouse": "OK" } } ``` ### Validating Queue Subscriptions (Optional) If you want to explicitly validate that your service is subscribed to one or more RabbitMQ queues, you can pass an additional params object to the check method. ````js fastify.get('/health', async (_request, reply) => { const dependencies = ['eventBroker']; const params = { eventBroker: { queues: ['queue_name_1', 'queue_name_2'], // these must match the configured subscriptions }, } const result = await HealthCheckService.check(dependencies, params); const httpStatus = result.status === "FAILED" ? 503 : 200; return reply.code(httpStatus).send(result); }); #### Warehouse Health Check Failure Conditions The warehouse checker marks the cluster as `FAILED` if any of these conditions are met: - **CPU Usage** ≥ configured threshold (default: 85%) - **JVM Heap Usage** ≥ configured threshold (default: 90%) - **Disk Usage** ≥ configured threshold (default: 85%) - **Circuit Breakers** near limit (ratio > 80%) - **Thread Pool Rejections** detected - **Cluster Status** is red (customizable) - **Unassigned Shards** > 0 (customizable) #### Environment-Specific Configurations ```js const params = { warehouse: { thresholds: { cpuUsagePercent: 90, jvmHeapPercent: 95, circuitBreakerPercent: 90 }, customClusterStatusCheck: (status) => status !== 'red', skipRules: { skipUnassignedShardsCheck: true }, } }; ```` ### Sample output ```json GET /health { "status": "OK", "details": { "eventBroker": "OK" } } ``` ### Warehouse Health Check Sample Outputs #### Basic Success Response ```json GET /health { "status": "OK", "details": { "warehouse": "OK" } } ``` #### Detailed Success Response (with returnClusterStats: true) ```json GET /health { "status": "OK", "durationMs": 460, "metadata": { "checkTimestamp": "2025-10-01T15:14:21.787Z", "clusterStats": { "clusterName": "elasticsearch", "status": "green", "nodes": { "total": 2, "successful": 2, "failed": 0 }, "cpuUsagePercent": 57.5, "jvmHeapUsagePercent": 47.5, "diskUsagePercent": 21.518163805507996, "activePrimaryShards": 776, "activeShards": 1552, "relocatingShards": 0, "initializingShards": 0, "unassignedShards": 0, "circuitBreakersException": false, "threadPoolRejections": false } } } ``` #### Failure Response ```json GET /health { "status": "FAILED", "details": { "warehouse": { "status": "FAILED", "metadata": { "clusterStats": { "clusterName": "my-elasticsearch-cluster", "status": "red", "cpuUsagePercent": 88, "jvmHeapUsagePercent": 95, "diskUsagePercent": 90, "circuitBreakersTripped": true, "threadPoolRejections": false, "unassignedShards": 15 }, "checkTimestamp": "2025-10-01T10:30:00.000Z" } } } } ``` #### VOID Response (dependency not in product license) When a dependency is not part of the current product license infrastructure (e.g. `warehouse` on an Ultra license), the check is skipped and returns `VOID`. This does not affect the overall status. ```json GET /health { "status": "OK", "details": { "cache": { "status": "OK", "durationMs": 12, "metadata": {} }, "warehouse": { "status": "VOID", "durationMs": 1, "metadata": { "checkTimestamp": "2025-10-01T10:30:00.000Z", "errorMessage": "Health check skipped due to configuration" } } } } ``` ## Other Configuration ### Multi-Environment Setup ```js const getHealthParams = (env) => { const baseParams = { eventBroker: { queues: process.env.REQUIRED_QUEUES?.split(',') || [], }, }; if (env === 'development') { return { ...baseParams, warehouse: { thresholds: { cpuUsagePercent: 90, jvmHeapPercent: 95, circuitBreakerPercent: 90, }, skipRules: { skipUnassignedShardsCheck: true }, }, }; } if (env === 'production') { return { ...baseParams, warehouse: { thresholds: { cpuUsagePercent: 70, jvmHeapPercent: 85, circuitBreakerPercent: 75, }, returnClusterStats: true, }, }; } return baseParams; }; fastify.get('/health', async (_request, reply) => { const dependencies = ['database', 'eventBroker', 'warehouse']; const params = getHealthParams(process.env.NODE_ENV); const result = await HealthCheckService.check(dependencies, params); const httpStatus = result.status === 'FAILED' ? 503 : 200; return reply.code(httpStatus).send(result); }); ``` ## API Reference ### HealthCheckService The main service for performing health checks. #### `HealthCheckService.check(dependencies, params?)` - **dependencies**: `string[]` - Array of dependency keys to check - **params**: `object` - Optional parameters for specific health checkers - **Returns**: `Promise<HealthCheckResult>` ```typescript interface HealthCheckResult { status: 'OK' | 'FAILED' | 'VOID'; details: Record<string, any>; } ``` ### RuntimeHealthMiddleware Middleware helper that returns `429` when runtime health is `FAILED`. ```js const { RuntimeHealthMiddleware } = require('@qrvey/health-checker'); // Express app.use(RuntimeHealthMiddleware.express.start()); // Fastify fastify.addHook('preHandler', RuntimeHealthMiddleware.fastify.start()); ``` ### Individual Health Checkers You can also import and use individual health checkers directly: ```js const { RabbitMQHealthChecker, WarehouseHealthChecker, DatabaseHealthChecker, CacheHealthChecker, } = require('@qrvey/health-checker'); // Direct usage const warehouseResult = await WarehouseHealthChecker.check({ thresholds: { cpuUsagePercent: 70 }, }); const brokerResult = await RabbitMQHealthChecker.check({ queues: ['important-queue'], }); ``` ### Environment Variables Reference | Variable | Service | Description | Default | | ---------------------------------------- | ------------- | ---------------------------------------------------- | ----------- | | `ELASTICSEARCH_HOST` | warehouse | Elasticsearch/OpenSearch host | `localhost` | | `ELASTICSEARCH_PORT` | warehouse | Elasticsearch port | `9200` | | `ELASTICSEARCH_PROTOCOL` | warehouse | HTTP protocol | `http` | | `ELASTICSEARCH_AUTH_USER` | warehouse | Basic Auth username (self-managed ES) | - | | `ELASTICSEARCH_AUTH_PASSWORD` | warehouse | Basic Auth password (self-managed ES) | - | | `ELASTICSEARCH_TIMEOUT` | warehouse | Request timeout (ms) | `5000` | | `RABBITMQ_HTTP_HOST` | eventBroker | RabbitMQ management API host | - | | `RABBITMQ_USER` | eventBroker | RabbitMQ username | - | | `RABBITMQ_PASSWORD` | eventBroker | RabbitMQ password | - | | `HOSTNAME` | eventBroker | Consumer tag hostname | - | | `RUNTIME_HEALTH_WINDOW_MS` | runtimeHealth | Runtime measurement window in ms | `250` | | `RUNTIME_HEALTH_CPU_PERCENT` | runtimeHealth | Runtime CPU threshold percent | `90` | | `RUNTIME_HEALTH_HEAP_PERCENT` | runtimeHealth | Runtime heap threshold percent | `90` | | `RUNTIME_HEALTH_EVENT_LOOP_DELAY_MS` | runtimeHealth | Runtime event loop delay threshold in ms | `200` | | `RUNTIME_HEALTH_ENABLE_CPU` | runtimeHealth | Enable/disable CPU metric (`false` disables) | `true` | | `RUNTIME_HEALTH_ENABLE_HEAP` | runtimeHealth | Enable/disable heap metric (`false` disables) | `true` | | `RUNTIME_HEALTH_ENABLE_EVENT_LOOP_DELAY` | runtimeHealth | Enable/disable event loop metric (`false` disables) | `true` | | `RUNTIME_HEALTH_LOG_GET` | runtimeHealth | Log payload returned by `RuntimeHealthService.get()` | `false` | | `RUNTIME_HEALTH_CACHE_TTL_SECONDS` | runtimeHealth | Runtime middleware cache TTL in seconds | `3` | | `QRVEY_PRODUCT_TYPE` | warehouse | Product license type. Only `A3` (Ultra) includes warehouse infrastructure; any other value causes the warehouse health check to return `VOID` | - | ## Development ### Running Tests #### Unit Tests ```bash yarn workspace @qrvey/health-checker test ``` #### Integration Tests **Prerequisites:** - Access to a running Elasticsearch/OpenSearch instance (local or remote) - Running Redis, RabbitMQ, and PostgreSQL services **Setup:** 1. Configure `integration.test.env` with your Elasticsearch credentials: ```bash # Edit integration.test.env ELASTICSEARCH_HOST=https://your-elasticsearch-domain.com ELASTICSEARCH_PORT=443 ELASTICSEARCH_AUTH_USER=your_username ELASTICSEARCH_AUTH_PASSWORD=your_password # For AWS OpenSearch (uses AWS credentials automatically) # ELASTICSEARCH_HOST=https://search-domain.us-west-2.es.amazonaws.com # ELASTICSEARCH_PORT=443 # AWS_REGION=us-west-2 ``` 2. Start local services (Redis, RabbitMQ, PostgreSQL): ```bash docker-compose -f integration.test.docker-compose.yml --env-file integration.test.env up -d ``` 3. Run integration tests (automatically loads `integration.test.env`): ```bash yarn workspace @qrvey/health-checker test:integration ``` 4. Stop local services: ```bash docker-compose -f integration.test.docker-compose.yml down -v ``` ### Services in Integration Tests | Service | Location | Credentials | | ------------- | --------------------- | ------------------- | | Elasticsearch | Configure in `.env` | Your credentials | | RabbitMQ | localhost:5672, 15672 | guest:guest | | Redis | localhost:6379 | - | | PostgreSQL | localhost:5432 | test_user:test_pass | ## License MIT