Correlation ID đi qua hai service: nối log đúng cách, đừng nhầm với tracing Trung cấp
Thời gian đọc ước tính: 6 phút
Vấn đề
Phần tiêu đề “Vấn đề”Một request tạo đơn đi qua API rồi gọi service phí. Khi có hàng nghìn request đồng thời, các dòng log của một request nằm lẫn giữa log người khác. Timestamp gần nhau không chứng minh cùng một request, và orderId chưa chắc đã có lúc request bắt đầu.
Correlation ID là định danh gắn với một luồng xử lý để tra các log liên quan. API tạo ID, đưa vào log, gửi qua header khi gọi downstream; downstream ghi lại ID nhận được. Đây là output thật của demo trong repo (một request, hai service, cùng một ID):
{"event":"api.start","requestId":"a0638d3f-f7ea-4ef0-87a6-15734e1f3ad0"}{"event":"downstream.received","requestId":"a0638d3f-f7ea-4ef0-87a6-15734e1f3ad0"}{"event":"api.after-downstream","requestId":"a0638d3f-f7ea-4ef0-87a6-15734e1f3ad0"}Một câu requestId:"a0638d3f..." trong log aggregator ra đủ ba dòng, kể cả khi log nằm ở hai service.
Chọn loại ID nào
Phần tiêu đề “Chọn loại ID nào”x-request-id |
traceparent |
baggage |
|
|---|---|---|---|
| Mang gì | Một ID để tìm log | trace-id + parent-id + flags |
Cặp key-value nghiệp vụ |
| Chuẩn | Quy ước, không phải chuẩn | W3C Trace Context (Recommendation 2021) | W3C Baggage (Candidate Recommendation) |
| Ai tạo | Ingress/gateway | Tracing SDK (OpenTelemetry) | Ứng dụng |
| Cho biết cây span, latency | Không | Có, kèm span | Không |
| Dùng khi | Chưa có tracing | Đã có tracing | Cần truyền ngữ cảnh như tenant, tier |
Quy tắc: đã chạy OpenTelemetry thì dùng traceparent và đưa trace_id vào log, đừng hand-roll thêm header. x-request-id chỉ còn lý do tồn tại ở ingress, như ID trả cho client để support tra cứu.
Định dạng traceparent (W3C): bốn field cách nhau bằng dấu gạch ngang.
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 │ └──── trace-id (32 hex) ───────┘ └─ parent-id ──┘ └ flags version (00) (16 hex) (01 = sampled)trace-idvàparent-idtoàn số 0 là không hợp lệ.tracestateđi kèm để mang dữ liệu riêng của từng vendor tracing.baggagecó thể chứa dữ liệu nhận dạng người dùng và đi sang mọi hệ thống phía sau; không đưa dữ liệu nhạy cảm vào và lọc ở ranh giới tin cậy.
Khái niệm cốt lõi
Phần tiêu đề “Khái niệm cốt lõi”AsyncLocalStorage giữ store gắn với chuỗi bất đồng bộ trong một process; hàm log() đọc requestId mà không cần truyền tham số. Store đó không tự đi qua HTTP, broker hay service khác: code gọi HTTP phải đặt header, bên nhận đọc và gắn vào context của mình. Chi tiết cơ chế xem AsyncLocalStorage.
requestRequest vào API. API tạo hoặc xác thực correlation ID ở ranh giới tin cậy.
Header từ client là input không đáng tin: có thể rất dài, chứa newline, PII hoặc trùng ID request khác. Edge tạo ID mới, hoặc validate theo regex và giới hạn độ dài; ID của client, nếu cần, lưu ở field riêng.
Propagation qua Kafka
Phần tiêu đề “Propagation qua Kafka”Message queue không có “request” HTTP, nên ID phải đi trong record header; consumer đọc lại và khôi phục context.
// producerawait producer.send({ topic: 'orders', messages: [{ key: orderId, value: payload, headers: { traceparent: currentTraceparent(), 'x-request-id': requestId },}] });
// consumerconst requestId = message.headers?.['x-request-id']?.toString();context.run({ requestId }, () => handle(message)); // log trong handler có IDVới OpenTelemetry, các instrumentation HTTP và Kafka client thường inject/extract traceparent tự động; kiểm tra bằng cách chụp header outbound trong integration test.
Khi nào dùng
Phần tiêu đề “Khi nào dùng”| Dùng | Không đủ |
|---|---|
| Ghép log của một request qua nhiều service | Cần latency từng hop: dùng trace |
| Support tra cứu bằng ID trả trong response | Cần theo dõi cả workflow nhiều request: thêm business ID hoặc baggage |
| Hệ thống chưa có tracing | Dùng như idempotency key hoặc quyền truy cập: sai mục đích |
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Chạy demo từ gốc repo (hai server loopback, không cần Docker):
npx --prefix examples ts-node examples/src/correlation-id/correlation-demo.tsThử xóa header trong fetch: log downstream mất ID. Thử bỏ context.run: log API mất ID. Hai cơ chế độc lập. Demo chỉ chứng minh một HTTP hop; chưa có retry, broker hay OpenTelemetry.
Checklist production
Phần tiêu đề “Checklist production”- Ingress tạo ID mới, hoặc validate ID nhận được (regex, tối đa khoảng 64 ký tự, không newline).
- Echo ID trong response header để client và support dùng.
- Dùng một tên field log thống nhất (
request_id,trace_id) ở mọi service. - Wrap HTTP client và Kafka producer để forward header; consumer khôi phục context.
- Không dùng biến global cho ID; dùng
AsyncLocalStorage. - Đã có OpenTelemetry thì ưu tiên
traceparent, log kèmtrace_idvàspan_id. - Test hai request song song và test downstream timeout vẫn giữ ID trong log lỗi.
Trade-offs
Phần tiêu đề “Trade-offs”Có ích cho
- Ghép log của một lần xử lý qua các hop mà không đoán theo timestamp.
AsyncLocalStoragebỏ việc truyền ID qua mọi chữ ký hàm.- Header tường minh dễ test ở ranh giới network.
Giới hạn và chi phí
- Một service quên forward là đứt chuỗi.
- Request ID không có span, latency hay quan hệ cha-con.
- Log vẫn cần retention, masking và kiểm soát truy cập.
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) Xóa x-request-id khỏi fetch thì log nào mất ID? (2) Field parent-id của traceparent dài bao nhiêu ký tự hex? (3) Message qua Kafka mang ID bằng cách nào?