Best Practices Backend Developer Khi Viết Log Có Cấu Trúc

Bạn đã bao giờ mở file log và chỉ thấy một đống dòng kiểu ERROR: something went wrong chưa? Đội ngũ chúng tôi từng rơi vào cảnh đó đến mức thuộc lòng từng câu mơ hồ. Có một lần, cả nhóm ngồi hàng giờ chỉ để lần ra một lỗi mà log chỉ nói được rằng “có gì đó sai sai”. Từ hôm ấy, chúng tôi hiểu log không phải thứ viết cho có. Với backend developer, log là công cụ sống còn, và cách bạn viết log quyết định việc tìm lỗi nhanh hay chậm.

Viết Log Tự Do Làm Việc Debug Hệ Thống Khó Khăn Ra Sao

Hồi mới vào nghề, chúng tôi cũng viết log theo kiểu nghĩ gì viết nấy. Gặp lỗi thì ghi Console.WriteLine("Lỗi rồi"), hoặc để nguyên định dạng mặc định của framework. Lúc đó trông vẫn ổn, vì chỉ cần đọc vài dòng là biết chương trình chạy đến đâu. Nhưng khi hệ thống lớn dần và số dòng log lên đến hàng nghìn mỗi giờ, mớ hỗn độn ấy bắt đầu phản tác dụng.

Hãy hình dung bạn đang tìm một cây kim trong đống rơm. Log tự do giống đống rơm không có nhãn. Bạn phải đọc từng dòng, đoán ý nghĩa, rồi tự hỏi dòng này do module nào ghi ra. Có lần, đội chúng tôi mất gần ba tiếng chỉ để phát hiện một biến bị null. Log có ghi lại, nhưng không có tên request, không có ID người dùng, và stack trace thì cụt lủn. Chúng tôi phải thêm log tạm, deploy lại rồi chờ lỗi tái diễn. Tốn thời gian vô cùng.

Vấn đề không chỉ nằm ở khâu tìm kiếm. Khi log không có cấu trúc, các công cụ phân tích gần như vô dụng. Bạn không lọc được theo mức độ nghiêm trọng, không nhóm được theo request, cũng không vẽ được biểu đồ tần suất lỗi. Tất cả những gì còn lại chỉ là một chuỗi ký tự vô hồn. Nhiều dev không hình thành thói quen này ngay từ đầu dự án, họ nghĩ “để sau, khi nào cần thì thêm”. Đến lúc cần thì hệ thống đã phức tạp, và việc bổ sung log có cấu trúc khi đó tốn công gấp bội.

Một dự án từng phải dành hai tuần chỉ để dọn log cho một module nhỏ. Lý do rất đơn giản: không ai đặt quy chuẩn từ đầu, nên mỗi người viết một kiểu. Người dùng dấu gạch ngang, người dùng dấu hai chấm, người chèn cả đoạn văn tự do vào giữa dòng. Kết quả là không công cụ nào đọc nổi. Nếu ngay từ đầu họ dành một buổi để thống nhất định dạng, mọi việc đã đơn giản hơn nhiều.

Vì Sao Nên Viết Log Có Cấu Trúc

Có thể bạn đang thắc mắc log có cấu trúc khác gì log tự do. Khác biệt nằm ở cách ghi và cách xử lý thông tin. Log có cấu trúc được ghi theo một khuôn mẫu cố định, thường là JSON, để máy có thể đọc và hiểu. Log tự do là những dòng chữ chỉ con người mới đọc được, và người đọc thường phải đoán mò.

Lợi ích đầu tiên và rõ rệt nhất là khả năng lọc và tìm kiếm. Khi log có cấu trúc, bạn lấy toàn bộ log của một request bằng một truy vấn đơn giản. Giả sử mỗi dòng đều có trường requestId, bạn chỉ cần tìm requestId=abc123 là ra toàn bộ hành trình của request đó qua các service. Với log tự do, bạn phải đọc từng dòng rồi tự nối chúng lại.

