+9

🧩🧠 Logs, Metrics, Traces: ba giác quan của Microservices - Microservice Architecture P9

Logs, Metrics, Traces: ba giác quan của Microservices

1. Đơn hàng chậm 5 giây và không ai biết tại sao

Đó là một buổi sáng thứ Hai bình thường. Một kỹ sư nhận được ticket từ bộ phận chăm sóc khách hàng: "Tại sao một số đơn hàng bị xử lý chậm tới 5 giây? Khách hàng đang phàn nàn."

Kỹ sư mở Kibana lên, tìm kiếm log của service order-service. Log sạch bóng. Không có exception, không có warning, mọi thứ đều trả về 200 OK. Chuyển sang payment-service, tình trạng tương tự. Rồi inventory-service, rồi notification-service. Mỗi service đều chạy tốt theo đúng nghĩa của nó — từng cái một.

Vậy 5 giây đó đã biến mất vào đâu?

Đây là cái bẫy kinh điển của Microservices: mỗi service đều xanh, nhưng toàn bộ hệ thống lại đỏ. Khi một request phải đi qua 5-6 service khác nhau mới hoàn thành một nghiệp vụ, việc xem log của từng service riêng lẻ sẽ không bao giờ cho bạn thấy bức tranh toàn cảnh. Bạn đang cố ghép một bức tranh 5000 mảnh trong bóng tối.

Đó là lý do tại sao Observability không phải là tính năng tùy chọn trong Microservices. Đó là điều kiện bắt buộc để vận hành được.


2. Niềm tin phổ biến: "Gom log về một chỗ là đủ rồi"

Khi bắt đầu tiếp cận Observability, hầu hết các team đều làm theo một con đường rất quen thuộc: cài ELK Stack (Elasticsearch, Logstash, Kibana), cấu hình tất cả container ship log về, rồi tự thưởng cho mình cảm giác "chúng ta đã có Observability rồi."

Lý luận này có vẻ rất hợp lý. Log là thứ duy nhất bạn có thể đọc trực tiếp, hiểu ngay bằng ngôn ngữ tự nhiên, và nó ghi lại chi tiết những gì đã xảy ra bên trong một service tại một thời điểm cụ thể. Nếu gom tất cả log lại một chỗ thì có thể tìm kiếm tập trung. Logic mà.

Nhưng đây là lúc thực tế va đập vào tư duy đó.


3. Tại sao "chỉ cần log tập trung" không đủ trong thế giới phân tán

Hãy tưởng tượng một request tạo đơn hàng đi qua chuỗi sau:

Client
  → API Gateway        (10ms)
  → Order Service      (80ms)
    → Inventory Service  (3,200ms)  ← đây là thủ phạm
  → Payment Service    (150ms)
  → Notification Service (40ms)

Tổng: ~3,480ms ≈ 3.5 giây

Tổng cộng gần 3.5 giây. Inventory Service là nơi đang gây tắc nghẽn.

Nhưng trong Kibana, log của từng service trông như thế này:

[order-service]     INFO  Order created successfully in 80ms
[inventory-service] INFO  Inventory checked successfully in 3200ms
[payment-service]   INFO  Payment processed successfully in 150ms

Về mặt kỹ thuật, inventory-service không có lỗi. Nó chỉ đang chậm. Không có exception. Không có warning. Nếu bạn tìm "error" hay "exception" trong log, bạn sẽ không tìm thấy gì.

Câu hỏi bạn cần trả lời là: Chính xác request nào của khách hàng nào đã bị chậm 3.5 giây? Và 3.5 giây đó phân bổ như thế nào qua từng service?

Chỉ với log thuần túy, bạn không thể trả lời được. Bởi vì log của từng service không biết gì về nhau. Không có sợi chỉ kết nối chúng lại thành một câu chuyện thống nhất.

Đây là điểm mà ba khái niệm Logs, Metrics, và Traces bắt đầu tách biệt nhau.


4. Ba giác quan, ba câu hỏi khác nhau

Một sai lầm phổ biến là nghĩ rằng ba công cụ này có thể thay thế nhau. Thực ra chúng trả lời ba câu hỏi hoàn toàn khác biệt:

