37 điểm bởi GN⁺ 2025-12-22 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong các hệ thống phân tán hiện đại, cách làm log truyền thống có những giới hạn mang tính cấu trúc khiến nó không truyền tải được sự thật
  • Log vẫn được thiết kế dựa trên môi trường máy chủ đơn kiểu năm 2005, nên làm mất ngữ cảnh của những request đi qua nhiều service, database và cache
  • Việc chỉ tìm kiếm chuỗi đơn thuần không hiểu được cấu trúc, quan hệ và tương quan, khiến việc tìm nguyên nhân sự cố trở nên khó khăn
  • Giải pháp là với mỗi request, ghi lại một ‘Wide Event’ (hoặc Canonical Log Line) duy nhất chứa toàn bộ ngữ cảnh
  • Nhờ đó, log được chuyển từ văn bản đơn thuần thành tài sản dữ liệu có thể phân tích

Vấn đề cốt lõi của logging

  • Log truyền thống được tạo ra với giả định của thời đại máy chủ monolithic, nên không phản ánh được kiến trúc dịch vụ phân tán hiện đại
    • Một request đi qua nhiều service, DB, cache và queue, nhưng log vẫn được ghi theo tiêu chuẩn của một máy chủ đơn
  • Trong ví dụ, mỗi request tạo ra 13 dòng log; với 10.000 người dùng đồng thời sẽ có 130.000 dòng mỗi giây, nhưng phần lớn là thông tin vô nghĩa
  • Khi xảy ra sự cố, thứ cần thiết là ngữ cảnh (context), nhưng log hiện tại lại thiếu điều đó

Giới hạn của tìm kiếm theo chuỗi

  • Khi người dùng báo “không thanh toán được”, dù tìm log bằng email hay user_id thì cũng khó có được kết quả hữu ích vì không có cấu trúc nhất quán
    • Cùng một user ID có thể được ghi dưới hàng chục dạng khác nhau như user-123, user_id=user-123, {"userId":"user-123"}
  • Định dạng log khác nhau giữa các service khiến không thể lần theo các sự kiện liên quan
  • Vấn đề cốt lõi là log được thiết kế xoay quanh ghi (write), chứ không được tối ưu cho truy vấn (query)

Định nghĩa các khái niệm cốt lõi

  • Structured Logging: cách ghi log dưới dạng key-value (JSON) thay vì chuỗi
  • Cardinality: số lượng giá trị duy nhất của một field; ví dụ user_id có độ cardinality rất cao
  • Dimensionality: số lượng field trong một sự kiện log; càng nhiều thì khả năng phân tích càng cao
  • Wide Event / Canonical Log Line: một sự kiện log đơn giàu ngữ cảnh cho mỗi request
  • Phần lớn hệ thống logging hạn chế dữ liệu có cardinality cao vì chi phí, nhưng trên thực tế đây lại là thứ hữu ích nhất cho việc debug

Giới hạn của OpenTelemetry

  • OpenTelemetry (OTel) là một bộ giao thức và SDK, chỉ cung cấp tiêu chuẩn cho việc thu thập và truyền dữ liệu
  • Tuy nhiên OTel không làm những việc sau
    1. Không quyết định cần log cái gì
    2. Không tự động thêm ngữ cảnh nghiệp vụ (ví dụ: gói thuê bao, giá trị giỏ hàng)
    3. Không thay đổi tư duy logging của lập trình viên
  • Dù dùng cùng một thư viện, trải nghiệm debug giữa instrumentation được thêm ngữ cảnh một cách có chủ đích và instrumentation đơn thuần là khác biệt rất lớn
  • OTel chỉ là đường ống truyền dẫn (plumbing), còn truyền cái gì qua đó là do lập trình viên quyết định

