Vấn đề với thư viện Logging tiêu chuẩn của Python
Thư viện logging tích hợp sẵn của Python rất đáng tin cậy, nhưng API của nó mang lại cảm giác như một di tích từ đầu những năm 2000. Việc thiết lập xoay vòng file (file rotation) đơn giản hay định dạng tùy chỉnh thường yêu cầu từ 20 đến 30 dòng boilerplate trước khi bạn viết dòng log đầu tiên. Khi xây dựng microservice đầu tiên, tôi đã dành nhiều thời gian để vật lộn với logging.basicConfig và các handler hơn là tập trung vào logic nghiệp vụ thực tế.
Trong môi trường production, log là cứu cánh duy nhất của bạn. Nếu chúng lộn xộn hoặc thiếu ngữ cảnh, bạn chẳng khác nào đang mò mẫm trong bóng tối. Việc kết hợp Loguru và Sentry sẽ giải quyết vấn đề này. Loguru loại bỏ nỗi lo cấu hình nhờ cách tiếp cận “không boilerplate”, trong khi Sentry bắt các ngoại lệ (exceptions) trước khi người dùng kịp gửi email hỗ trợ.
Bắt đầu nhanh: Log tốt hơn trong chưa đầy 5 phút
Bạn không cần một file cấu hình phức tạp để có kết quả chuyên nghiệp. Hãy bắt đầu bằng cách cài đặt các thư viện:
pip install loguru sentry-sdk
Loguru hoạt động ngay lập tức mà không cần thiết lập nhiều dòng thông thường. Dưới đây là một đoạn code mẫu sẵn sàng cho production, xử lý cả đầu ra console và xoay vòng file:
from loguru import logger
import sys
# Làm sạch cấu hình mặc định
logger.remove()
logger.add(sys.stderr, level="INFO")
logger.add("logs/app_{time}.log", rotation="500 MB", retention="10 days", compression="zip")
def calculate_division(a, b):
try:
return a / b
except Exception:
logger.exception("Phép tính thất bại ngoài dự kiến")
calculate_division(10, 0)
Cấu hình này mạnh mẽ một cách đáng ngạc nhiên. Nó tự động xoay vòng file khi đạt dung lượng 500MB, nén các log cũ thành file ZIP và xóa bỏ bất kỳ log nào cũ hơn 10 ngày. Phương thức logger.exception là một tính năng nổi bật; nó ghi lại toàn bộ stack trace để bạn không phải kiểm tra đối tượng lỗi theo cách thủ công.
Tại sao Loguru vượt trội hơn thư viện tiêu chuẩn
Loguru được xây dựng để trở nên trực quan và an toàn với luồng (thread-safe). Bạn không còn cần phải khởi tạo một logger có tên trong mỗi file bằng cách sử dụng logger = logging.getLogger(__name__). Thay vào đó, bạn chỉ cần import đối tượng logger toàn cục và bắt đầu ghi log. Nó tự động xử lý sự phức tạp của việc ghi log đồng thời (concurrent writes) cho bạn.
1. Decorator @logger.catch
Decorator @logger.catch giúp tiết kiệm rất nhiều thời gian. Nó đảm bảo rằng nếu một hàm bị crash, lỗi sẽ được ghi lại với đầy đủ backtrace có màu sắc trước khi tiến trình kết thúc. Điều này hoàn hảo cho các worker chạy ngầm hoặc các tác vụ cron định kỳ vốn có thể bị lỗi một cách âm thầm.
@logger.catch
def main_process():
# Bất kỳ lỗi crash nào ở đây cũng được tự động bắt và ghi lại chi tiết
result = 1 / 0
2. Ghi log có cấu trúc (JSON) bản địa
Các công cụ quan sát (observability) hiện đại như Datadog hoặc ELK stack ưu tiên định dạng JSON hơn là văn bản thuần túy. Loguru xử lý việc này chỉ với một đối số. Bằng cách thiết lập serialize=True, mỗi mục log sẽ trở thành một đối tượng có cấu trúc. Điều này giúp các trình thu thập (aggregators) dễ dàng đánh chỉ mục các trường như timestamp, mức độ nghiêm trọng (severity levels) và metadata tùy chỉnh mà không cần các mẫu regex phức tạp.
Sử dụng nâng cao: Cảnh báo thời gian thực với Sentry
Log cục bộ rất tốt để debug, nhưng chúng sẽ không đánh thức bạn khi một dịch vụ quan trọng gặp sự cố vào lúc 3 giờ sáng. Sentry lấp đầy khoảng trống này. Nó theo dõi các ngoại lệ, nhóm các lỗi giống nhau lại và cho bạn thấy chính xác commit nào đã gây ra lỗi.
Trong một dự án gần đây xử lý khoảng 15.000 request mỗi giờ, việc tích hợp này đã giảm thời gian trung bình để phục hồi (MTTR) của chúng tôi đi gần 40%. Chúng tôi đạt được điều này bằng cách chuyển hướng các lỗi từ Loguru trực tiếp vào Sentry.
Kết nối hai công cụ
import sentry_sdk
from loguru import logger
sentry_sdk.init(
dsn="https://[email protected]/project_id",
traces_sample_rate=0.1, # Giám sát 10% giao dịch để tiết kiệm chi phí
environment="production"
)
# Tạo một cầu nối giữa Loguru và Sentry
def sentry_sink(message):
record = message.record
level = record["level"].name.lower()
if record.get("exception"):
sentry_sdk.capture_exception(record["exception"])
else:
sentry_sdk.capture_message(record["message"], level=level)
# Chỉ gửi log mức ERROR hoặc CRITICAL tới Sentry
logger.add(sentry_sink, level="ERROR")
Với “sink” này, việc gọi logger.error() sẽ thực hiện hai việc cùng lúc. Nó ghi một dòng chi tiết vào ổ đĩa cục bộ và kích hoạt một cảnh báo ngay lập tức trên dashboard Sentry của bạn.
Bài học sau 6 tháng chạy Production
Việc vận hành thiết lập này trong môi trường lưu lượng truy cập cao đã dạy tôi một vài cách để giữ cho log sạch sẽ và chi phí trong tầm kiểm soát.
1. Loại bỏ dữ liệu nhạy cảm
Ghi log request.body rất hữu ích cho đến khi bạn nhận ra mình đã lưu 5.000 mật khẩu văn bản thuần túy vào ổ đĩa. Hãy sử dụng tính năng lọc của Loguru hoặc một sink tùy chỉnh để che giấu PII (Thông tin nhận dạng cá nhân) trước khi nó rời khỏi ứng dụng của bạn.
2. Sử dụng Contextual Logging với .bind()
Khi debug vấn đề của một người dùng cụ thể, bạn chỉ cần xem log của riêng họ. Phương thức bind() của Loguru cho phép bạn đính kèm user_id hoặc request_id vào một instance logger mà không ảnh hưởng đến các phần khác của ứng dụng.
# Đính kèm ngữ cảnh vào một instance logger cục bộ
context_logger = logger.bind(user_id="88", task_id="upload-01")
context_logger.info("Đang xử lý file")
# Mọi log từ context_logger giờ đây sẽ bao gồm các ID đó
3. Tôn trọng các cấp độ log (Levels)
Đừng sa đà vào việc ghi mọi thứ ở mức INFO. Sử dụng DEBUG cho các ghi chú chi tiết của lập trình viên và dành WARNING cho các vấn đề không nghiêm trọng như phản hồi API chậm. Tôi thường thiết lập console production ở mức INFO và cảnh báo Sentry ở mức ERROR. Chiến lược này giúp giảm bớt “sự mệt mỏi vì cảnh báo” (alert fatigue) và đảm bảo rằng khi điện thoại của bạn rung lên, đó thực sự là vấn đề quan trọng.
Chuyển từ thư viện tiêu chuẩn sang Loguru và Sentry giúp bạn thay đổi trọng tâm từ việc “tìm kiếm qua các file văn bản” sang quản lý sự cố chủ động. Đây là một thay đổi nhỏ nhưng giúp các ứng dụng Python của bạn trở nên bền bỉ hơn đáng kể.