┌────────────────────────────────────────────────────────────────┐
│  Câu hỏi                │  Công cụ  │  Ví dụ                  │
├────────────────────────────────────────────────────────────────┤
│  Chuyện gì xảy ra?      │  Logs     │  "NullPointerException   │
│  (tại service X)        │           │   tại dòng 42"           │
├────────────────────────────────────────────────────────────────┤
│  Hệ thống đang khỏe     │  Metrics  │  "Error rate tăng        │
│  không? (tổng quan)     │           │   từ 0.1% lên 5%"        │
├────────────────────────────────────────────────────────────────┤
│  Request đi đường nào?  │  Traces   │  "Request #xyz mất       │
│  (xuyên các service)    │           │   3.2s ở inventory"      │
└────────────────────────────────────────────────────────────────┘

Ba giác quan này phải hoạt động cùng nhau. Khi Metrics báo động rằng có điều gì đó bất thường, bạn dùng Traces để xác định đường đi của request. Khi Traces chỉ ra service nào đang gây vấn đề, bạn dùng Logs để đào sâu vào chi tiết lỗi bên trong service đó.

Metrics phát hiện vấn đề. Traces khoanh vùng vấn đề. Logs giải thích vấn đề.


5. Metrics: Giác quan phát hiện sớm

Metrics là tập hợp các con số đo lường được thu thập theo thời gian. CPU usage, memory, request per second, error rate, p99 latency — đây là những chỉ số giúp bạn biết hệ thống đang ở trạng thái nào một cách tổng quan.

Điểm mạnh của Metrics nằm ở tính nhẹ nhàng và tần suất thu thập cao. Bạn có thể poll mỗi 15 giây mà không ảnh hưởng đến performance hệ thống. Prometheus thu thập Metrics bằng cách scrape từng service endpoint, Grafana vẽ dashboard real-time, và team vận hành cài alert rule: "Nếu error rate của order-service vượt 1% trong 5 phút, gửi PagerDuty."

Điều này tạo ra khả năng phát hiện vấn đề trước khi khách hàng gọi điện.

Nhưng Metrics không giải thích tại sao. Khi dashboard Grafana báo đỏ p99 latency của order-service tăng lên 5 giây, nó không nói với bạn rằng nguyên nhân là do inventory-service ở phía sau đang quá tải. Bạn cần một giác quan khác.


6. Logs: Giác quan đào sâu chi tiết

Log ghi lại những gì thực sự xảy ra bên trong một service, tại một khoảnh khắc cụ thể. Nhưng để Log phát huy tác dụng trong Microservices, bạn không thể viết log theo kiểu cũ như console.log("Order created").

Structured Logging là yêu cầu bắt buộc. Thay vì log plain text, bạn log dạng JSON có cấu trúc với các field cố định:

{
  "timestamp": "2026-07-10T09:15:32.451Z",
  "level": "INFO",
  "service": "order-service",
  "trace_id": "abc123def456",
  "span_id": "789ghi",
  "user_id": "usr_9812",
  "order_id": "ord_4521",
  "message": "Order created successfully",
  "duration_ms": 82
}

Trường trace_id ở đây không phải là chi tiết phụ — đó là sợi chỉ đỏ kết nối log entry này với toàn bộ hành trình của request qua các service khác. Khi bạn tìm kiếm trace_id: abc123def456 trên toàn bộ hệ thống log, bạn sẽ thấy đúng request đó đã chạm vào service nào, theo thứ tự nào, và mỗi service mất bao nhiêu thời gian.

Đây là lý do tại sao Logs và Traces không độc lập với nhau — chúng phải được thiết kế để hoạt động cùng nhau từ đầu.


7. Traces: Giác quan nhìn xuyên không gian phân tán

Distributed Tracing là công nghệ trả lời câu hỏi mà log thuần túy không thể giải quyết: "Request này đã đi đâu, qua những service nào, và mất bao lâu ở mỗi bước?"

Cơ chế hoạt động dựa trên hai khái niệm cốt lõi:

Trace ID là một định danh duy nhất được sinh ra khi request vào hệ thống lần đầu tiên (thường ở API Gateway), và được truyền đi theo request xuyên suốt toàn bộ chuỗi service cho đến khi hoàn thành. Mọi log entry, mọi hoạt động liên quan đến request này đều được gắn cùng Trace ID.

Span ID đại diện cho một đơn vị công việc cụ thể trong một service. Mỗi service khi xử lý request sẽ tạo ra Span riêng của mình, có timestamp bắt đầu và kết thúc, và có thể lồng nhau (parent-child spans) để biểu diễn cấu trúc gọi lệnh phức tạp.