Cách tiếp cận Wide Event / Canonical Log Line

  • Cần rời bỏ kiểu logging tập trung vào “code đang làm gì” để ghi lại “điều gì đã xảy ra với request
  • Với mỗi request, tạo một sự kiện rộng ở cấp độ service
    • Có thể bao gồm hơn 50 field như request, người dùng, thanh toán, lỗi, môi trường...
  • JSON ví dụ chứa toàn bộ ngữ cảnh cần cho debug như user_id, subscription_tier, service_version, error_code
  • Nhờ vậy, chỉ với một lần tìm kiếm có thể phân tích ngay lập tức những việc như “nguyên nhân thất bại thanh toán của người dùng premium”

Cách truy vấn Wide Event

  • Wide Event không được xử lý bằng tìm kiếm văn bản đơn thuần mà bằng truy vấn dữ liệu có cấu trúc
  • Có thể debug ở mức độ phân tích thời gian thực dựa trên dữ liệu có cardinality cao và nhiều chiều
  • Ví dụ: có thể chạy ngay truy vấn như “thống kê tỷ lệ thất bại thanh toán của người dùng premium trong 1 giờ qua theo từng mã lỗi”

Mẫu triển khai

  • Xây dựng sự kiện xuyên suốt toàn bộ vòng đời request, rồi chỉ ghi ra một lần ở cuối
    • Trong middleware, khởi tạo các field cơ bản như request_id, timestamp, method, path
    • Trong handler, bổ sung dần thông tin về người dùng, giỏ hàng, thanh toán và lỗi
  • Cuối cùng ghi một sự kiện JSON duy nhất bằng logger.info(event)

Kiểm soát chi phí bằng sampling

  • Ghi hơn 50 field cho mỗi request có thể làm chi phí tăng mạnh, nên cần sampling
  • Sampling ngẫu nhiên đơn thuần có nguy cơ bỏ sót lỗi
  • Đề xuất chiến lược Tail Sampling
    1. Luôn lưu lỗi (như 500)
    2. Luôn lưu các request chậm (từ p99 trở lên)
    3. Luôn lưu người dùng VIP hoặc session có cờ cụ thể
    4. Phần còn lại chỉ lấy mẫu ngẫu nhiên 1~5%
  • Nhờ vậy có thể giảm chi phí mà vẫn giữ lại các sự kiện quan trọng

Làm rõ những hiểu lầm phổ biến

  • Structured Logging ≠ Wide Event: chỉ dùng định dạng JSON là chưa đủ, ngữ cảnh mới là cốt lõi
  • Dùng OpenTelemetry ≠ có observability đầy đủ: nó chỉ chuẩn hóa việc thu thập, còn ghi gì vẫn là trách nhiệm của lập trình viên
  • Không giống Tracing: tracing thể hiện luồng đi giữa các service, còn Wide Event cung cấp ngữ cảnh bên trong service
  • Không cần tách biệt “log dùng để debug, metric dùng cho dashboard” — Wide Event đáp ứng được cả hai mục đích
  • Quan niệm “dữ liệu cardinality cao thì đắt” đã lỗi thời; các DB hiện đại như ClickHouse, BigQuery có thể xử lý hiệu quả

Hiệu quả của việc áp dụng Wide Event

  • Debug được chuyển từ khai quật (archaeology) sang phân tích (analytics)
  • Thay vì grep log của 50 service để tìm “lỗi thanh toán của người dùng”,
    giờ đây có thể chuyển sang phân tích dựa trên một truy vấn duy nhất như “xem tỷ lệ thất bại thanh toán của người dùng premium theo từng mã lỗi”
  • Kết quả là log được chuyển từ một công cụ từng nói dối thành một tài sản dữ liệu nói lên sự thật

