Khi một service production gặp sự cố lúc 2 giờ sáng, thứ quyết định bạn tìm ra nguyên nhân trong 5 phút hay 5 giờ là observability — khả năng quan sát hệ thống từ bên ngoài. Và observability dựng trên ba trụ cột: metrics, logs, traces. Người mới thường nghĩ chúng là ba cách làm cùng một việc, rồi chọn một cái. Sai lầm: chúng trả lời ba câu hỏi khác nhau, và một hệ thống quan sát tốt cần cả ba, phối hợp với nhau.

Đây là bài mở đầu loạt "Observability thực chiến cho backend". Thay vì định nghĩa trừu tượng, mình dựng một Go app thật được instrument, cho Prometheus scrape, rồi nhìn cùng một request qua cả ba lăng kính trên pg... trên obs-prom.

Ba lăng kính, ba câu hỏi

  • Metrics: số tổng hợp theo thời gian (counter, gauge, histogram). Trả lời "hệ thống có khỏe không? xu hướng ra sao?". Rẻ (đã gộp sẵn), giữ lâu — nền của dashboard và cảnh báo.
  • Logs: sự kiện rời rạc, lý tưởng là có cấu trúc (JSON). Trả lời "chuyện gì đã xảy ra chính xác?". Chi tiết nhất về một sự kiện đơn lẻ.
  • Traces: hành trình của một request qua các service (chuỗi span). Trả lời "request này chậm/lỗi ở bước nào?". Đắt (mỗi request), nhưng vô giá để định vị trong hệ phân tán.

Ba trụ cột observability metrics logs traces ba lăng kính ba câu hỏi: metrics là số tổng hợp theo thời gian counter gauge histogram trả lời có khỏe không xu hướng chi phí rẻ gộp sẵn; logs là sự kiện rời rạc có cấu trúc JSON trả lời chuyện gì đã xảy ra chính xác chi phí trung bình; traces là hành trình một request qua các service span trả lời chậm lỗi ở bước nào chi phí đắt mỗi request; cùng một endpoint work đo cả ba Go app instrument metric đếm request cộng đo trễ Prometheus client, log dòng JSON có cấu trúc kèm trace_id, trace mỗi request một trace_id nối các bước service lại; khi nào dùng metrics dashboard cảnh báo xu hướng phát hiện có vấn đề, traces theo một request qua nhiều service tìm bước chậm định vị vấn đề, logs chi tiết chính xác của một sự kiện lỗi hiểu vấn đề, trace_id trong log nối ba cái lại metric báo động trace tìm request log đọc chi tiết

Hình 1: Ba trụ cột trả lời ba câu hỏi. Metrics (tổng hợp, rẻ) phát hiện vấn đề; traces (hành trình request, đắt) định vị; logs (chi tiết sự kiện) hiểu. trace_id nối cả ba: metric báo động → trace tìm request → log đọc chi tiết.

// Cung endpoint /work, do ca 3:
reqs.WithLabelValues("/work", status).Inc()          // METRIC: dem
dur.WithLabelValues("/work").Observe(elapsed)        // METRIC: do tre
log.Printf(`{"path":"/work","status":%s,"dur_ms":%d,"trace_id":"%s"}`,...)  // LOG co trace_id
// TRACE: moi request mot trace_id, noi cac buoc/service lai

Đo thật: cùng một endpoint qua ba trụ cột

Mình chạy một Go app instrument (Prometheus client) trên go-lab, cho obs-prom scrape, gửi 100 request tới /work (ngẫu nhiên ~10% trả lỗi 500). Rồi nhìn qua ba lăng kính. Kết quả thật:

Bảng kết quả đo thật một endpoint qua 3 trụ cột obs-prom cộng go-lab prometheus v2.54.1 Go app instrument 100 request tới work: metric Prometheus scrape counter của app tổng hợp app_requests_total path work status 200 bằng 90 status 500 bằng 11 một con số tổng hợp 90 thành công 11 lỗi tỉ lệ lỗi khoảng 10,9 phần trăm rẻ giữ lâu hợp cho dashboard cộng cảnh báo trả lời có vấn đề không nhưng không cho biết request nào lỗi; log dòng JSON có cấu trúc mỗi sự kiện một dòng level info path work status 200 dur_ms 26 trace_id 6e8c218d, status 200 dur_ms 18 trace_id d2b64d50, status 200 dur_ms 9 trace_id 89bfb866 chi tiết chính xác từng request; trace trace_id nối các bước của một request phản hồi work ok 2c5a1d13 trace_id xuất hiện trong cả phản hồi lẫn log JSON theo một request xuyên suốt. Badge output thật màu xanh