Trace ID: abc123def456
│
├── Span: API Gateway              [0ms    →   12ms]
│
├── Span: Order Service            [12ms   →   95ms]
│   ├── Span: DB query             [13ms   →   25ms]
│   └── Span: gọi Inventory        [26ms   → 3,226ms]
│         └── Span: Inventory Svc  [26ms   → 3,226ms]
│               └── Span: DB query (FULL TABLE SCAN!) ← THỦ PHẠM
│
├── Span: Payment Service          [3,226ms → 3,376ms]
│
└── Span: Notification Service     [3,376ms → 3,416ms]

Nhìn vào biểu đồ Trace này, chỉ mất vài giây để xác định nguyên nhân: Inventory Service đang tốn 3.2 giây, và bên trong đó, một câu query đang thực hiện full table scan vì thiếu index. Không cần đoán mò. Không cần SSH vào từng server. Không cần đọc hàng nghìn dòng log thủ công.

Đây là lý do OpenTelemetry (tiêu chuẩn mở cho instrumentation) và Jaeger (hệ thống thu thập và hiển thị Trace) đang trở thành bộ đôi tiêu chuẩn ngành.


8. Correlation ID và thách thức truyền dẫn xuyên giao thức

Một câu hỏi thực tế mà nhiều team gặp phải: làm thế nào để Trace ID lan truyền qua các giao thức khác nhau như HTTP, gRPC, và Kafka?

Câu trả lời là Context Propagation — cơ chế nhúng thông tin trace vào header của mỗi cuộc giao tiếp.

HTTP / gRPC:
  Header: traceparent: 00-abc123def456-789ghi-01

Kafka Message Header:
  { "traceparent": "00-abc123def456-xyz789-01" }

Thư viện OpenTelemetry SDK hỗ trợ tự động inject và extract context này cho hầu hết các giao thức phổ biến. Khi service A gọi service B qua HTTP, SDK tự động đính kèm traceparent header. Khi service B nhận request, nó tự động extract context và tiếp tục Trace trong cùng chuỗi đó.

Với Kafka, vấn đề phức tạp hơn một chút vì giao tiếp là bất đồng bộ. Message được produce lúc T1, nhưng được consume lúc T2 có thể cách nhau vài giây hoặc vài phút. OpenTelemetry hỗ trợ kiểu liên kết này thông qua Span Links — cho phép tạo kết nối logic giữa hai Span thuộc hai Trace khác nhau nhưng có quan hệ nhân quả với nhau.

Điều này đảm bảo rằng dù hệ thống của bạn dùng HTTP, gRPC hay event-driven với Kafka, sợi chỉ Trace vẫn không bị đứt giữa chừng.


9. Góc nhìn kỹ sư: Observability không chỉ là vấn đề công cụ

Đây là điểm quan trọng mà nhiều bài viết kỹ thuật bỏ qua: Observability không phải là vấn đề công cụ — đó là vấn đề văn hóa kỹ thuật và thiết kế hệ thống từ đầu.

Nhiều team cài Jaeger sau khi hệ thống đã chạy production được 2 năm rồi mới phát hiện rằng code không có instrumentation. Thư viện tracing không được tích hợp. Log format mỗi service mỗi khác, không có trace_id chung. Kết quả là Jaeger chạy nhưng Trace rỗng tuếch hoặc chỉ thấy một phần nhỏ của chuỗi.

Observability phải được thiết kế song song với service, không phải bổ sung sau.

Điều này có nghĩa là:

  • Mỗi service mới phải được tích hợp OpenTelemetry SDK ngay từ ngày đầu tiên.
  • Mọi log entry phải bắt buộc có trace_idspan_id.
  • Mọi cuộc gọi đi ra ngoài (HTTP, gRPC, message queue) phải tự động propagate context.
  • Mọi background job, batch process, hay async worker đều phải tạo span riêng để không bị mất dấu.

Đây là sự khác biệt giữa team biết cài Jaeger và team thực sự có khả năng debug production trong vài phút.


10. Trade-offs: Observability không miễn phí

Đã đến lúc nói về phần mà nhiều người thường bỏ qua: Observability có chi phí thực sự, và bạn phải đưa ra những quyết định đánh đổi có ý thức.

