Khi hệ thống lên production, “nó chạy” và “nó đang chạy ra sao” là hai chuyện khác nhau. Người ops/dev tốt cần biết ba thứ: dịch vụ đang nói gì (logs), đếm/đo được gì (metrics), và một request đi qua đâu với thời gian bao nhiêu (traces).

Ba cái này không thay thế cho nhau. Bài này làm rõ: mỗi cái để làm gì, sai lầm phổ biến, và cách ghép ba lại để debug nhanh một sự cố thật.


1. Đặt lại bài toán: mình đang giải gì?

Trước công cụ, hỏi “mình đang cần trả lời câu nào?”

  • Có đang có vấn đề không? → Metrics (error rate, latency P99, QPS).
  • Ở đâu trong hệ thống? → Traces (chuyển từ service A → B → DB).
  • Chi tiết gì đã xảy ra? → Logs (message, stack trace, params).

Nếu bỏ một trụ cột, bạn sẽ đoán mò ở bước còn lại. Metrics nói “5% request 5xx”, traces chỉ “chậm ở service X khi gọi Redis”, logs cho “timeout connect pool full”. Ghép lại: chẩn đoán nhanh.


2. Logs: chi tiết sự kiện

2.1. Level và ý nghĩa

  • DEBUG: chi tiết phục vụ phát triển. Không bật mặc định trong production (trừ khi bật theo feature flag ngắn hạn).
  • INFO: sự kiện quan trọng, hợp lệ (“order created”, “user logged in”).
  • WARN: bất thường nhưng xử lý được (“retry 1/3 sau lỗi mạng”).
  • ERROR: lỗi cần can thiệp.
  • FATAL: chết tiến trình / không thể phục vụ.

Hướng dẫn thực tế: nếu pager reo vì WARN thì bạn đang log sai level. Nếu log đầy ERROR nhưng pager không reo, alert hoặc level sai.

2.2. Structured logging

Log dạng JSON có trường thay vì free text:

{
  "ts": "2026-04-10T12:00:01.234Z",
  "level": "ERROR",
  "service": "order-svc",
  "env": "prod",
  "msg": "db query timeout",
  "route": "/api/orders",
  "method": "POST",
  "user_id_hash": "a1b2c3",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "query": "SELECT ... ",
  "elapsed_ms": 5021,
  "err": "pq: canceling statement due to statement timeout"
}

Lợi: log tool (Elastic, Loki, OpenSearch, Datadog…) parse ngay, filter/aggregate theo field. Tránh log chỉ chuỗi như "Failed to load user 42 at 12:00" — khó query.

2.3. Không log PII trần trụi

Đừng log password, token, PAN, CCV, email, số CMND. Nếu cần đối chiếu:

  • Hash hoặc mask: user_id_hash, email_suffix="@gmail.com".
  • Log user_id (khoá nội bộ) thay vì email.

Tuân thủ GDPR / Nghị định bảo vệ dữ liệu cá nhân — khi user xoá tài khoản, log có thể cần purge hoặc redact theo quy định.

2.4. Sampling logs khi cần

Với endpoint QPS cực cao (ingress gateway), log mỗi request sẽ ngốn IO. Hai cách:

  • Log 1 trong N request INFO; vẫn log 100% ERROR/WARN.
  • Rate limit log theo key (ví dụ không log cùng lỗi quá 10 lần/giây).

Đừng sampling ERROR chỉ vì “đầy log” — đó là triệu chứng code hỏng hoặc alert sai.

2.5. Retention và chi phí

Log là đắt nhất trong ba trụ cột. Giữ nóng 7–14 ngày cho debug, archive 90 ngày cho audit, xa hơn thì object storage rẻ. Ngân sách log thường quyết định bạn có được log “rộng tay” không — thiết kế từ đầu.


3. Metrics: đo lường tập hợp

3.1. Các loại metric cơ bản

  • Counter: chỉ tăng (số request, số lỗi).
  • Gauge: giá trị tại thời điểm (số connection, memory used).
  • Histogram / Summary: phân phối (latency); cho phép tính P50/P95/P99.

Prometheus là mô hình phổ biến: scrape HTTP endpoint /metrics định kỳ. OpenTelemetry metrics dần thay thế/hợp nhất.

3.2. Chuẩn đặt tên

