0

Nghệ thuật Quản trị Log & Log Level trong Backend: Từ Local đến Production

Bất kỳ ai khi mới bước chân vào con đường lập trình đều có một "người bạn thân" mang tên print (hoặc fmt.Println trong Golang, console.log trong NodeJS). Code chạy không đúng? In nó ra! Quên luồng đi của data? In nó ra!

Nhưng khi bạn bắt đầu làm việc với các hệ thống backend thực tế — những hệ thống microservices phức tạp, xử lý hàng ngàn request mỗi giây (high-concurrency) hay các hệ thống core như trạm thu phí tự động, bán vé — thì việc dùng print bừa bãi sẽ trở thành một thảm họa. Ghi log ra console tốn tài nguyên I/O, và khi có bug trên Production, bạn sẽ mò kim đáy bể trong một đống text hỗn độn.

Đó là lúc chúng ta cần đến Log Level (Cấp độ Log).


1. Phân loại 4 cấp độ Log cốt lõi: Debug, Info, Warn, Error

Log Level sinh ra để phân loại mức độ quan trọng của thông điệp. Một thư viện logging chuẩn (như zap, logrus hay slog của Go 1.21+) luôn cung cấp ít nhất 4 cấp độ cơ bản này.

🐛 DEBUG (Mức độ chi tiết nhất)

  • Mục đích: Dành riêng cho Developer trong quá trình phát triển, tìm diệt bug hoặc theo dõi luồng thực thi chi tiết của một function.
  • Nội dung thường chứa: Payload của Request/Response API, câu query SQL chi tiết, giá trị của các biến trong vòng lặp, thời gian thực thi của một hàm số cụ thể.
  • Ví dụ thực tế:
    • "Executing query: SELECT * FROM users WHERE id = ? [args: 123]"
    • "Redis cache miss for key: user_profile_123, falling back to database"

ℹ️ INFO (Thông tin vận hành)

  • Mục đích: Ghi lại những sự kiện quan trọng cho thấy hệ thống vẫn đang "thở" và hoạt động đúng như thiết kế. Đây là những cột mốc chính trong vòng đời của ứng dụng.
  • Nội dung thường chứa: Server khởi động thành công, Worker bắt đầu consume message từ RabbitMQ/Kafka, chạy xong một batch job ban đêm.
  • Ví dụ thực tế:
    • "Server is listening on port 8080"
    • "Successfully synchronized 5000 transaction records to central server"

⚠️ WARN (Cảnh báo)

  • Mục đích: Hệ thống gặp phải một tình huống bất thường, nhưng chưa đủ nghiêm trọng để làm sập luồng xử lý. Ứng dụng vẫn có thể tự phục hồi hoặc bỏ qua để chạy tiếp.
  • Nội dung thường chứa: API bên thứ ba phản hồi chậm (timeout), thử kết nối lại (retry) tới Database lần 1, sử dụng API đã bị deprecated, hoặc dung lượng ổ đĩa sắp đầy (85%).
  • Ví dụ thực tế:
    • "Payment gateway API is responding slowly (> 2000ms)"
    • "Failed to connect to primary DB, switching to read-replica"

❌ ERROR (Lỗi hệ thống)

  • Mục đích: Một luồng xử lý cụ thể đã thất bại và cần sự chú ý của kỹ sư vận hành. Tuy nhiên, nó thường chỉ ảnh hưởng đến một request hoặc một tác vụ, chứ chưa làm chết toàn bộ ứng dụng (Fatal).
  • Nội dung thường chứa: Không thể kết nối đến Database (sau khi đã hết số lần retry), lỗi Null Pointer, không thể ghi dữ liệu ra file do thiếu quyền.
  • Ví dụ thực tế:
    • "Failed to insert ticket booking record: duplicate key constraint"
    • "Error parsing JSON payload from client: unexpected EOF"

2. Giải mã LOG_LEVEL=debug

Khi đọc các file cấu hình .env hoặc file docker-compose.yml, bạn sẽ rất hay bắt gặp dòng chữ này:

LOG_LEVEL=debug

Vậy nó thực chất là gì?

LOG_LEVEL là một biến môi trường hoạt động như một Bộ Lọc (Filter) / Cổng Gác cho toàn bộ log của ứng dụng. Các cấp độ log được sắp xếp theo độ nghiêm trọng tăng dần: DEBUG < INFO < WARN < ERROR.