Hình 2: Kết quả thật. Metric: 90 thành công / 11 lỗi (tổng hợp). Log: dòng JSON chi tiết từng request kèm trace_id. Trace: trace_id nối phản hồi với log của cùng request. Ba câu trả lời cho ba câu hỏi.

  • 1. METRIC (từ Prometheus): app_requests_total{status="200"} = 90, {status="500"} = 11. Một con số tổng hợp: 90 thành công, 11 lỗi (tỉ lệ lỗi ~10,9%). Rẻ, giữ lâu, hợp cho dashboard và cảnh báo. Nhưng nó không cho biết request nào lỗi.
  • 2. LOG (JSON có cấu trúc): mỗi request một dòng JSON với status, dur_ms, trace_id. Chi tiết chính xác từng request; vì là JSON (không phải text tự do), máy có thể truy vấn và lọc được. Trả lời "chuyện gì xảy ra chính xác".
  • 3. TRACE (trace_id): mỗi request có một trace_id xuất hiện trong cả phản hồi lẫn log. Đây là sợi dây theo một request xuyên suốt — trong hệ nhiều service, trace_id đi qua từng service để ghép lại thành hành trình đầy đủ.

Mấu chốt: trace_id là sợi dây nối ba trụ cột. Quy trình điều tra sự cố thực tế là: metric báo "tỉ lệ lỗi tăng" (phát hiện) → trace tìm một request lỗi cụ thể và xem nó chậm/hỏng ở service nào (định vị) → log đọc chi tiết chính xác tại trace_id đó (hiểu). Thiếu một trụ cột, bạn mất một bước trong chuỗi này.

Đánh đổi cần cân nhắc

Ba trụ cột khác nhau về chi phí — chọn độ phủ hợp lý cho từng cái. Metrics rẻ nhất (gộp sẵn thành vài time series, giữ được hàng tháng) nhưng mất chi tiết — bạn biết "10% lỗi" chứ không biết request nào. Traces chi tiết nhất nhưng đắt nhất (mỗi request một trace, tốn lưu trữ và overhead), nên production thường lấy mẫu (sampling — chỉ giữ 1% traces, hoặc giữ 100% traces lỗi). Logs ở giữa nhưng dễ bùng nổ dung lượng (một service bận ghi hàng GB log/ngày). Chiến lược đúng: metrics cho mọi thứ (rẻ), logs có cấu trúc + giữ ngắn hạn, traces lấy mẫu + giữ 100% khi lỗi.

Structured logging (JSON) đáng giá hơn log text tự do nhiều. Log "xu ly request /work mat 26ms" đẹp cho mắt người nhưng máy khó truy vấn: muốn "tìm mọi request /work chậm >100ms" phải regex mong manh. Log JSON {"path":"/work","dur_ms":26} cho phép truy vấn có cấu trúc (lọc theo trường, gộp, cảnh báo). Chi phí: hơi khó đọc bằng mắt thô, và tốn byte hơn chút. Nhưng ở quy mô mà bạn cần observability, truy vấn được là bắt buộc — luôn log có cấu trúc.

Observability không miễn phí — instrument có overhead và cần kỷ luật. Mỗi metric, log, span đều tốn CPU/bộ nhớ/mạng để tạo và gửi. Instrument quá tay (quá nhiều metric cardinality cao — bài 7, log mọi thứ, trace 100% không sampling) có thể làm chính observability trở thành gánh nặng hiệu năng và chi phí lớn hơn cả ứng dụng. Mục tiêu không phải "đo mọi thứ" mà "đo đúng thứ cần để trả lời câu hỏi vận hành". Các bài sau sẽ đo cụ thể chi phí này.

Ba ý mang về

  1. Metrics, logs, traces trả lời ba câu hỏi khác nhau, cần cả ba. Đo thật cùng endpoint /work: metric cho 90 thành công/11 lỗi (có vấn đề không?), log JSON cho chi tiết từng request (chuyện gì xảy ra?), trace_id nối các bước (lỗi ở đâu?). Đừng chọn một — mỗi cái cho một câu hỏi.
  2. trace_id là sợi dây nối ba trụ cột. Quy trình điều tra: metric phát hiện → trace định vị request → log hiểu chi tiết. trace_id xuất hiện trong cả log và trace giúp nhảy từ "có lỗi" tới "chính xác request này hỏng ở đây".
  3. Ba trụ cột khác nhau về chi phí — phối hợp thông minh. Metrics rẻ (dùng cho mọi thứ, cảnh báo); traces đắt (lấy mẫu, giữ 100% khi lỗi); logs dễ bùng nổ (có cấu trúc, giữ ngắn hạn). Observability có overhead — đo đúng thứ cần, không đo mọi thứ.

Nguồn

Phần sau ta đi sâu vào bốn loại metric của Prometheus — counter, gauge, histogram, summary — khi nào dùng cái nào, và đo thật chúng trên một Go app được instrument.