Theo Prometheus convention:

http_requests_total{service="order",route="/api/orders",method="POST",status="200"}
http_request_duration_seconds_bucket{service="order",route="/api/orders",le="0.5"}
db_connections_in_use{pool="primary"}
  • Dùng snake_case, đơn vị rõ (_seconds, _bytes, _total).
  • Label phải có giới hạn cardinality (xem phần 3.3).

3.3. Cardinality — cạm bẫy lớn nhất

Cardinality là số tổ hợp nhãn khả dĩ của một metric. Mỗi tổ hợp là một chuỗi thời gian riêng trong TSDB.

  • {route="/api/orders"} — OK, ít giá trị.
  • {user_id="42"}bom: mỗi user là một series.
  • {url="/api/orders/abc123"}bom: ID trong URL đổi mỗi request.

Quy tắc: metric dùng label có tập giá trị hữu hạn, nhỏ (status code, route template, method, region). Dữ liệu per-user / per-request → log/trace, không phải metric.

Hệ quả khi vỡ cardinality: TSDB phình, scrape chậm, query sập. Rất khó fix sau khi đã đẩy sai — phải rollback metric, chờ TTL hết.

3.4. RED và USE

Hai framework thực dụng:

  • RED (cho service request-response): Rate (QPS), Errors (error rate), Duration (latency).
  • USE (cho resource): Utilization, Saturation, Errors.

Khi xây dashboard mới cho một service, hãy có RED trước.

3.5. SLI / SLO

  • SLI (indicator): con số đo được (ví dụ: rate(http_requests_total{status="5xx"}) / rate(http_requests_total)).
  • SLO: mục tiêu (“99.9% request có status < 500 trong 28 ngày rolling”).
  • Error budget: phần còn lại của SLO — quyết định bạn được phép “liều” bao nhiêu thay đổi.

Metric không có SLO là metric dashboard để xem cho biết. Thêm SLO biến nó thành công cụ ra quyết định.


4. Traces: hành trình một request

4.1. Mô hình

  • Một trace có ID (hex 16 bytes — W3C Trace Context).
  • Bên trong có nhiều span: mỗi đoạn công việc (một RPC, một query DB, một xử lý nội bộ). Span có span_id, parent_span_id, thời gian bắt đầu/kết thúc, attributes (key-value).
  • Context propagation: khi service A gọi B, A nhét traceparent vào HTTP header; B đọc ra và tiếp tục cây span.

4.2. Giá trị thực

  • Hiển thị “đang chậm ở đâu” khi request đi qua nhiều service.
  • Phát hiện fan-out không cần thiết (ví dụ 50 query DB nối tiếp trong 1 request → N+1).
  • Liên kết với log và metric (nhờ trace_id chung).

4.3. Sampling

Ghi 100% trace tốn kém ở QPS cao. Chiến lược:

  • Head-based: quyết định sampling ngay từ entry (ví dụ 1 trong 100). Đơn giản, dễ dự đoán chi phí. Mất ngẫu nhiên một số trace.
  • Tail-based: ghi buffer, quyết định giữ sau khi thấy kết quả (ưu tiên giữ trace có lỗi/chậm). Cần collector mạnh.
  • Rate limiting per endpoint: các route bận sampling thấp, route ít được sampling cao để đủ dữ liệu.

Luôn force sample cho request có X-Debug-Trace header hoặc từ internal tool — đảm bảo debug on-demand.

4.4. Đừng bỏ qua trace DB/cache

Bật instrumentation cho:

  • HTTP client / server.
  • DB driver (Postgres, MySQL, Redis).
  • Message broker (Kafka, RabbitMQ, SQS).
  • Framework (Express, Rails, Spring, Django).

Nếu thiếu, bạn có cây span thưa thớt — mất phần quan trọng của câu chuyện.

4.5. Attributes quan trọng

Trên mỗi span, ít nhất:

  • service.name, service.version, deployment.environment.
  • http.method, http.route, http.status_code.
  • db.system, db.statement (che thông tin nhạy cảm), db.operation.
  • error=true + exception.message, exception.stacktrace khi lỗi.

Đây là quy ước OpenTelemetry Semantic Conventions — tuân theo để các tool (Jaeger, Tempo, Datadog, Honeycomb) hiểu chuẩn.


5. Tương quan ba trụ cột

Chìa khoá: cùng một trace_id xuất hiện ở cả log, metric (qua exemplar) và trace.

1. Metric cảnh báo "error rate route=/api/orders > 2%"
2. Click exemplar → mở trace có lỗi trong Jaeger/Tempo
3. Trên span lỗi, lấy trace_id
4. Paste vào log UI → thấy toàn bộ log liên quan (có trace_id field)
5. Đọc log, tìm nguyên nhân gốc

Ba bước nhảy này chỉ hoạt động nếu:

  • Mọi service inject trace_id/span_id vào mọi log line (middleware).
  • Mọi HTTP client/server propagate traceparent.
  • Metric hỗ trợ exemplar (Prometheus 2.26+).

Đầu tư làm đúng context propagation một lần, thu lời dài hạn.

5.1. Baggage

Baggage là cặp key-value đi kèm trace qua mọi service (ví dụ tenant=acme, feature=new-checkout). Hữu ích khi muốn filter log/metric theo tenant mà không phải log lại tenant ở mỗi service. Cẩn trọng: baggage truyền qua header nên không để dữ liệu lớn/nhạy cảm.


6. Chuẩn mở (OpenTelemetry)

Vài năm gần đây, OpenTelemetry (OTel) thống nhất mô hình cho logs, metrics, traces:

  • SDK cho hầu hết ngôn ngữ, auto-instrument nhiều framework.
  • OTLP (gRPC/HTTP) là định dạng truyền chung.
  • Collector: daemon nhận OTLP, xử lý (batch, filter, sample), xuất ra backend (Prometheus, Tempo, Loki, vendor…).

Khuyến nghị: ứng dụng chỉ biết OTel SDK + OTLP; chi tiết đi đâu do collector config quyết. Đổi vendor = đổi config collector, không đổi code.


7. Alert: biến số liệu thành hành động

Metric không alert đi kèm dễ bị lờ. Vài nguyên tắc:

  • Alert theo SLO, không theo “CPU > 80%” — CPU cao là triệu chứng, không phải tác động lên user.
  • Symptom over cause: “latency > 1s” thay vì “pod restart 3 lần”.
  • Burn rate cho SLO: cảnh báo khi error budget đang bị tiêu quá nhanh, chứ không chỉ khi đã hết.
  • Runbook kèm mỗi alert: link tới document nói ai on-call nên làm gì trước.
  • Alert fatigue: nếu trong tuần bạn thấy alert lặp mà “không làm gì” → sửa alert hoặc fix nguyên nhân gốc; đừng bỏ qua.

8. Các anti-pattern

  • Log thay cho metric: parse log để đếm → chậm, tốn; dùng counter.
  • Metric thay cho log: nhồi chi tiết vào label → cardinality nổ.
  • Trace lẻ tẻ: một nửa service có trace, nửa kia không → bạn thấy “black hole”.
  • Không đồng bộ timestamp: clock drift giữa service → trace trông sai thứ tự. Chạy NTP, gắn server.start_time vào trace.
  • Bật DEBUG toàn hệ thống: log nổ, chi phí nổ, tín hiệu tốt chìm trong nhiễu.
  • Không audit chi phí observability: đến khi bill SaaS tăng 3 lần mới ngạc nhiên. Đặt budget sớm.

9. Tóm tắt

  • Logs kể chi tiết từng sự kiện; structured JSON + trace_id + không log PII.
  • Metrics đếm/đo tập hợp; RED/USE + label cardinality thấp + có SLO.
  • Traces vẽ hành trình; sample đủ, instrument đủ, attributes theo OTel.
  • Tương quan qua trace_id và exemplar là công cụ gỡ lỗi production mạnh nhất.
  • Chuẩn mở (OTel) giảm lock-in, dễ đổi backend.
  • Alert theo tác động người dùng, gắn runbook, cắt alert nhiễu.

Observability không phải là “bật tool rồi xem”. Đó là thiết kế vào kiến trúc: chọn tên metric, quy ước log, cách propagate context, ngân sách chi phí. Làm đúng một lần, mỗi lần incident tiếp theo bạn đi từ “có gì đó hỏng” tới “đây là nguyên nhân” nhanh hơn hàng chục phút.