Phần lớn lời khuyên về logging mặc định bug là của bạn. Với giả định đó, một dòng log là mẩu giấy nhắc cho chính mình: đủ để nhớ lại đoạn code mình viết và lúc nào mở ra đọc lại cũng được.
Việc tích hợp phá vỡ giả định đó. Khi một booking hành xử lạ, cái lỗi thường nằm trong một hệ mình không có quyền truy cập — platform của supplier, middleware của đối tác, một thứ nằm sau một endpoint và một file PDF. Mình không đọc được code đó, cũng không thêm được một dòng print vào đó. Thứ mình làm được là chìa ra cái đã đi qua ranh giới, và để nó kết luận được điều gì, nó phải thuyết phục được một người đọc không làm ở đây và mặc định rằng lỗi nằm ở phía mình.
Nên mục tiêu thiết kế đổi. Log không phải công cụ nhắc trí nhớ của mình. Nó là bằng chứng, và thỉnh thoảng sẽ được đính vào một email gửi sang công ty khác. Mọi thứ bên dưới đều suy ra từ câu đó. (Còn đọc mấy bản ghi này thế nào lại là một môn khác, nằm ở một bài khác; bài này nói về việc trong bản ghi phải có gì.)
Bắt đúng cái đã bay đi, không phải cái mình định gửi
Quy tắc đầu tiên cũng là chỗ nhiều bản triển khai làm sai nhất: ghi lại request đúng như lúc nó đi ra, không phải cái object mình vừa dựng.
Hai thứ đó khác nhau, và khác đúng ở chỗ quan trọng. HTTP client thêm header, interceptor nhét token vào, serialiser bỏ key và format ngày theo ý nó, cái wrapper retry viết lại thứ gì đó ở lần thử thứ hai. Log cái object mình dựng là log ý định của mình — một bản ghi trung thực về thứ mình tin là mình đã gửi, và vô giá trị trong một cuộc tranh luận về thứ bên kia đã nhận.
Nên điểm chụp phải là điểm cuối cùng trước khi byte rời đi: body đã serialise, bộ header cuối cùng, URL đã resolve. Và nó phải nằm ở chỗ không đoạn code nào phía sau sửa được nữa. Một bản ghi mà middleware vẫn còn chạm vào được sau khi đã ghi thì không phải bằng chứng, vì câu hỏi đầu tiên của một người đọc cẩn thận là: cái log này phản ánh cú gọi, hay phản ánh ý kiến của code về cú gọi?
Giữ body kể cả khi status là 200
Có supplier trả HTTP 200 kèm lỗi nằm trong body. Chuyện đó bình thường trong nghề này, và nó phá tan cái mẹo logging phổ biến nhất: lỗi thì giữ response, thành công thì bỏ. Theo chính sách đó, đúng cái bản ghi mình cần — cú gọi trông như thành công mà bên trong là một lời từ chối — lại là cái đã vứt đi, và mấy hôm sau mới lộ ra khi có người hỏi vì sao booking không bao giờ confirm.
Status code là ý kiến của tầng transport. Trong logging tích hợp, nó là một trường trong bản ghi, không phải bộ lọc quyết định bản ghi có tồn tại hay không. Giữ response body, ở mọi cú gọi, với retention nào mình chịu được — và nếu buộc phải sample thì sample theo lưu lượng, đừng bao giờ sample theo kết quả.
Bản ghi phải bắn lại được
Tính chất khiến một dòng log thôi là lời tranh cãi chính là khả năng tái hiện. Provider log bên mình được giữ đủ đầy để một cú gọi lỗi bắn lại được đúng như request đã gửi, dựng lại thành một câu curl.
Tác dụng lên cuộc trao đổi lớn hơn nhiều so với công bỏ ra. "Bọn mình tin là đã gửi một request hợp lệ" mở ra một cuộc thảo luận về niềm tin. "Đây là request, anh chạy thử và sẽ thấy đúng cái bọn tôi thấy" thì kết thúc nó — hoặc nó tái hiện được, và bóng nằm rõ ràng ở sân bên kia; hoặc không, và có gì đó khác nhau ở môi trường, mà đó mới chính là phát hiện. Cả hai đều hơn một chuỗi mail so trí nhớ. Một khẳng định bắn lại được là một khẳng định kiểm tra được.
Để schema quyết định ghi những trường nào
Khi bản ghi tóm tắt các trường của một request, danh sách trường nên lấy từ chính validation schema của endpoint chứ đừng chọn tay. Schema là lời phát biểu đầy đủ về những gì endpoint nhận; một danh sách viết tay chỉ là ảnh chụp những gì ai đó thấy quan trọng vào hôm đó, và nó cũ đi ngay ở release sau mà không ai để ý.
Lấy từ schema còn gỡ một cái bẫy bảo trì: một danh sách, ở một chỗ, đổi theo contract. Một danh sách thứ hai giữ bằng tay thì sớm muộn cũng bỏ sót đúng cái trường hoá ra là toàn bộ vấn đề — và bỏ sót trong im lặng.
Vắng mặt, rỗng và null là ba sự kiện khác nhau
Một trường chưa từng được gửi, một trường gửi kèm giá trị rỗng, và một trường gửi null một cách tường minh là ba chuyện khác nhau, và thường sinh ra ba hành vi khác nhau ở phía sau: cái thì rơi về default, cái thì đè lên default, cái thì bị từ chối thẳng.
Từ quan trọng là giữ nguyên, và nó thuộc về lúc chụp chứ không phải lúc đọc. Phần lớn serialiser xoá sự phân biệt này miễn phí — một key mang giá trị undefined thì biến mất trên đường sang JSON — nên tới khi có người mở bản ghi ra xem, cái bằng chứng phân biệt ba trường hợp đã mất từ đời nào. Đó là một quyết định lúc ghi log đội lốt một chi tiết format, và phải quyết một cách có chủ đích, đúng một lần, trong đoạn code viết ra bản ghi.
Ba ràng buộc, không thì bằng chứng thành sự cố
Mọi thứ ở trên đều đẩy về hướng ghi nhiều hơn. Có ba thứ đẩy ngược lại, và không thứ nào mặc cả được.
Credential không bao giờ được vào bản ghi. Token, password, API key, dữ liệu thẻ đều bị bóc ngay tại điểm chụp — không phải che lúc hiển thị, không phải lọc ở tầng xem log. Một bản ghi đã từng chứa secret là đã chứa rồi, mà cả mục đích của mấy bản ghi này là để zip vào folder rồi gửi sang công ty khác.
Dung lượng phải có trần. Ghi đủ request và response cho từng cú gọi supplier, ở lưu lượng search với vài chục tích hợp, là một trong những luồng ghi lớn nhất hệ thống. Nên nó nằm sau một flag, có retention, và có một quyết định rõ ràng là môi trường nào được bật hết cỡ. Mình không có con số một ngày log provider đầy đủ tốn bao nhiêu dung lượng — chưa ai tính theo cách mình dám trích — nhưng chính cỡ của con số đó là lý do cái flag tồn tại.
Đường ghi log phải fail soft. Nếu lượt ghi log ném lỗi, timeout hay đầy đĩa, cái request nó đang mô tả vẫn phải chạy xong. Một service ngã vì không ghi được log là đã biến một tính năng observability thành một sự cố — và nó sẽ ngã đúng lúc lưu lượng cao nhất, cũng là lúc log có ích nhất.
Quy tắc
Hãy log cho người đọc không đọc được code của bạn. Toàn bộ thiết kế nằm ở đó: cái gì đã rời process, cái gì đã quay về, nguyên văn, bắn lại được, secret đã bóc, phân biệt còn nguyên. Mỗi thứ bạn giữ lại là một thứ sau này khỏi phải cãi. Mỗi thứ bạn tóm tắt cho gọn là một thứ một ngày nào đó bạn sẽ được yêu cầu chứng minh.