1 bình luận

 
GN⁺ 2025-12-22
Ý kiến trên Hacker News
  • Bài viết khá khó đọc và có cảm giác như được AI hỗ trợ. Dù vậy, thông điệp vẫn có giá trị, và có lẽ sẽ tốt hơn nếu ngắn gọn hơn
    Gần đây tôi có vài suy nghĩ như sau.

    • Vì toàn bộ stack đều có xác thực, tôi đã bắt đầu thêm user id vào mọi dòng log. Nhờ vậy, việc nhìn tổng thể trải nghiệm mà người dùng đã gặp trở nên dễ hơn
    • Ghi lỗi thành dòng riêng tách khỏi log request thì khá phiền. Có thể lọc bằng trace, nhưng rất khó tạo truy vấn kiểu “chỉ cho tôi các lỗi liên quan đến request 5xx”
    • Chỉ thêm ngữ cảnh như vậy vẫn chưa đủ, còn phải đào tạo đồng nghiệp rằng đã có các field mới. Tôi thấy nhiều người tự vật lộn chỉ vì không biết điều đó
    • Nếu đầu tư vào công cụ tracing tốt hơn, bạn có thể debug ở mức mà log đơn thuần không thể làm được. Có thể xem đây là mở rộng ý tưởng dùng user id như một trace
    • Nếu codebase có khái niệm request ID, thì cũng có thể dùng nó để theo dõi hành vi của người dùng cụ thể hơn
    • Nếu bắt buộc áp dụng TID trên toàn bộ dịch vụ, bất kỳ team nào cũng có thể lần theo toàn bộ transaction chỉ với một TID
    • Những bình luận kiểu “ngửi thấy mùi AI” như thế này sớm muộn cũng sẽ trở thành một văn hóa bị chỉ trích
  • Với chủ đề này thì không thể không nhắc đến Charity Majors. Cô ấy đã truyền bá các khái niệm “wide events” và “observability” hơn 10 năm qua, và xây dựng Honeycomb.io dựa trên triết lý đó.
    Hiện nay có nhiều công cụ khác nhau để hiện thực hóa cách làm này. Điều quan trọng là dùng structured logs hoặc traces để ghi lại wide event, rồi dùng các công cụ có trực quan hóa phong phú như chuỗi thời gian, histogram, v.v.

    • Tuy vậy, không phải chính cô ấy là người tạo ra thuật ngữ “observability”. Đây vốn là khái niệm đã được dùng từ nhiều thập kỷ trước trong nhiều lĩnh vực. Cô ấy là một người thực hành có ảnh hưởng, nhưng không phải người khai sinh thuật ngữ này
    • Blog của cô ấy và câu chuyện hậu trường của Honeycomb là thứ mà ai trong ngành cũng rất đáng đọc. Đó là một trong những team đầu tiên nhận ra giá trị của cách tiếp cận này
    • Bài viết giống phong cách của cô ấy đến mức tôi cứ nghĩ cuối bài sẽ có quảng cáo Honeycomb, nhưng không có nên khá bất ngờ
    • Trong hệ sinh thái .NET, Nick Blumhardt đã nói về structured logging từ rất lâu, và Seq cùng Serilog đã hỗ trợ điều đó
    • Nội dung của cô ấy rất tốt, nhưng không ai “thương hiệu hóa” được “observability”. Nên tôn trọng, nhưng tránh những khẳng định phóng đại
  • Tôi đồng ý với một phần lập luận của bài, nhưng cách chỉ để lại một wide event duy nhất có một cái bẫy. Nếu ngoại lệ hoặc timeout xảy ra giữa chừng trong request thì sẽ không còn gì được ghi lại cả.
    Bạn cũng sẽ bỏ lỡ framework logging mặc định của ngôn ngữ hoặc log từ dependency.
    Vì vậy, tốt hơn là dùng nó như một lớp bổ sung phủ lên trên log hiện có. Chỉ cần có ID theo request/session rồi tổng hợp ở nơi như ClickHouse là được

    • Nếu vấn đề là khả năng quan sát ở tầng trung gian, thì nghĩa là event vẫn chưa đủ wide. log.error(data) hay wide_event.attach(error, data) về bản chất là như nhau
    • Những log như “connection X:Y accepted at Z ns” và “closed at Z ns” rất hữu ích để debug hệ thống chậm
    • Tôi đã giải quyết việc này trong framework PHP bằng cách tạo LoggerInterface. Ngoại lệ được bắt bởi handler toàn cục rồi lưu vào DB dưới dạng wide. Có hơi boilerplate một chút, nhưng hoạt động quá tốt nên giờ không có lại thấy khó chịu
  • Phần trình bày và ví dụ tương tác rất hay. Nhưng rốt cuộc thì vẫn có thể tóm lại là “hãy thêm các tag có cấu trúc vào log”.
    Tôi cảm thấy wide log không mang lại lợi ích đủ lớn so với mức độ phức tạp và giảm khả năng đọc mà nó tạo ra.
    Chỉ cần grep "uid=user-123" application.log là đủ rồi, liệu có cần phải gắn thêm cả phương thức giao hàng của người dùng không?
    (Nhân tiện, trên trình duyệt Brave Android thì checkbox không hoạt động)

    • Nếu là log JSON thì vẫn có thể tìm bằng grep '\"uid\": \"user-123\"'. Cũng có thể dùng tùy chọn --context để xem các dòng xung quanh
  • Tôi từng làm việc trong môi trường sản xuất bán dẫn, với hệ thống có hàng nghìn thành phần tham gia vào message bus. Log tạo ra 300~400MB mỗi giờ, nhưng vẫn quản lý đủ tốt chỉ với grep và các công cụ CLI.
    Log chỉ đơn giản là chuỗi sự kiện theo thời gian, còn phân tích chi tiết thì xử lý bằng truy vấn Oracle. Log là công cụ để nắm được quan hệ nhân quả của sự việc

    • Log là để nắm timeline, chứ không phải để chứa toàn bộ dữ liệu của request·response. Nhồi quá nhiều thông tin vào thì ngược lại còn khó hiểu hơn.
      Log nói cho ta biết “khi nào, điều gì đã xảy ra”, còn “tại sao” thì phải tìm trong tổ hợp của code, dữ liệu và sự kiện
      Cá nhân tôi thấy các giao diện như ELK stack không thuận tiện cho việc khám phá trực quan. Điều quan trọng là log phải có thể được đọc và lần theo một cách bản năng
    • 400MB log mỗi giờ thực ra không nhiều. Vì vậy chỉ dùng grep đơn giản cũng xử lý đủ tốt
  • Lời khuyên ở cuối bài rằng “hãy log mọi lỗi, ngoại lệ và request chậm” là một ý tưởng nguy hiểm.
    Ví dụ, nếu một dependency trở nên chậm đi, lượng log có thể tăng vọt gấp 100 lần.
    Trong tình huống sự cố, dịch vụ nên làm ít việc hơn để dễ phục hồi, nhưng việc log bùng nổ lại dễ gây ra sự cố dây chuyền

    • Ở Cloudflare họ dùng adaptive sampling. Họ bucket các lô log theo từng field, rồi trong mỗi bucket chỉ giữ lại căn bậc hai hoặc logarit của số lượng log đầu vào.
      Lượng log càng nhiều thì tỷ lệ lấy mẫu càng tự động điều chỉnh để hệ thống không bị quá tải
    • Những ngưỡng ma thuật như thế này rất nguy hiểm. Các con số như P(99) nên được cập nhật động. Sẽ an toàn hơn nếu OTEL provider định kỳ lấy giá trị thực tế
    • Dịch vụ production nên được thiết kế sao cho việc thu thập log có thể mở rộng theo nhu cầu. Chỉ cần buffering trên đĩa cục bộ thôi cũng đã giúp ích rất nhiều
    • Nếu là dịch vụ traffic cao thì có thể lấy mẫu các request khỏe mạnh bằng cách như trace_id mod 100 == 0
    • Nếu log trở thành nút thắt cổ chai thì nghĩa là thiết kế hệ thống đã có vấn đề. Logging hiệu quả có thể xử lý hàng trăm triệu sự kiện mỗi giây
  • Trong phần mềm hiện đại, rất khó để một log đơn lẻ giải thích hoàn toàn “điều gì đã xảy ra”.
    Vì vậy cần có tương quan theo chiều dọc (Vertical correlation)tương quan theo chiều ngang (Horizontal correlation).
    Giữa các tầng trên dưới trong stack phải chia sẻ cùng một giá trị correlation, còn khi giao tiếp giữa các hệ thống thì cần correlation giữa các peer.
    Việc thêm các giá trị này vào API hay protocol là khó, nhưng nếu thiết kế sẵn transaction ID thì có thể lần theo toàn bộ

  • Tôi nghĩ việc đăng ký hẳn một domain riêng chỉ cho một bài viết thì thiếu tính bền vững.
    Vì phải trả phí gia hạn hằng năm, nên dùng blog cá nhân hoặc subdomain sẽ tốt hơn.
    Ví dụ dạng logging-sucks.boristane.com sẽ hợp lý hơn

    • Thực ra domain này và bài viết này là để quảng bá observability SaaS của tác giả. Cần tài khoản Cloudflare, nhưng miễn phí nên có vẻ là một chiến lược marketing dài hạn. Dù vậy vẫn hữu ích, và tôi cũng có tài khoản CF nên chắc sẽ thử dùng
    • Bài này khá giống với “Give people something to link to” của Simon Willison
    • Cái này gần với một landing page tạo lead cho digital marketing hơn là một bài blog. Mục đích quảng bá dịch vụ rất rõ ràng
  • Với nhận định “log là di tích của thời kỳ monolithic”, tôi nghĩ log cục bộ vẫn còn giá trị.
    Vai trò vốn có của nó là ghi lại cuộc đối thoại trong tiến trình cục bộ, còn nếu muốn hiểu tình hình ở các server khác thì cần transaction tracing.
    Chỉ cần xem log ở đúng chỗ cũng có thể lần ra nguyên nhân gốc rễ

    • Tuy vậy, log không chỉ để phân tích nguyên nhân đơn thuần mà còn có thể mang lại insight kinh doanh như ai bị ảnh hưởng, mối tương quan giữa hiệu năng và đầu vào, hay tác động của lỗ hổng bảo mật.
      Log giàu ngữ cảnh khi kết hợp với engine phân tích còn có thể dùng để cải thiện sản phẩm
    • Khi nghe câu “một request đi qua 15 service và 3 DB”, cũng có người phản ứng rằng nên tránh kiểu phức tạp như vậy
    • Tôi cảm thấy chỉ với APN/Kibana thôi cũng đã đủ để phân tích log
  • Tôi đồng ý với câu “đừng log những gì code đang làm, hãy log những gì đã xảy ra với request”, nhưng tác giả có vẻ còn thiếu kinh nghiệm.
    Tôi gọi điều này là “bug parts logging”, và cho rằng cần đưa vào các tín hiệu báo trước như đường đi xử lý, số lần, thời gian, v.v.
    Logging khác với metric hay audit. Nếu logging thất bại thì xử lý vẫn phải tiếp tục, nhưng audit mà thất bại thì là vấn đề nghiêm trọng.
    Giống như khái niệm “historian” trong hệ thống SCADA, cần phân biệt observablesevaluatives.
    Ví dụ, các sự kiện chi tiết từ cảm biến nhiên liệu hữu ích cho chẩn đoán, nhưng lại không cần thiết cho câu hỏi “có đi tới đích được không”.
    Cuối cùng, điều quan trọng là phải xác định rõ cần quan sát gì, và cần đánh giá gì

    • Tôi ủng hộ “lý thuyết thống nhất của observability”. Log, metric, audit rốt cuộc đều chỉ là các luồng bit, và có thể chuyển đổi qua lại mà không mất mát.
      Dù cách lưu trữ, chuyển đổi, truy vấn có khác nhau, nhưng điểm tiêu thụ và cơ chế có thể được thiết kế thống nhất.
      Làm vậy sẽ giúp thiết kế hệ thống đơn giản hơn, và log lưu trữ dài hạn cũng có thể được xử lý lại về sau nếu cần