Quy luật hoạt động rất đơn giản: Hệ thống chỉ in ra những log có cấp độ LỚN HƠN HOẶC BẰNG cấp độ được cấu hình trong LOG_LEVEL:

  • Nếu LOG_LEVEL=error: Ứng dụng sẽ im lặng tuyệt đối. Nó chỉ mở miệng khi có Lỗi (Error) xảy ra. Khá tiết kiệm tài nguyên.
  • Nếu LOG_LEVEL=info: Bạn sẽ thấy các log Info, Warn, và Error. Các log Debug rác rưởi sẽ bị chặn lại ở tầng memory và không bao giờ được ghi ra file hay console.
  • Nếu LOG_LEVEL=debug: Cổng thành mở toang! MỌI THỨ mà hệ thống có thể log ra đều sẽ được ghi lại. Từ những câu query nhỏ nhất cho đến luồng dữ liệu chạy qua message broker.

3. Câu chuyện của hai môi trường: Development vs Production

Tại Development (Môi trường phát triển)

Ở dưới local máy dev, thiết lập LOG_LEVEL=debug là bắt buộc. Bạn cần nhìn thấy mọi ngóc ngách của hệ thống để đảm bảo logic mình viết ra (như locking database row, cache TTL) hoạt động chính xác.

Tại Production (Môi trường thực tế)

Nếu bạn bê nguyên LOG_LEVEL=debug lên Production, xin chúc mừng, bạn vừa tự tạo ra một vụ "tấn công DDOS" vào chính hệ thống của mình!

  • Nghẽn cổ chai I/O: Ghi log liên tục ra đĩa cứng (Disk I/O) là một thao tác cực kỳ chậm. Nó sẽ kéo tụt performance của toàn bộ ứng dụng.
  • Tràn ổ đĩa (Disk Full): Những file log sẽ phình to ra hàng Gigabyte chỉ trong vài giờ. Nếu không có cơ chế Log Rotation (xoay vòng log), server của bạn sẽ chết cứng vì hết dung lượng.
  • Lộ dữ liệu nhạy cảm (Data Leak): Log debug thường chứa dump payload, đôi khi vô tình in ra cả password, token, hay thông tin cá nhân của người dùng.

👉 Best Practice: Trên Production, biến môi trường này luôn phải được set là LOG_LEVEL=info hoặc LOG_LEVEL=warn.


4. Bỏ túi kinh nghiệm: Viết Log chuẩn như chuyên gia (Golang Edition)

Để kết thúc bài viết, hãy nâng cấp tư duy log của chúng ta lên một bậc với vài "bí kíp" thực chiến:

  1. Sử dụng Structured Logging (Log có cấu trúc): Đừng log theo kiểu chuỗi (string) thuần túy. Hãy log dưới dạng JSON. Các hệ thống như Elasticsearch (ELK Stack) hay Grafana Loki sẽ đọc và index JSON cực kỳ nhanh chóng.
    • Bad: log.Printf("User %s failed to login from IP %s", username, ip)
    • Good (với slog của Go):
      slog.Warn("User login failed", 
          slog.String("username", username), 
          slog.String("ip_address", ip),
      )
      
  2. Gắn TraceID / RequestID: Trong hệ thống microservices, một request từ client có thể đi qua 5 services khác nhau. Nếu không có TraceID đính kèm vào mỗi dòng log, khi có lỗi ở service thứ 4, bạn sẽ không thể nào ráp nối lại được câu chuyện từ đầu.
  3. Tách biệt Data và Message: Message của log phải mang tính cố định (Static) để dễ search. Dữ liệu động (Dynamic data) phải được đưa vào các field bổ sung.
    • Bad: "Transaction 192837 failed" -> Không thể group lỗi này lại được.
    • Good: Message: "Transaction failed", Field: {"transaction_id": 192837}.

Lời kết

Log Level không chỉ là một tính năng của ngôn ngữ lập trình, nó là tư duy thiết kế hệ thống. Nắm vững LOG_LEVEL giúp ứng dụng của bạn vừa dễ debug khi phát triển, vừa mượt mà, tối ưu hiệu suất khi chinh chiến trên Production.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí