// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
// SPDX-License-Identifier: Apache-2.0
import { MEDPLUM_VERSION } from '@medplum/core';
import type { Request, Response } from 'express';
import os from 'node:os';
import type { Pool } from 'pg';
import { DatabaseMode, getDatabasePool } from './database';
import type { RecordMetricOptions } from './otel/otel';
import { setGauge } from './otel/otel';
import { getRedis } from './redis';
const hostname = os.hostname();
const BASE_METRIC_OPTIONS = { attributes: { hostname } } satisfies RecordMetricOptions;
const METRIC_IN_SECS_OPTIONS = { ...BASE_METRIC_OPTIONS, options: { unit: 's' } } satisfies RecordMetricOptions;
export async function healthcheckHandler(_req: Request, res: Response): Promise<void> {
const writerPool = getDatabasePool(DatabaseMode.WRITER);
const readerPool = getDatabasePool(DatabaseMode.READER);
let startTime = Date.now();
const postgresWriterOk = await testPostgres(writerPool);
const writerRoundtripMs = Date.now() - startTime;
setGauge('medplum.db.healthcheckRTT', writerRoundtripMs / 1000, {
...METRIC_IN_SECS_OPTIONS,
attributes: { ...METRIC_IN_SECS_OPTIONS.attributes, dbInstanceType: 'writer' },
});
if (writerPool !== readerPool) {
startTime = Date.now();
await testPostgres(readerPool);
const readerRoundtripMs = Date.now() - startTime;
setGauge('medplum.db.healthcheckRTT', readerRoundtripMs / 1000, {
...METRIC_IN_SECS_OPTIONS,
attributes: { ...METRIC_IN_SECS_OPTIONS.attributes, dbInstanceType: 'reader' },
});
}
startTime = Date.now();
const redisOk = await testRedis();
const redisRoundtripMs = Date.now() - startTime;
setGauge('medplum.redis.healthcheckRTT', redisRoundtripMs / 1000, METRIC_IN_SECS_OPTIONS);
res.json({
ok: true,
version: MEDPLUM_VERSION,
platform: process.platform,
runtime: process.version,
postgres: postgresWriterOk,
redis: redisOk,
});
}
async function testPostgres(pool: Pool): Promise<boolean> {
return (await pool.query(`SELECT 1 AS "status"`)).rows[0].status === 1;
}
async function testRedis(): Promise<boolean> {
return (await getRedis().ping()) === 'PONG';
}