Lợi ích thứ hai là khả năng tích hợp với công cụ giám sát. Các hệ thống như ELK (Elasticsearch, Logstash, Kibana) hay Datadog đều dựa trên log có cấu trúc. Chúng tự nhận diện các trường như level, timestamp, message rồi dựng dashboard trực quan. Bạn thấy ngay số lỗi tăng vọt lúc hai giờ sáng, hoặc biết request nào chậm nhất. Với log tự do, những công cụ này chỉ coi dữ liệu là một chuỗi văn bản thô.

Chúng tôi từng áp dụng log có cấu trúc cho một dịch vụ thanh toán. Trước đó, mỗi khi có sự cố, chúng tôi phải SSH vào server, mở file log và dò bằng mắt, mỗi lần mất ít nhất 30 phút. Sau khi chuyển sang JSON và đưa vào ELK, thời gian trung bình để xác định nguyên nhân giảm xuống dưới 5 phút. Chúng tôi chỉ cần lọc theo mã lỗi, tên service và khoảng thời gian, mọi thứ hiện ra rõ ràng.

Một lợi ích ít người nhắc tới là khả năng tái sử dụng log cho mục đích khác. Log có cấu trúc giúp đo hiệu năng, theo dõi hành vi người dùng hay phát hiện bất thường. Ví dụ, nếu mỗi request đều ghi trường duration, bạn tính thời gian phản hồi trung bình của từng API mà không cần viết thêm công cụ nào. Log tự do thì không làm được điều đó, vì không có chuẩn nào để trích xuất dữ liệu.

Cách Áp Dụng Log Có Cấu Trúc Trong Dự Án

Đến phần thực hành. Nếu bạn đã đọc tới đây, chắc hẳn đã thấy lợi ích. Vậy làm sao đưa vào dự án của mình? Dưới đây là những bước đội ngũ chúng tôi đúc kết qua nhiều lần triển khai.

Chọn Định Dạng JSON Cho Log

Bước đầu tiên và quan trọng nhất là chọn định dạng. Chúng tôi khuyên dùng JSON, vì đây là chuẩn mà hầu hết thư viện log và công cụ phân tích hỗ trợ. JSON vẫn đủ dễ đọc với con người và dễ mở rộng khi cần thêm trường mới.

Thay vì viết:

2023-10-26 14:30:00 ERROR: Đã có lỗi xảy ra khi xử lý đơn hàng #12345

Bạn sẽ viết:

{
  "timestamp": "2023-10-26T14:30:00Z",
  "level": "ERROR",
  "message": "Lỗi khi xử lý đơn hàng",
  "orderId": "12345",
  "userId": "67890",
  "service": "order-service"
}

Log JSON dài hơn một chút, nhưng mỗi trường là một chiều để truy vết. Bạn lọc được theo orderId, theo level, theo service. Trong .NET, nếu dùng Serilog thì chuyển sang JSON chỉ cần cấu hình vài dòng. Còn nếu tự viết log, hãy tạo một helper để đảm bảo định dạng luôn nhất quán.

Đừng ghi quá nhiều trường. Chỉ giữ những gì thật sự cần cho việc debug: thời gian, mức độ, thông điệp và các ID ngữ cảnh như request ID, user ID hay order ID. Ghi thừa sẽ làm log phình to, tốn dung lượng lưu trữ và khó đọc. Trước khi thêm một trường mới, hãy tự hỏi nó có giúp bạn trả lời được câu hỏi khi sự cố xảy ra không.

Ghi Rõ Mức Độ Nghiêm Trọng Và Ngữ Cảnh

Một log tốt không chỉ nói “có lỗi”, mà còn cho biết lỗi nghiêm trọng đến đâu và xảy ra trong bối cảnh nào. Vì vậy, đội ngũ chúng tôi luôn ghi rõ level và context cho từng sự kiện.

