1. 05:59 06/08/2026
Khi ứng dụng còn nhỏ, nhiều đội chỉ cần vài dòng console.log hoặc print là đủ. Nhưng khi hệ thống có nhiều API, background job, queue, worker, cron, bên thứ ba và nhiều môi trường deploy, log kiểu “có gì in nấy” rất nhanh trở thành một đống chữ khó tìm, khó hiểu và khó dùng khi sự cố xảy ra.
Logging tốt không có nghĩa là ghi thật nhiều. Logging tốt là ghi đúng thông tin, đúng định dạng, đúng mức độ và đủ ngữ cảnh để người sau có thể lần ra chuyện gì đã xảy ra.
1. Log có cấu trúc thay vì log câu văn tự do
Thay vì ghi:
“User 123 checkout failed because payment timeout”
Hãy ưu tiên log dạng có cấu trúc, ví dụ theo JSON hoặc key-value:
level=error event=checkout_failed user_id=123 order_id=789 reason=payment_timeout provider=stripe request_id=abc-xyz
Lợi ích là hệ thống log có thể lọc theo user_id, order_id, event, provider hoặc request_id. Khi cần điều tra một lỗi thanh toán, bạn không phải đoán từ khóa trong một biển text.
Các trường nên có trong nhiều log:
- timestamp
- level: debug, info, warn, error
- service hoặc module
- event: tên sự kiện ngắn gọn, ổn định
- request_id hoặc trace_id
- user_id nếu có và nếu được phép ghi
- resource_id liên quan: order_id, invoice_id, file_id...
- error_code hoặc reason
2. Dùng request_id để nối các mảnh rời rạc
Một request có thể đi qua frontend, API gateway, backend, database, cache, queue và worker. Nếu mỗi nơi ghi log một kiểu, bạn rất khó biết các dòng log có liên quan với nhau hay không.
Giải pháp thực tế là tạo hoặc nhận một request_id ở điểm vào hệ thống, sau đó truyền nó qua các lớp xử lý. Mọi log trong cùng một luồng nên kèm request_id.
Ví dụ:
- API nhận yêu cầu tạo đơn hàng: request_id=req_01
- Backend validate giỏ hàng: request_id=req_01
- Gọi cổng thanh toán: request_id=req_01
- Đẩy job gửi email: request_id=req_01
- Worker gửi email thành công: request_id=req_01
Khi khách báo “tôi bấm thanh toán nhưng không nhận email”, bạn có thể tìm theo request_id hoặc order_id để thấy toàn bộ hành trình.
3. Chọn đúng level log
Một lỗi phổ biến là cái gì cũng ghi error, hoặc ngược lại lỗi nghiêm trọng chỉ ghi info.
Có thể tham khảo cách phân loại sau:
- debug: thông tin chi tiết phục vụ lập trình, thường tắt ở production hoặc bật có chọn lọc.
- info: sự kiện bình thường nhưng có giá trị theo dõi, như user đăng nhập, đơn hàng được tạo, job hoàn tất.
- warn: bất thường nhưng hệ thống vẫn xử lý được, như gọi API bên thứ ba lần đầu bị timeout nhưng retry thành công.
- error: thao tác thất bại hoặc cần điều tra, như không tạo được đơn, không trừ được tồn kho, job thất bại sau nhiều lần retry.
Đừng dùng error cho mọi tình huống người dùng nhập sai mật khẩu hoặc validation fail thông thường. Những việc đó có thể là info hoặc warn tùy ngữ cảnh, nếu không dashboard lỗi sẽ bị nhiễu.
4. Không ghi dữ liệu nhạy cảm
Log thường được nhiều người trong đội truy cập hơn database thật, và có thể được gửi sang công cụ bên ngoài. Vì vậy cần tránh ghi:
- mật khẩu, token, refresh token, API key
- số thẻ, mã bảo mật, thông tin thanh toán nhạy cảm
- mã OTP
- cookie hoặc header Authorization đầy đủ
- dữ liệu cá nhân không cần thiết
Nếu cần debug, hãy che bớt:
- email: n***@example.com
- phone: ******789
- token: chỉ ghi 6 ký tự đầu/cuối nếu thật sự cần
Một nguyên tắc đơn giản: log nên giúp hiểu hành vi hệ thống, không nên trở thành bản sao dữ liệu người dùng.
5. Đặt tên event ổn định
Nếu hôm nay ghi event=create_order_success, ngày mai đổi thành order_created, tuần sau lại là checkout_ok thì việc thống kê và tìm kiếm sẽ rất mệt.
Nên thống nhất cách đặt tên, ví dụ:
- order_created
- order_create_failed
- payment_authorized
- payment_authorize_failed
- email_send_failed
- file_upload_rejected
Tên event nên ngắn, rõ, dùng được lâu dài. Phần chi tiết để trong các field như reason, error_code, provider, attempt.
6. Log ở ranh giới quan trọng
Không cần log từng dòng xử lý nhỏ, nhưng nên log ở các điểm có ý nghĩa:
- request bắt đầu và kết thúc, đặc biệt với API quan trọng
- gọi dịch vụ bên thứ ba
- ghi dữ liệu quan trọng vào database
- enqueue/dequeue background job
- retry và kết quả retry
- lỗi bị bắt và xử lý
- quyết định nghiệp vụ quan trọng: từ chối thanh toán, khóa tài khoản, hủy đơn
Ví dụ với API tạo đơn hàng, các log hữu ích có thể là:
- checkout_started
- cart_validated
- inventory_reserved
- payment_failed hoặc payment_authorized
- order_created
- checkout_failed kèm reason
Nhìn vào chuỗi này, bạn biết request chết ở bước nào.
7. Đừng chỉ log lỗi, hãy log cả thời gian xử lý
Nhiều sự cố không phải “hỏng hẳn” mà là “chậm bất thường”. Nếu log có duration_ms cho các bước quan trọng, bạn sẽ dễ nhận ra bottleneck hơn.
Ví dụ:
event=payment_provider_called provider=abc duration_ms=2300 status=timeout
event=search_completed duration_ms=850 result_count=20
Các field như duration_ms, attempt, queue_wait_ms, db_query_ms rất hữu ích khi điều tra hiệu năng.
8. Thiết lập retention và chi phí
Log càng nhiều càng tốn lưu trữ, tốn tiền và tốn công tìm kiếm. Vì vậy cần có chính sách:
- log debug giữ ngắn hoặc chỉ bật khi cần
- log production quan trọng giữ lâu hơn
- log truy cập dung lượng lớn có thể tách riêng
- loại bỏ các log lặp vô ích trong vòng lặp hoặc batch job
Một job xử lý 100.000 bản ghi không nên ghi một dòng info cho từng bản ghi nếu không cần. Có thể ghi tổng kết: processed_count, failed_count, duration_ms và chỉ ghi chi tiết cho bản ghi lỗi.
9. Checklist áp dụng nhanh
Nếu đội của bạn muốn cải thiện logging ngay tuần này, có thể bắt đầu bằng các việc sau:
- Chuẩn hóa format log dạng có cấu trúc.
- Thêm request_id hoặc trace_id cho mọi request.
- Thống nhất danh sách level và cách dùng.
- Rà soát để không ghi token, mật khẩu, OTP, dữ liệu nhạy cảm.
- Đặt tên event ổn định cho các luồng chính.
- Thêm duration_ms cho API, job và lời gọi bên thứ ba.
- Giảm log nhiễu, đặc biệt trong vòng lặp và job lớn.
- Viết tài liệu ngắn: khi nào log info, warn, error; field nào là bắt buộc.
Kết luận
Logging không phải phần trang trí thêm sau khi code xong. Nó là công cụ vận hành và debug rất quan trọng. Một hệ thống có log tốt giúp đội phát triển trả lời nhanh các câu hỏi: lỗi xảy ra ở đâu, ảnh hưởng đến ai, bắt đầu từ khi nào, có lặp lại không và bước tiếp theo nên kiểm tra là gì.
Ghi log ít nhưng đúng ngữ cảnh thường có giá trị hơn ghi thật nhiều nhưng không thể lọc, không thể nối chuỗi và không ai dám đọc.