On production the code does not change. What changes is who is watching: nobody. The application runs at night, on Sundays and on holidays, and all you know about it are the signals it sends about itself. This lesson is about organising them.
A centurion did not inspect each legionary in person. He had three sources of knowledge: numerical reports on the state of the cohorts, watch records of individual incidents, and couriers' dispatches showing where an order had got stuck on the way. The same division holds to this day.
The three pillars of observability are metrics, logs and traces. Not frontend, backend and database, for those are layers of an application. Not CPU, memory and disk, for those are resources - one kind of metric among many. And not development, testing and production, for those are environments.
Each pillar answers a different question, so none replaces the others:
A metric tells you that something is wrong. A log tells you what broke. A trace tells you where.
An orchestrator - Kubernetes, Docker Swarm, or a plain load balancer - must know whether traffic may be sent to this instance. It asks through a health endpoint:
1@Controller('health')
2export class HealthController {
3 constructor(
4 private health: HealthCheckService,
5 private db: MongooseHealthIndicator,
6 private memory: MemoryHealthIndicator,
7 ) {}
8
9 @Get()
10 @HealthCheck()
11 check() {
12 return this.health.check([
13 () => this.db.pingCheck('database'),
14 () => this.memory.checkHeap('memory_heap', 300 * 1024 * 1024),
15 ]);
16 }
17}The
@nestjs/terminus package supplies the parts ready-made: HealthCheckService gathers the results and assembles the response, MongooseHealthIndicator checks the database connection, and MemoryHealthIndicator watches heap use. There are more indicators - for Redis, for the disk, for any external HTTP service.A health check should check the availability of the database, Redis and dependent services. Not whether the source code is up to date - that is not its role. Not the number of logged-in users - that is a business metric, not a health signal. And certainly not only whether the HTTP server responds.
That last distinction is the heart of it. An application whose database has died still answers HTTP - the process is alive, the port is open,
200 OK comes back without delay. A health check testing only that tells the orchestrator "all is well" about an instance that cannot serve a single real request. Check the dependencies without which the application can do nothing anyway.A monitoring system is set up in four steps, in this order:
The order is often reversed, and that is the commonest mistake in setting monitoring up. Dashboards come last, because until metrics are flowing you do not know what you would draw on them. Alerts come before them, because an alert arrives by itself whereas a dashboard has to be looked at - and at three in the morning nobody is looking.
Not every threshold crossing means the same thing. Alerts fall into four levels:
Note that the first two thresholds lie below a hundred per cent. That is deliberate: an alert at 100% is no longer a warning but a notification of failure. Sensible monitoring gives time to react, not a commentary on the fire.
The fourth level is often skipped, and it is the only one that changes anything for the future. Without a post-incident analysis the same alert will ring again next month.
The last rule concerns switching off. When a new version is deployed the old instance receives a
SIGTERM signal - and what it does over the next few seconds decides whether anyone sees an error:1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 app.enableShutdownHooks();
5
6 await app.listen(3000);
7}
makes NestJS intercept the signal and, before closing, call the enableShutdownHooks()
onModuleDestroy and beforeApplicationShutdown methods in the modules. The application thus has time to finish the requests in flight, close database connections and deregister from the service registry.Without it a deployment cuts off the requests being served at that moment. At ten deployments a day that is ten bursts of errors nobody connects with the deployment - because in the logs they look like random dropped connections.
The centurion reads reports rather than inspecting every legionary, @name:
200 - which is why checking HTTP alone is worthless,@nestjs/terminus: HealthCheckService assembles the result, MongooseHealthIndicator tests the database, MemoryHealthIndicator the heap,enableShutdownHooks() lets in-flight requests finish when an instance is shut down.That closes our survey of production practice. For now remember: an application in production is only as good as the signals it sends about itself - because nobody is going to guess what is happening to it.