Mức độ thường gồm năm cấp: DEBUG, INFO, WARN, ERROR và FATAL. DEBUG ghi chi tiết quá trình xử lý và thường chỉ bật khi cần điều tra. INFO ghi các sự kiện bình thường như request đến và request hoàn thành. WARN báo những điều bất thường nhưng chưa gây lỗi, chẳng hạn một thao tác chậm hơn mức thường gặp. ERROR ghi lại lỗi khiến một chức năng không chạy được. FATAL dành cho lỗi làm sập cả hệ thống.

Nhiều dev chỉ dùng ERROR cho mọi thứ. Khi hệ thống giám sát báo có 1000 lỗi trong một giờ, mà 990 trong số đó chỉ là chuyện vặt, bạn sẽ không còn biết đâu là vấn đề thật. Hãy dành thời gian định nghĩa rõ ý nghĩa từng mức trong dự án và tuân thủ nghiêm túc.

Ngữ cảnh cũng quan trọng không kém. Một log chỉ ghi "Lỗi khi truy cập database" thì gần như chẳng giúp được gì. Nhưng nếu thêm exceptionType, tên bảng hoặc thao tác liên quan (đã che thông tin nhạy cảm), bạn sẽ hình dung vấn đề ngay. Chúng tôi thường đính kèm stack trace đầy đủ, vì nó chỉ đúng dòng code gây lỗi. Trong .NET, Exception.ToString() cho bạn toàn bộ thông tin cần thiết.

Một mẹo nhỏ đội ngũ chúng tôi hay dùng: ghi INFO cho mỗi request đến và đi, kèm thời gian xử lý. Nhờ vậy, khi có request chậm, bạn tìm thấy ngay trong log. Khi cần xem chi tiết bên trong, hãy bật DEBUG riêng cho request đó bằng cách lọc theo request ID. Cách này giữ log gọn nhưng vẫn đủ thông tin khi cần.

Dùng Correlation ID Để Truy Vết Xuyên Service

Correlation ID là một mã duy nhất gắn cho mỗi request và được truyền qua mọi service mà request đi qua. Khi mỗi dòng log đều mang correlation ID, bạn theo dõi được toàn bộ hành trình của một request, từ API gateway đến database. Trong kiến trúc microservices, đây là thứ thay đổi hẳn cách debug. Nếu chưa dùng, hãy bắt đầu sớm, vì càng để lâu càng khó thêm vào.

Viết log có cấu trúc không phải việc quá khó. Chỉ cần dành chút thời gian thiết lập từ đầu, bạn sẽ tiết kiệm rất nhiều công sức về sau. Khi review code, chúng tôi cũng thường soi kỹ các đoạn log thiếu nhất quán, vì đó là dấu hiệu sớm của nợ kỹ thuật. Bạn có thể tham khảo thêm best practices cho backend developer khi review code để hiểu cách làm việc này. Ngoài ra, top 7 lưu ý về debug và tối ưu mã cho dev .NET sẽ cho bạn thêm góc nhìn về cách log hỗ trợ debug hằng ngày.

Kết Luận

Viết log có cấu trúc không chỉ là một kỹ thuật, mà còn là một thói quen cần rèn luyện. Nó giúp backend developer theo dõi và xử lý sự cố nhanh hơn rất nhiều. Khi gặp lỗi khó chịu, thay vì mò mẫm trong đống log tự do, bạn chỉ cần vài thao tác là tìm ra nguyên nhân, và cảm giác đó thật sự dễ chịu.

Bạn có thể bắt đầu ngay hôm nay: chọn JSON làm định dạng, ghi rõ mức độ và ngữ cảnh, rồi thêm correlation ID. Chỉ với vài thay đổi nhỏ, khác biệt sẽ rất lớn. Nếu muốn đào sâu hơn về cách xử lý lỗi trong ứng dụng, hãy đọc bài xử lý exception đúng cách thay vì try-catch tràn lan, vì log và exception vốn luôn đi cùng nhau. Chúc bạn thành công!