Terminus integration cung cấp cho bạn health check kiểu readiness/liveness. Healthcheck là yếu tố then chốt khi làm việc với các hệ thống backend phức tạp. Nói ngắn gọn, một health check trong lĩnh vực phát triển web thường bao gồm một địa chỉ đặc biệt, ví dụ https://my-website.com/health/readiness.
Một service hoặc component nào đó trong hạ tầng của bạn (ví dụ: Kubernetes) liên tục kiểm tra địa chỉ này. Dựa vào HTTP status code trả về từ một GET request tới địa chỉ đó, service sẽ hành động khi nhận được response “unhealthy” (không lành mạnh).
Vì định nghĩa “healthy” hoặc “unhealthy” thay đổi theo loại service bạn cung cấp, integration Terminus hỗ trợ bạn với
một tập health indicator.
Ví dụ, nếu web server của bạn dùng MongoDB để lưu dữ liệu, việc biết MongoDB còn hoạt động hay không sẽ là thông tin quan trọng.
Trong trường hợp đó, bạn có thể dùng MongooseHealthIndicator. Nếu được cấu hình đúng - sẽ nói rõ hơn sau - địa chỉ health check của bạn sẽ trả về
một HTTP status code healthy hoặc unhealthy, tùy vào việc MongoDB có đang chạy hay không.
Ghi chú người dịch Nếu bạn từng viết health check kiểu Express (một route /health tự query SELECT 1 rồi res.sendStatus(200)), Terminus chỉ là lớp trừu tượng hóa việc đó: nó chuẩn hóa format response JSON, gom nhiều indicator lại thành một kết quả duy nhất, và tích hợp sẵn với lifecycle shutdown của Nest. Bản chất vẫn là một endpoint HTTP bình thường, không có gì “ma thuật”.
Getting started
Để bắt đầu với @nestjs/terminus, ta cần cài dependency cần thiết.
$ npm install --save @nestjs/terminus
Setting up a Healthcheck (Thiết lập một Healthcheck)
Một health check đại diện cho một bản tổng hợp các health indicator. Một health indicator thực hiện việc kiểm tra một service, xem nó đang ở trạng thái healthy hay unhealthy. Một health check là dương tính (positive/lành mạnh) nếu tất cả health indicator được gán đều đang hoạt động (up and running). Vì rất nhiều ứng dụng sẽ cần các health indicator tương tự nhau, @nestjs/terminus cung cấp sẵn một tập indicator được định nghĩa trước, chẳng hạn như:
HttpHealthIndicatorTypeOrmHealthIndicatorMongooseHealthIndicatorSequelizeHealthIndicatorMikroOrmHealthIndicatorPrismaHealthIndicatorMicroserviceHealthIndicatorGRPCHealthIndicatorMemoryHealthIndicatorDiskHealthIndicator
Để bắt đầu với health check đầu tiên, hãy tạo HealthModule và import TerminusModule vào trong mảng imports của nó.
Gợi ý Để tạo module bằng Nest CLI, chỉ cần thực thi lệnh $ nest g module health.
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
@Module({
imports: [TerminusModule]
})
export class HealthModule {}
Healthcheck của ta có thể được thực thi thông qua một controller, có thể được thiết lập dễ dàng bằng Nest CLI.
$ nest g controller health
Info Rất khuyến khích bật shutdown hooks trong ứng dụng của bạn. Terminus integration sử dụng lifecycle event này nếu được bật. Đọc thêm về shutdown hooks tại đây.
HTTP Healthcheck
Sau khi đã cài @nestjs/terminus, import TerminusModule và tạo một controller mới, ta đã sẵn sàng để tạo một health check.
HTTPHealthIndicator yêu cầu package @nestjs/axios, vậy nên đảm bảo bạn đã cài nó:
$ npm i --save @nestjs/axios axios
Bây giờ ta có thể thiết lập HealthController:
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
]);
}
}
import { Controller, Dependencies, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus';
@Controller('health')
@Dependencies(HealthCheckService, HttpHealthIndicator)
export class HealthController {
constructor(
private health,
private http,
) { }
@Get()
@HealthCheck()
healthCheck() {
return this.health.check([
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
])
}
}
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HttpModule } from '@nestjs/axios';
import { HealthController } from './health.controller.js';
@Module({
imports: [TerminusModule, HttpModule],
controllers: [HealthController],
})
export class HealthModule {}
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { HttpModule } from '@nestjs/axios';
import { HealthController } from './health.controller.js';
@Module({
imports: [TerminusModule, HttpModule],
controllers: [HealthController],
})
export class HealthModule {}
Health check của ta giờ sẽ gửi một GET-request tới địa chỉ https://docs.nestjs.com. Nếu
ta nhận được response healthy từ địa chỉ đó, route tại http://localhost:3000/health sẽ trả về
object sau với status code 200.
{
"status": "ok",
"info": {
"nestjs-docs": {
"status": "up"
}
},
"error": {},
"details": {
"nestjs-docs": {
"status": "up"
}
}
}
Interface của response object này có thể truy cập từ package @nestjs/terminus với interface HealthCheckResult.
status |
Nếu bất kỳ health indicator nào lỗi, status sẽ là 'error'. Nếu không có indicator nào lỗi nhưng có ít nhất một indicator ở trạng thái 'degraded', status sẽ là 'degraded'. Nếu ứng dụng NestJS đang shutting down nhưng vẫn nhận HTTP request, health check sẽ có status 'shutting_down'. |
'error' | 'ok' | 'degraded' | 'shutting_down' |
info |
Object chứa thông tin của mỗi health indicator có status 'up' hoặc 'degraded', hay nói cách khác là “vẫn đang hoạt động”. |
object |
error |
Object chứa thông tin của mỗi health indicator có status 'down', hay nói cách khác là “unhealthy”. |
object |
details |
Object chứa toàn bộ thông tin của mỗi health indicator | object |
Check for specific HTTP response codes (kiểm tra mã response HTTP cụ thể)
Trong một số trường hợp, bạn có thể muốn kiểm tra theo tiêu chí cụ thể và validate response. Ví dụ, giả sử
https://my-external-service.com trả về response code 204. Với HttpHealthIndicator.responseCheck, bạn có thể
kiểm tra chính xác response code đó và xác định mọi code khác là unhealthy.
Trong trường hợp bất kỳ response code nào khác 204 được trả về, ví dụ sau sẽ là unhealthy. Tham số thứ ba
yêu cầu bạn cung cấp một hàm (sync hoặc async) trả về boolean cho biết response được xem là
healthy (true) hay unhealthy (false).
// Trong class `HealthController`
@Get()
@HealthCheck()
check() {
return this.health.check([
() =>
this.http.responseCheck(
'my-external-service',
'https://my-external-service.com',
(res) => res.status === 204,
),
]);
}
TypeOrm health indicator
Terminus cho phép thêm database check vào health check của bạn. Để bắt đầu với health indicator này, bạn nên xem qua chương Database và đảm bảo kết nối database trong ứng dụng của bạn đã được thiết lập.
Gợi ý Bên dưới, TypeOrmHealthIndicator chỉ đơn giản thực thi lệnh SQL SELECT 1, thường được dùng để kiểm tra database còn sống hay không. Nếu bạn dùng database Oracle, nó sẽ dùng SELECT 1 FROM DUAL.
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database').withTimeout(1000),
]);
}
}
@Controller('health')
@Dependencies(HealthCheckService, TypeOrmHealthIndicator)
export class HealthController {
constructor(
private health,
private db,
) { }
@Get()
@HealthCheck()
healthCheck() {
return this.health.check([
() => this.db.pingCheck('database').withTimeout(1000),
])
}
}
Nếu database của bạn có thể kết nối được, bạn sẽ thấy kết quả JSON sau khi gửi GET request tới http://localhost:3000/health:
{
"status": "ok",
"info": {
"database": {
"status": "up",
"responseTime": 12
}
},
"error": {},
"details": {
"database": {
"status": "up",
"responseTime": 12
}
}
}
Trong trường hợp app của bạn dùng nhiều database, bạn cần inject từng
connection vào HealthController. Sau đó, bạn chỉ cần truyền tham chiếu connection vào TypeOrmHealthIndicator.
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
@InjectConnection('albumsConnection')
private albumsConnection: Connection,
@InjectConnection()
private defaultConnection: Connection,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('albums-database', { connection: this.albumsConnection }).withTimeout(1000),
() => this.db.pingCheck('database', { connection: this.defaultConnection }).withTimeout(1000),
]);
}
}
Disk health indicator
Với DiskHealthIndicator, ta có thể kiểm tra lượng dung lượng lưu trữ đang được sử dụng. Để bắt đầu, đảm bảo inject DiskHealthIndicator
vào HealthController của bạn. Ví dụ sau kiểm tra dung lượng đã dùng của path / (hoặc trên Windows bạn có thể dùng C:\\).
Nếu vượt quá 50% tổng dung lượng lưu trữ, health check sẽ phản hồi với trạng thái unhealthy.
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly disk: DiskHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.disk.checkStorage('storage', { path: '/', thresholdPercent: 0.5 }),
]);
}
}
@Controller('health')
@Dependencies(HealthCheckService, DiskHealthIndicator)
export class HealthController {
constructor(health, disk) {}
@Get()
@HealthCheck()
healthCheck() {
return this.health.check([
() => this.disk.checkStorage('storage', { path: '/', thresholdPercent: 0.5 }),
])
}
}
Với hàm DiskHealthIndicator.checkStorage, bạn cũng có thể kiểm tra theo một lượng dung lượng cố định.
Ví dụ sau sẽ unhealthy trong trường hợp path /my-app/ vượt quá 250GB.
// Trong class `HealthController`
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.disk.checkStorage('storage', { path: '/', threshold: 250 * 1024 * 1024 * 1024, })
]);
}
Memory health indicator
Để đảm bảo process của bạn không vượt quá một giới hạn memory nhất định, MemoryHealthIndicator có thể được dùng.
Ví dụ sau có thể dùng để kiểm tra heap của process.
Gợi ý Heap là phần memory nơi chứa memory được cấp phát động (dynamically allocated) (tức memory được cấp phát qua malloc). Memory được cấp từ heap sẽ vẫn được giữ cho đến khi xảy ra một trong các trường hợp sau:
- Memory bị giải phóng (free)
- Chương trình kết thúc (terminate)
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private memory: MemoryHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
]);
}
}
@Controller('health')
@Dependencies(HealthCheckService, MemoryHealthIndicator)
export class HealthController {
constructor(health, memory) {}
@Get()
@HealthCheck()
healthCheck() {
return this.health.check([
() => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),
])
}
}
Cũng có thể kiểm tra RSS memory của process với MemoryHealthIndicator.checkRSS. Ví dụ này
sẽ trả về response code unhealthy trong trường hợp process của bạn đã cấp phát hơn 150MB.
Gợi ý RSS là Resident Set Size, dùng để thể hiện lượng memory được cấp cho process đó và đang nằm trong RAM. Nó không bao gồm memory đã bị swap ra ngoài. Nó có bao gồm memory từ các shared library, miễn là các page của những library đó thực sự đang nằm trong memory. Nó bao gồm toàn bộ stack và heap memory.
// Trong class `HealthController`
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.memory.checkRSS('memory_rss', 150 * 1024 * 1024),
]);
}
Custom health indicator
Trong một số trường hợp, các health indicator được định nghĩa sẵn bởi @nestjs/terminus không đủ để đáp ứng mọi yêu cầu health check của bạn. Trong trường hợp đó, bạn có thể tự thiết lập một custom health indicator theo nhu cầu của mình.
Phần lớn health indicator đều quy về cùng một pattern: chạy một operation, đánh dấu indicator là 'up' nếu thành công và 'down' nếu thất bại. HealthIndicatorService bao quát chính xác pattern này với attempt(). Để có hiểu biết cơ bản về cách một indicator được cấu trúc, ta sẽ tạo một ví dụ DogHealthIndicator. Service này sẽ giữ trạng thái 'up' miễn là một dog API bên ngoài phản hồi trong vòng một giây.
import { Injectable } from '@nestjs/common';
import { HealthIndicatorService } from '@nestjs/terminus';
@Injectable()
export class DogHealthIndicator {
constructor(
private readonly healthIndicatorService: HealthIndicatorService
) {}
isHealthy(key: string) {
return this.healthIndicatorService
.check(key)
.attempt(async ({ signal }) => {
const response = await fetch('https://dog.ceo/api/breeds/list', { signal });
const { message: breeds } = await response.json();
return { breeds: breeds.length };
})
.withTimeout(1000);
}
}
import { Injectable, Dependencies } from '@nestjs/common';
import { HealthIndicatorService } from '@nestjs/terminus';
@Injectable()
@Dependencies(HealthIndicatorService)
export class DogHealthIndicator {
constructor(healthIndicatorService) {
this.healthIndicatorService = healthIndicatorService;
}
isHealthy(key) {
return this.healthIndicatorService
.check(key)
.attempt(async ({ signal }) => {
const response = await fetch('https://dog.ceo/api/breeds/list', { signal });
const { message: breeds } = await response.json();
return { breeds: breeds.length };
})
.withTimeout(1000);
}
}
attempt() nhận một hàm sync hoặc async và đánh dấu indicator là 'up' ngay khi hàm đó resolve. Bất kỳ giá trị gì hàm trả về sẽ được thêm vào kết quả. Nếu hàm throw lỗi, indicator sẽ được đánh dấu là 'down' và error message sẽ được thêm vào kết quả. Trong cả hai trường hợp, thời gian hàm thực thi được báo cáo dưới dạng responseTime. Với withTimeout(), attempt sẽ bị giới hạn theo số milliseconds được truyền vào - nói kỹ hơn ở phần Timeouts and caching.
Việc tiếp theo ta cần làm là đăng ký health indicator như một provider.
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { DogHealthIndicator } from './dog.health.js';
@Module({
controllers: [HealthController],
imports: [TerminusModule],
providers: [DogHealthIndicator]
})
export class HealthModule { }
Gợi ý Trong một ứng dụng thực tế, DogHealthIndicator nên được provide trong một module riêng, ví dụ DogModule, sau đó module này sẽ được import bởi HealthModule.
Bước cuối cùng cần làm là thêm health indicator vừa có được vào health check endpoint cần dùng. Để làm điều đó, ta quay lại HealthController và thêm nó vào hàm check.
import { HealthCheckService, HealthCheck } from '@nestjs/terminus';
import { Controller, Get } from '@nestjs/common';
import { DogHealthIndicator } from './dog.health.js';
@Controller('health')
export class HealthController {
constructor(
private health: HealthCheckService,
private dogHealthIndicator: DogHealthIndicator
) {}
@Get()
@HealthCheck()
healthCheck() {
return this.health.check([
() => this.dogHealthIndicator.isHealthy('dog'),
])
}
}
import { HealthCheckService, HealthCheck } from '@nestjs/terminus';
import { Controller, Dependencies, Get } from '@nestjs/common';
import { DogHealthIndicator } from './dog.health.js';
@Controller('health')
@Dependencies(HealthCheckService, DogHealthIndicator)
export class HealthController {
constructor(
health,
dogHealthIndicator
) {
this.health = health;
this.dogHealthIndicator = dogHealthIndicator;
}
@Get()
@HealthCheck()
healthCheck() {
return this.health.check([
() => this.dogHealthIndicator.isHealthy('dog'),
])
}
}
Nếu API phản hồi kịp thời, health check của ta sẽ trả về kết quả sau:
{
"status": "ok",
"info": {
"dog": {
"status": "up",
"breeds": 98,
"responseTime": 143
}
},
"error": {},
"details": {
"dog": {
"status": "up",
"breeds": 98,
"responseTime": 143
}
}
}
Gợi ý Một attempt được tự Terminus thực thi, nên nó có thể truyền trực tiếp vào health.check([...]) - dù trực tiếp (this.dogHealthIndicator.isHealthy('dog')) hay bọc trong một hàm như ví dụ trên. Cả hai cách đều hoạt động.
Explicit control with up() and down() (kiểm soát rõ ràng với up() và down())
Đôi lúc “healthy” không đơn giản chỉ có nghĩa là “operation không throw lỗi”. Nếu bạn cần tự quyết định trạng thái, bạn có thể trả về up() hoặc down() trực tiếp thay vì dùng attempt(). Giả sử giờ các con dog được lưu ở local và indicator chỉ nên healthy nếu mọi Dog đều có type 'goodboy'.
import { Injectable } from '@nestjs/common';
import { HealthIndicatorService } from '@nestjs/terminus';
export interface Dog {
name: string;
type: string;
}
@Injectable()
export class DogHealthIndicator {
constructor(
private readonly healthIndicatorService: HealthIndicatorService
) {}
private dogs: Dog[] = [
{ name: 'Fido', type: 'goodboy' },
{ name: 'Rex', type: 'badboy' },
];
async isHealthy(key: string) {
const indicator = this.healthIndicatorService.check(key);
const badboys = this.dogs.filter(dog => dog.type === 'badboy');
if (badboys.length > 0) {
return indicator.down({ badboys: badboys.length });
}
return indicator.up();
}
}
import { Injectable, Dependencies } from '@nestjs/common';
import { HealthIndicatorService } from '@nestjs/terminus';
@Injectable()
@Dependencies(HealthIndicatorService)
export class DogHealthIndicator {
constructor(healthIndicatorService) {
this.healthIndicatorService = healthIndicatorService;
}
private dogs = [
{ name: 'Fido', type: 'goodboy' },
{ name: 'Rex', type: 'badboy' },
];
async isHealthy(key) {
const indicator = this.healthIndicatorService.check(key);
const badboys = this.dogs.filter(dog => dog.type === 'badboy');
if (badboys.length > 0) {
return indicator.down({ badboys: badboys.length });
}
return indicator.up();
}
}
Cả up() và down() đều nhận vào một object chứa dữ liệu bổ sung (sẽ được thêm vào kết quả), hoặc một string, sẽ được thêm vào dưới dạng message.
Degraded state (trạng thái suy giảm)
Không phải vấn đề nào cũng đáng để đưa cả ứng dụng ra khỏi vòng hoạt động. Ví dụ, một cache không thể kết nối được sẽ làm ứng dụng chậm đi nhưng không làm nó hỏng. Với những trường hợp như vậy, một indicator có thể trả về degraded(). Một indicator degraded không làm fail health check: nó được liệt vào info, status tổng thể trở thành 'degraded' và HTTP status code vẫn giữ nguyên 200, nên các orchestrator vẫn tiếp tục route traffic trong khi trạng thái này vẫn được hiển thị trong monitoring.
@Injectable()
export class CacheHealthIndicator {
constructor(
private readonly healthIndicatorService: HealthIndicatorService,
private readonly cache: CacheService,
) {}
async isHealthy(key: string) {
const indicator = this.healthIndicatorService.check(key);
const hasCache = await this.cache.isConnected();
if (!hasCache) {
return indicator.degraded('cache unreachable, serving without cache');
}
return indicator.up();
}
}
{
"status": "degraded",
"info": {
"cache": {
"status": "degraded",
"message": "cache unreachable, serving without cache"
}
},
"error": {},
"details": {
"cache": {
"status": "degraded",
"message": "cache unreachable, serving without cache"
}
}
}
Giống như up() và down(), degraded() cũng nhận vào một object chứa dữ liệu bổ sung hoặc một message dạng string. Ngay khi có bất kỳ indicator nào ở trạng thái 'down', status tổng thể sẽ là 'error' bất kể có indicator degraded hay không.
Cảnh báo Một health indicator được xây dựng với up(), down() và degraded() báo cáo trạng thái của nó bằng cách trả về một trong ba hàm này. Một lỗi bị throw ra từ indicator như vậy được xem là một bug, không phải một trạng thái unhealthy: nó sẽ hủy toàn bộ health check và trả về response 500 thay vì 503. Nếu indicator của bạn bọc một operation có thể throw lỗi, nên dùng attempt(), vì nó sẽ tự chuyển một lỗi bị throw thành trạng thái 'down' cho bạn.
Ghi chú người dịch Sự khác biệt giữa 500 và 503 ở trên rất đáng để nhớ, vì nó là điểm dễ vibe-code sai nhất: attempt() coi throw là “chuyện bình thường của một dependency bên ngoài” (giống try/catch quanh một HTTP call trong Express) nên convert thành down → 503 (unhealthy nhưng đúng logic). Còn up()/down()/degraded() coi throw là bug trong code của bạn (giống một exception không bắt được lọt ra khỏi handler PHP) nên để nó lọt ra thành 500. Nếu health check của bạn tự nhiên trả 500 thay vì 503, gần như chắc chắn là do bạn viết logic tự quyết định trạng thái (up/down) nhưng lại quên bọc phần code có thể throw.
Timeouts and caching (timeout và caching)
Mỗi check được xây dựng bằng attempt() - indicator tùy chỉnh của bạn cũng như các indicator có sẵn cho database, microservice và gRPC - đều trả về một HealthCheckAttempt. Trước khi được thực thi, attempt có thể được cấu hình bằng cách chain withTimeout() và cacheFor().
Timeouts
Với withTimeout(), attempt sẽ bị giới hạn theo số milliseconds được truyền vào. Khi hết thời gian, indicator được đánh dấu là 'down' và AbortSignal được truyền vào hàm của bạn sẽ bị abort. Đảm bảo truyền signal đó cho operation bên dưới để nó cũng bị hủy theo, thay vì tiếp tục chạy ngầm phía sau.
// Trong class `HealthController`
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database').withTimeout(1500),
() => this.dogHealthIndicator.isHealthy('dog'),
]);
}
Trong trường hợp dog API từ custom indicator của ta không phản hồi trong vòng một giây, health check sẽ phản hồi với kết quả sau:
{
"status": "error",
"info": {
"database": {
"status": "up",
"responseTime": 12
}
},
"error": {
"dog": {
"status": "down",
"message": "timeout of 1000ms exceeded",
"responseTime": 1001
}
},
"details": {
"database": {
"status": "up",
"responseTime": 12
},
"dog": {
"status": "down",
"message": "timeout of 1000ms exceeded",
"responseTime": 1001
}
}
}
Cảnh báo Option timeout của các indicator có sẵn (ví dụ this.db.pingCheck('database', { timeout: 1500 })) đã bị deprecated. Hãy chain .withTimeout() trên attempt được trả về thay vào đó.
Caching
Health check thường được các orchestrator poll mỗi vài giây, và một số check tốn quá nhiều tài nguyên để chạy trên mỗi request. Với cacheFor(), kết quả của một attempt sẽ được cache trong số milliseconds được chỉ định. Trong khi vẫn có một kết quả còn “mới” (fresh), hàm sẽ không được thực thi lại và các health check đồng thời (concurrent) sẽ dùng chung một lần thực thi đang chạy (in-flight execution). Cache được đánh key theo key của indicator.
// Trong class `HealthController`
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.db.pingCheck('database').withTimeout(1500).cacheFor(5000),
]);
}
Kết quả được trả từ cache sẽ được đánh dấu với cachedResponse: true:
{
"status": "ok",
"info": {
"database": {
"status": "up",
"responseTime": 12,
"cachedResponse": true
}
},
"error": {},
"details": {
"database": {
"status": "up",
"responseTime": 12,
"cachedResponse": true
}
}
}
Logging
Terminus chỉ log các error message, ví dụ khi một Healthcheck thất bại. Với phương thức TerminusModule.forRoot(), bạn có nhiều kiểm soát hơn về cách lỗi được log
cũng như có thể toàn quyền tự xử lý việc logging.
Trong phần này, ta sẽ hướng dẫn bạn cách tạo một custom logger TerminusLogger. Logger này kế thừa từ logger có sẵn.
Vì vậy bạn có thể chọn phần nào của logger bạn muốn ghi đè (overwrite).
Info Nếu bạn muốn tìm hiểu thêm về custom logger trong NestJS, đọc thêm tại đây.
import { Injectable, Scope, ConsoleLogger } from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class TerminusLogger extends ConsoleLogger {
error(message: any, stack?: string, context?: string): void;
error(message: any, ...optionalParams: any[]): void;
error(
message: unknown,
stack?: unknown,
context?: unknown,
...rest: unknown[]
): void {
// Ghi đè tại đây cách các error message nên được log
}
}
Sau khi đã tạo custom logger, việc bạn cần làm chỉ là truyền nó vào TerminusModule.forRoot() như sau.
@Module({
imports: [
TerminusModule.forRoot({
logger: TerminusLogger,
}),
],
})
export class HealthModule {}
Để hoàn toàn ẩn đi mọi log message từ Terminus, kể cả error message, cấu hình Terminus như sau.
@Module({
imports: [
TerminusModule.forRoot({
logger: false,
}),
],
})
export class HealthModule {}
Terminus cho phép bạn cấu hình cách các lỗi Healthcheck được hiển thị trong log của bạn.
| Error Log Style | Mô tả | Ví dụ |
|---|---|---|
json (mặc định) |
In một bản tổng hợp kết quả health check trong trường hợp lỗi dưới dạng object JSON | ![]() |
pretty |
In một bản tổng hợp kết quả health check trong trường hợp lỗi trong các khung được định dạng đẹp và highlight kết quả thành công/lỗi | ![]() |
Bạn có thể đổi kiểu log bằng option cấu hình errorLogStyle như đoạn code sau.
@Module({
imports: [
TerminusModule.forRoot({
errorLogStyle: 'pretty',
}),
]
})
export class HealthModule {}
Graceful shutdown timeout (timeout khi shutdown một cách graceful)
Nếu ứng dụng của bạn cần trì hoãn tiến trình shutdown, Terminus có thể xử lý điều đó cho bạn.
Setting này đặc biệt hữu ích khi làm việc với một orchestrator như Kubernetes.
Bằng cách đặt một khoảng delay dài hơn một chút so với interval của readiness check, bạn có thể đạt được zero downtime khi shutdown container: ngay khi nhận được termination signal, health check sẽ báo trạng thái 'shutting_down' với status code 503, để orchestrator ngừng route traffic mới trong khi ứng dụng hoàn tất các request đang xử lý (in-flight) trong khoảng thời gian delay đó.
@Module({
imports: [
TerminusModule.forRoot({
gracefulShutdownTimeoutMs: 1000,
}),
]
})
export class HealthModule {}
Cảnh báo Khoảng delay này chỉ được áp dụng khi nhận SIGTERM, là signal mà các orchestrator như Kubernetes gửi khi dừng một container. Các signal khác (ví dụ SIGINT từ Ctrl+C) vẫn chuyển health check sang 'shutting_down' nhưng shutdown ứng dụng ngay lập tức. Đảm bảo shutdown hooks đã được bật, nếu không Terminus sẽ không bao giờ nhận được signal này.
Ghi chú người dịch Đây chính là cơ chế “graceful shutdown” mà bạn có thể đã tự viết thủ công trong Go (lắng nghe SIGTERM, ngừng nhận connection mới, chờ request cũ xong rồi mới os.Exit) hoặc trong Express (gọi server.close() rồi mới process.exit()). Terminus chỉ tự động hóa phần “báo cho load balancer/orchestrator biết là đang shutdown” thông qua health check status, còn phần chờ request xử lý xong vẫn phụ thuộc vào shutdown hooks bạn đã đăng ký.
More examples
Nhiều ví dụ hoạt động khác có tại đây.

