Bạn có bao giờ nhận điện thoại lúc 2 giờ sáng vì API bị chậm mà không biết nguyên nhân ở đâu không? Mình từng như vậy — SSH vào từng server kiểm tra log, chạy top, netstat, mà vẫn không ra vấn đề. Từ khi tích hợp prom-client + Grafana, mọi thứ thay đổi hẳn. Chỉ cần mở dashboard là thấy ngay: request rate đang bao nhiêu, endpoint nào chậm, error rate là mấy phần trăm.
Blog đã có bài cài Prometheus + Grafana để monitor server (CPU, RAM, disk). Bài này đi sâu hơn một tầng: giám sát ở application level — tức là chính code Node.js của bạn đang làm gì, endpoint nào bị bottleneck, và business logic có đang chạy đúng không.
3 cách giám sát Node.js — so sánh trước khi chọn
Không phải lúc nào prom-client cũng là đáp án đúng. Dưới đây là 3 hướng phổ biến — mỗi cái có lý do tồn tại riêng.
Cách 1: Chỉ dùng log + tìm thủ công
Ghi log request/response ra file, dùng grep, awk hoặc Graylog để phân tích sau.
- Ưu điểm: Không cần setup thêm gì, log có sẵn rồi, dễ debug lỗi cụ thể
- Nhược điểm: Không thấy được trend theo thời gian, không có alerting real-time, phân tích thủ công tốn thời gian — nhất là khi sự cố xảy ra lúc 3 giờ sáng
Cách 2: APM commercial (Datadog, New Relic, Dynatrace)
Cài agent, tự động trace mọi thứ, dashboard đẹp sẵn ngay từ đầu.
- Ưu điểm: Cực kỳ dễ setup, có distributed tracing, anomaly detection, không cần tự quản lý hạ tầng
- Nhược điểm: Chi phí cao (Datadog từ $15/host/tháng, chưa kể $0.10/GB data ingested), vendor lock-in, không tự định nghĩa được metrics theo business logic riêng
Cách 3: prom-client + Prometheus + Grafana (self-hosted)
Bạn tự expose metrics từ code, Prometheus scrape định kỳ, Grafana visualize và alert.
- Ưu điểm: Hoàn toàn miễn phí, full control, tự định nghĩa metrics theo ý muốn, cộng đồng lớn, tích hợp tốt với Kubernetes
- Nhược điểm: Cần biết PromQL để query, tự quản lý hạ tầng Prometheus + Grafana
Tại sao chọn prom-client?
Nếu dự án đã có Prometheus rồi (hoặc đang tính cài), prom-client là lựa chọn tự nhiên nhất. APM commercial phù hợp cho team lớn với budget cao, cần distributed tracing phức tạp. Với startup, side project, hoặc khi bạn muốn track chính xác business metrics theo cách riêng — prom-client + Grafana là đủ dùng và free hoàn toàn.
prom-client có 4 loại metric. Counter chỉ tăng — dùng đếm request, lỗi. Gauge tăng giảm tùy — active connections, memory. Histogram cho phân phối — request duration. Summary tính quantile phía client. Với web API, Counter và Histogram là hai cái bạn sẽ đụng nhiều nhất.
Tích hợp prom-client vào Express.js — từng bước
Bước 1: Cài package
npm install prom-client
Bước 2: Khởi tạo metrics trong file riêng
Tách logic monitoring ra file metrics.js riêng để không lẫn vào business code:
// metrics.js
const client = require('prom-client');
const register = new client.Registry();
// Thu thập default metrics của Node.js (memory heap, event loop lag, GC...)
client.collectDefaultMetrics({ register });
// Counter: đếm tổng số HTTP request
const httpRequestsTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code'],
registers: [register],
});
// Histogram: phân phối thời gian xử lý request (latency)
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
registers: [register],
});
// Business metric: đếm đơn hàng tạo ra (ví dụ)
const ordersCreatedTotal = new client.Counter({
name: 'orders_created_total',
help: 'Total number of orders created',
labelNames: ['status', 'payment_method'],
registers: [register],
});
// Gauge: số user đang active (có thể tăng/giảm)
const activeUsers = new client.Gauge({
name: 'active_users_current',
help: 'Number of currently active users',
registers: [register],
});
module.exports = { register, httpRequestsTotal, httpRequestDuration, ordersCreatedTotal, activeUsers };
Bước 3: Middleware tự động track mọi HTTP request
// middleware/metricsMiddleware.js
const { httpRequestsTotal, httpRequestDuration } = require('../metrics');
function metricsMiddleware(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
// req.route.path cho ra pattern như /api/users/:id thay vì /api/users/123
const route = req.route ? req.route.path : req.path;
const labels = { method: req.method, route, status_code: res.statusCode };
httpRequestsTotal.inc(labels);
httpRequestDuration.observe(labels, duration);
});
next();
}
module.exports = metricsMiddleware;
Bước 4: Đăng ký middleware và expose /metrics
// app.js
const express = require('express');
const { register } = require('./metrics');
const metricsMiddleware = require('./middleware/metricsMiddleware');
const app = express();
app.use(express.json());
app.use(metricsMiddleware);
// Prometheus scrape endpoint — KHÔNG để public, xem lưu ý bên dưới
app.get('/metrics', async (req, res) => {
res.set('Content-Type', register.contentType);
res.end(await register.metrics());
});
app.get('/api/orders', (req, res) => {
res.json({ orders: [] });
});
app.listen(3000, () => console.log('Server :3000 | Metrics: :3000/metrics'));
Bước 5: Track custom business metrics ngay trong route handler
Đây là chỗ prom-client tỏa sáng so với monitoring generic — bạn track được chính xác logic nghiệp vụ của riêng mình:
// routes/orders.js
const { ordersCreatedTotal, activeUsers } = require('../metrics');
app.post('/api/orders', async (req, res) => {
try {
const order = await createOrder(req.body);
ordersCreatedTotal.inc({ status: 'success', payment_method: order.paymentMethod });
res.json({ success: true, orderId: order.id });
} catch (err) {
ordersCreatedTotal.inc({ status: 'failed', payment_method: req.body.paymentMethod || 'unknown' });
res.status(500).json({ error: err.message });
}
});
app.post('/api/login', async (req, res) => {
// ... auth logic
activeUsers.inc();
res.json({ token: '...' });
});
app.post('/api/logout', (req, res) => {
activeUsers.dec();
res.json({ success: true });
});
Cấu hình Prometheus scrape Node.js app
Mở file prometheus.yml, thêm job mới song song với job node-exporter đã có:
scrape_configs:
- job_name: 'node-exporter'
static_configs:
- targets: ['localhost:9100']
# Job mới cho Node.js application
- job_name: 'nodejs-app'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/metrics'
scrape_interval: 15s
Reload Prometheus config (không cần restart):
curl -X POST http://localhost:9090/-/reload
PromQL queries cho Grafana Dashboard
Prometheus đã scrape xong, giờ là lúc xây dashboard. 4 panel dưới đây là đủ để có cái nhìn toàn cảnh về sức khỏe API:
Request Rate (requests/giây)
sum(rate(http_requests_total[5m])) by (route, method)
P95 Latency — metric quan trọng nhất
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, route))
P95 nghĩa là 95% request hoàn thành trong thời gian X. Thực tế hơn average nhiều — average dễ bị kéo bởi những request nhanh và che giấu những request thực sự chậm.
Error Rate (% lỗi 5xx)
rate(http_requests_total{status_code=~"5.."}[5m]) / rate(http_requests_total[5m]) * 100
Business Metric: Tỷ lệ đơn hàng thành công
rate(orders_created_total{status="success"}[5m]) / rate(orders_created_total[5m]) * 100
Kiểm tra nhanh trước khi kết nối Grafana
# Chạy app
node app.js
# Gửi vài request test
curl http://localhost:3000/api/orders
curl -X POST http://localhost:3000/api/orders \
-H "Content-Type: application/json" \
-d '{"item":"product-1","paymentMethod":"card"}'
# Xem raw metrics output
curl http://localhost:3000/metrics
Nếu thấy output dạng này là ổn:
# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",route="/api/orders",status_code="200"} 3
http_requests_total{method="POST",route="/api/orders",status_code="200"} 1
# HELP http_request_duration_seconds Duration of HTTP requests in seconds
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.005",...} 2
http_request_duration_seconds_bucket{le="0.01",...} 4
Những lưu ý từ thực tế
- Không dùng user_id làm label: Label phải có cardinality thấp. Dùng
user_idhayrequest_idlàm label sẽ tạo hàng triệu time series — Prometheus tràn bộ nhớ rất nhanh. Method, route, status_code là an toàn. - Bảo vệ /metrics endpoint: Đừng để public. Dùng Basic Auth, whitelist IP nội bộ, hoặc bind metrics server ra port riêng chỉ Prometheus nội bộ mới reach được. Endpoint này tiết lộ khá nhiều thông tin về hạ tầng.
- scrape_interval 15s là đủ: Đừng cài 5s hay thấp hơn nếu không có lý do cụ thể — tăng load không cần thiết cho cả app lẫn Prometheus.
- Test route normalization: Với nested Express router,
req.route.pathcó thể trả về path tương đối. Test kỹ để chắc/api/users/:idkhông bị lẫn thành/api/users/123.
Có metrics trong Grafana rồi, bước tiếp là setup alert — ví dụ cảnh báo khi P95 latency vượt 500ms, hoặc error rate > 5% liên tục 5 phút. Alertmanager đã có bài riêng trên blog; kết hợp với dashboard này là có vòng lặp giám sát hoàn chỉnh.