Về performance: Mỗi Span được tạo ra đều tiêu tốn CPU và memory. Với hệ thống xử lý hàng triệu request mỗi ngày, nếu trace 100% request, bạn sẽ gặp overhead đáng kể — đặc biệt ở các service có latency thấp như cache lookup hay health check endpoint.

Giải pháp là Sampling Strategy. Thay vì trace mọi request, bạn chỉ trace một tỷ lệ nhất định:

Head-based Sampling:
  Quyết định trace hay không ngay khi request vào hệ thống.
  Ưu điểm: đơn giản, overhead thấp.
  Nhược điểm: bỏ sót lỗi hiếm gặp (vì không biết trước request nào sẽ lỗi).

Tail-based Sampling:
  Thu thập toàn bộ trace, nhưng chỉ giữ lại những trace có lỗi hoặc latency cao.
  Ưu điểm: không bỏ sót lỗi.
  Nhược điểm: cần buffer lớn hơn, phức tạp hơn để triển khai.

Về chi phí lưu trữ: Log của hệ thống 50 service chạy 24/7 có thể lên tới hàng terabyte mỗi tháng. Tracing data thậm chí còn nặng hơn vì mỗi Span chứa nhiều metadata. Một team không có retention policy rõ ràng sẽ có Elasticsearch đầy ổ cứng trong vài tuần đầu tiên.

Về PII và bảo mật — failure case nguy hiểm nhất: Đây là sai lầm mà nhiều team vô tình phạm phải. Developer thêm dòng log kiểu INFO: Processing payment for user@email.com, card 4xxx-xxxx-xxxx-1234 vào production. Log này được lưu vào Elasticsearch, được đội DevOps xem trong Kibana, được backup vào S3 trong nhiều tháng. Đây là vi phạm GDPR nghiêm trọng.

Quy tắc bắt buộc: Không bao giờ log PII (email, số điện thoại, thông tin thanh toán, địa chỉ, số chứng minh thư). Nếu cần debug liên quan đến user, chỉ log internal user ID và dùng cơ chế lookup riêng biệt, có kiểm soát truy cập, khi cần thiết.


11. Khi nào mô hình này thất bại?

Ngay cả khi đã triển khai đầy đủ Logs, Metrics, Traces, vẫn có những tình huống khiến Observability của bạn không phát huy tác dụng:

Sampling quá thưa: Nếu bạn chỉ sample 0.1% request để tiết kiệm chi phí, một lỗi xảy ra với tần suất 0.05% sẽ gần như không bao giờ xuất hiện trong Trace. Bạn có Jaeger nhưng không tái hiện được lỗi production.

Service mesh nhưng thiếu application-level instrumentation: Istio hay Linkerd có thể tự động tạo network-level trace (service A gọi service B mất bao nhiêu ms ở tầng mạng). Nhưng chúng không thể biết bên trong service A đó, query database nào tốn 3 giây, hay business logic nào đang chạy vòng lặp. Bạn cần instrumentation ở tầng application code để có full-context trace.

Clock drift giữa các server: Khi các server có system time lệch nhau vài trăm millisecond (do NTP không đồng bộ tốt), thứ tự log entries trong Elasticsearch sẽ bị sai. Bạn sẽ thấy service B ghi log trước service A dù request đi từ A sang B. Debug với log bị lệch clock là cực kỳ khó chịu và dễ dẫn đến kết luận sai.


12. Tổng kết: Hệ thống mù và hệ thống sáng

Quay lại câu chuyện ban đầu. Nếu hệ thống đơn hàng đó được trang bị đủ ba giác quan, kịch bản sẽ hoàn toàn khác:

  1. Metrics của Grafana bắt alert: p99 latency của order-service vượt ngưỡng 3 giây trong 5 phút liên tiếp, PagerDuty gửi thông báo đến kỹ sư on-call ngay lập tức.
  2. Kỹ sư mở Jaeger, lọc những Trace nào của order-service có duration > 3 giây. Ngay lập tức thấy rằng Span call-inventory chiếm hơn 90% thời gian.
  3. Click vào Trace đó, thấy bên trong inventory-service có một Span db-query với duration 3.1 giây. Log entry liên kết với Span đó cho thấy câu query đang thực hiện full table scan vì thiếu index.
  4. Thêm index, deploy, kiểm tra lại Trace — vấn đề biến mất.

