Logs, metrics và traces: ba góc nhìn cho cùng một request lỗi Cơ bản
Thời gian đọc ước tính: 6 phút
Vấn đề
Phần tiêu đề “Vấn đề”Khách báo “trang đặt hàng đôi khi quay mãi”. Một log request failed cho biết có lỗi nhưng không biết nhiều hay ít. Dashboard p95 tăng nhưng không chỉ ra chặng nào chậm. Một trace cho thấy payment chiếm phần lớn thời gian nhưng không nói toàn bộ traffic có cùng triệu chứng hay không. Chỉ thu một loại dữ liệu thì phải đoán phần còn lại.
| Signal | Trả lời | Giới hạn | Ví dụ cụ thể |
|---|---|---|---|
| Metric | Bao nhiêu, xu hướng, route nào? | Không chỉ ra request nào hỏng | Tỷ lệ lỗi 5xx của /orders/:id/pay |
| Trace | Request đi qua những chặng nào, mỗi chặng bao lâu? | Chỉ thấy phần được instrument và được lấy mẫu | Span payment.charge chiếm 850 ms trong 900 ms |
| Log | Chuyện gì đã xảy ra ở sự kiện này? | Có thể bị sampling, redact hoặc thiếu | payment.timeout, deadline_ms: 330 |
Mỗi signal trông thế nào
Phần tiêu đề “Mỗi signal trông thế nào”Log có cấu trúc: field có tên thay vì một chuỗi dài. Đây là output thật từ demo trong repo (hai request song song, traceId giữ nguyên qua await):
{"event":"request.start","traceId":"796e70b7-...","orderId":42}{"event":"request.error","traceId":"1884c8d2-...","reason":"invalid order"}Metric: câu hỏi “tỷ lệ lỗi” là một PromQL, không phải một dòng log:
sum(rate(http_requests_total{route="/orders/:id/pay", status=~"5.."}[5m])) /sum(rate(http_requests_total{route="/orders/:id/pay"}[5m]))Trace: cây span có start, end và quan hệ cha-con; trace_id nối chúng với log. Trace chỉ là bản đồ của những đoạn đã đo, không phải bộ giải thích nguyên nhân: span payment 850 ms có thể gồm cả chờ connection pool hay retry.
Drill-down mặc định: alert, exemplar, trace, log
Phần tiêu đề “Drill-down mặc định: alert, exemplar, trace, log”Đang tải diagram…
Exemplar là mắt xích nối metric với trace: một data point của histogram kèm trace_id của một request đại diện. Prometheus chỉ lưu exemplar khi bật --enable-feature=exemplar-storage; chúng nằm trong buffer vòng cố định trong RAM, cấu hình bằng khối storage.exemplars. Không có exemplar thì quay về cách thủ công: lọc trace theo route và khoảng thời gian.
Dừng ở giả thuyết, không ở kết luận: một trace chưa chứng minh nguyên nhân. Đối chiếu bằng metric của dependency, ví dụ pool utilization.
Sampling: head vs tail
Phần tiêu đề “Sampling: head vs tail”| Head sampling | Tail sampling | |
|---|---|---|
| Quyết định lúc | Đầu trace, chưa biết kết quả | Sau khi thấy gần hết span |
| Tiêu chí | Xác suất theo trace_id (ví dụ 5%) |
Có lỗi, tổng latency, attribute, tỷ lệ theo service |
| Ưu | Đơn giản, rẻ, chạy ở bất kỳ đâu | Giữ được trace lỗi và trace chậm |
| Nhược | Không đảm bảo giữ trace lỗi | Khó vận hành: component có state, tốn tài nguyên |
Đặt ở OpenTelemetry Collector (Tail Sampling Processor). Hệ thống rất lớn thường kết hợp cả hai: head sampling để bảo vệ pipeline, rồi tail sampling để chọn lọc. Nếu chỉ dùng head sampling, log phải đủ thông tin để điều tra mà không cần trace.
Percentile: lấy từ histogram, không lấy trung bình
Phần tiêu đề “Percentile: lấy từ histogram, không lấy trung bình”- p95 của từng instance không thể cộng hay lấy trung bình; Prometheus nói thẳng rằng trung bình các quantile cho ra giá trị vô nghĩa về mặt thống kê.
- Dùng histogram: cộng bucket của mọi instance, rồi mới tính quantile.
histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))Bucket boundaries phải giống nhau giữa các instance. Native histogram tương thích bucket tự động và được Prometheus khuyến nghị khi dùng được.
Khi nào dùng cái nào
Phần tiêu đề “Khi nào dùng cái nào”| Câu hỏi | Bắt đầu bằng |
|---|---|
| Có sự cố không, lớn cỡ nào? | Metric (RED: Rate, Errors, Duration, cho service request-driven) |
| Tài nguyên có cạn không? | Metric (USE: Utilization, Saturation, Errors, cho tài nguyên) |
| Chậm ở chặng nào? | Trace |
| Lỗi cụ thể là gì? | Log kèm trace_id |
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Chạy demo (hai request song song, ID 42 thành công, -1 lỗi):
npx --prefix examples ts-node examples/src/observability-signals/signals-demo.tsDòng cuối là metrics { total: 2, errors: 1 }; requests chỉ là object trong RAM, không phải counter Prometheus, và spanId chỉ là chuỗi minh họa, chưa phải span chuẩn. Xóa context.run(...) để thấy log mất traceId trong khi counter vẫn tăng. Về AsyncLocalStorage và propagation qua network, xem correlation ID; về label metric, xem metric cardinality.
trace contextRequest vào API; tạo hoặc nhận trace context và ghi thời điểm bắt đầu.
Checklist production
Phần tiêu đề “Checklist production”- Log có cấu trúc, mỗi dòng có
trace_id,route,status,duration_ms. - Metric đếm đúng một lần theo định nghĩa (không đếm ở cả middleware và controller).
- Label metric có số giá trị hữu hạn; ID cá thể ở log hoặc trace.
- Latency là histogram; percentile tính bằng
histogram_quantiletrên bucket đã cộng. - Propagation chuẩn qua HTTP và message; kiểm tra bằng integration test.
- Chiến lược sampling nêu rõ: có giữ trace lỗi không (tail), hay log đủ để bù.
- Redact secret và PII ở nguồn; phân quyền và retention cho log.
Trade-offs
Phần tiêu đề “Trade-offs”Khi kết hợp ba signal
- Metric phát hiện xu hướng, trace khoanh vùng, log giữ chi tiết.
trace_idtrong log rút ngắn việc tìm dữ liệu của một request.- Quy ước route, service, status giúp đối chiếu nhiều tầng.
Chi phí phải kiểm soát
- Log và trace tốn ingest; sampling và retention làm mất một phần.
- Label cardinality cao làm tăng chi phí metric.
- Instrumentation sai hoặc context đứt cho kết luận sai.
Sai lầm thường gặp
Phần tiêu đề “Sai lầm thường gặp”Tóm tắt
Phần tiêu đề “Tóm tắt”Tự kiểm tra: (1) Vì sao p95 tăng chưa đủ kết luận payment chậm? (2) Head sampling bỏ sót điều gì mà tail sampling giữ được? (3) Vì sao không lấy trung bình p95 của các Pod?