Toàn bộ quá trình này có thể hoàn thành trong 15-20 phút. So với việc ngồi đọc log thủ công từng service một, không có correlation nào, có thể mất cả ngày mà chưa tìm ra nguyên nhân.

Đó là sự khác biệt giữa hệ thống mù và hệ thống có đủ giác quan để tự nhận biết trạng thái của mình.

Ba bài học cốt lõi:

  1. Logs, Metrics, Traces là ba công cụ bổ trợ nhau, không thay thế nhau. Thiếu bất kỳ cái nào, bạn sẽ có điểm mù trong hệ thống phân tán.
  2. Distributed Tracing không phải là monitoring — đó là khả năng tái hiện lại hành trình của một request. Không có nó, bạn không thể debug lỗi phân tán một cách có hệ thống.
  3. Observability phải được thiết kế từ đầu, không phải cài thêm sau. Instrumentation, structured log format, context propagation — tất cả phải là yêu cầu thiết kế, không phải afterthought.

Lời kết: Khi nhìn thấy rõ rồi, câu hỏi tiếp theo là gì?

Khi bạn đã có khả năng quan sát toàn bộ hành vi của hệ thống — biết service nào gọi service nào, request nào đến từ đâu, mỗi bước tốn bao nhiêu thời gian — một câu hỏi bảo mật lớn bắt đầu xuất hiện:

Nếu kẻ tấn công xâm nhập được vào nội mạng của bạn và giả mạo một internal service, hệ thống của bạn có nhận ra không? Khi mọi service tin tưởng nhau mặc định vì chúng cùng nằm trong một cluster, một service bị compromise có thể tự do gọi sang bất kỳ service nào khác mà không ai phát hiện.

Đây là bài toán mà Zero Trust Architecture được thiết kế để giải quyết — và đó cũng là chủ đề của Episode tiếp theo trong series.


Góc nhìn thêm: Logs, metrics, traces không phải ba tool khác nhau, mà là ba góc nhìn của cùng một sự thật

Khi hệ thống phân tán gặp sự cố, rất nhiều team rơi vào tình trạng "có rất nhiều dữ liệu quan sát nhưng vẫn không biết chuyện gì đang xảy ra". Lý do là họ thu thập riêng lẻ:

  • log để xem lỗi
  • metrics để xem dashboard
  • traces để xem request chain

Nhưng không nối chúng lại thành một câu chuyện vận hành.

Observability trưởng thành hơn là khả năng chuyển ngữ linh hoạt giữa ba lớp:

  • thấy p99 tăng ở metrics
  • nhảy sang trace để thấy nghẽn ở dependency nào
  • từ trace khoanh tiếp sang log của instance hoặc consumer cụ thể

Nếu ba lớp này không liên kết được, đội ngũ vẫn đang debug như thời monolith nhưng trên một hệ thống đắt đỏ hơn nhiều.

Nhìn thêm: Điều nhiều team đánh giá thấp: chi phí observability

Tracing đầy đủ, log giữ lâu, metrics độ phân giải cao đều có giá. Không chỉ là tiền hạ tầng lưu trữ, mà còn là chi phí cognitive load:

  • quá nhiều log làm nhiễu tín hiệu
  • cardinality metrics quá cao khiến monitoring đắt và khó dùng
  • trace thu 100% làm dữ liệu bùng nổ

Vì thế observability tốt không phải thu càng nhiều càng tốt. Nó là thiết kế xem signal nào thực sự giúp trả lời câu hỏi production quan trọng nhất của mình.

💡 Về TechCraft

TechCraft được xây dựng với mong muốn giúp Developer phát triển tư duy hệ thống thông qua những nội dung có chiều sâu về Backend Engineering, Distributed Systems và Production Architecture.

Tại đây, bạn sẽ không chỉ học cách một kỹ thuật hoạt động, mà còn hiểu vì sao các hệ thống lớn lại được thiết kế theo cách đó.

Nếu muốn tiếp tục đào sâu hơn, bạn có thể khám phá Dev Insider — nơi tập trung các series chuyên sâu dành cho Backend Developer.

🚀 Dev Insider https://www.patreon.com/techcraft_official/posts/vi-sao-dev-ra-161163881?collection=2220113

📘 Facebook https://www.facebook.com/techcraft.official

🎥 YouTube https://www.youtube.com/@techcraft.official

🎵 TikTok https://www.tiktok.com/@techcraft.official

Từ Developer biết code -> Engineer hiểu hệ thống.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí