Python: Cách dùng Log như một Pro Vip trong Python

Chúc mừng năm mới

Năm mới khai bút, chúc tất cả mọi người một năm tràn ngập niềm vui, hạnh phúc và đạt được mọi điều mình mong ước.

Lời nói đầu

Bài viết này chỉ mang tính chất mày mò dịch và viết lại theo ý hiểu của bản thân nên nếu còn có gì thiếu sót thì mong mọi người góp ý thêm cho mình với.

Nếu bạn đã từng code Python rồi thì tỷ lệ cao là bạn đã phải dùng khá khá câu lệnh print để xem code của mình chạy như nào, đã đúng và ổn hay chưa hay là để tìm xem lỗi nó nằm ở chỗ nào. Nhưng việc dùng print nhiều như vậy thì cực kỳ là ối dồi ôi nên hôm nay mình sẽ chỉ cho bạn cách bạn có thể bắt đầu sử dụng Log như một a.k.a provip trong python.

Logging là một cái rất gì và này nọ cũng như cực kỳ quan trọng trong việc phát triển phần mềm. Nó giúp các nhà phát triển hiểu rõ hơn về việc thực thi chương trình, lỗi không mong muốn và lý do của chúng. Logging có thể lưu trữ thông tin như trạng thái hiện tại của chương trình hoặc nơi chương trình đang chạy. Nếu xảy ra lỗi, các nhà phát triển có thể nhanh chóng tìm ra dòng code gây ra sự cố và xử lý nó. Việc tìm kiếm những thứ này có thể được thực hiện dễ dàng hơn bằng cách sử dụng các công cụ ghi nhật ký (logging) tích hợp sẵn của Python.

Log Levels

Có một lưu ý nhỏ là các trình ghi nhật ký (logging) cũng như các lớp xử lý phía dưới thì đều sử dụng một cái gọi là logging levels. Đây chính là một hệ thống phân cấp log về cách thức mọi việc được xử lý.

Ví dụ: Khi chạy project trên local khi các bạn sử dụng DEBUG level, nó sẽ nhận được các câu lệnh debug và tất cả các cấp độ trên nó. Các mức này được sử dụng để lọc dữ liệu theo mức độ quan trọng của nó. Ví dụ như khi chọn level log là DEBUG thì hệ thống sẽ log lại hết tất cả mọi thứ liên quan từ DEBUG, INFO, WARNING, ERROR, CRITICAL.

LevelGiá trị
CRITICAL50
ERROR40
WARNING30
INFO20
DEBUG10
NOTSET10

Logger

Logger chỉ đơn giản là một instance được đặt tên của logging object. Tất cả các logger thì đều là “global” trong chương trình. Ví dụ.

# logging/app.py
import logging
logger = logging.getLogger("app")

Đoạn code trên đã xác định một logger có tên app trong app.py.

Bây giờ nếu muốn, mình có thể import vào một tệp khác và có quyền truy cập vào cùng một logger instance mà mình đã tạo trước đó. Nó sẽ trông giống như sau:

# logging/common.py
import logging
logger = logging.getLogger("app")

Bây giờ common.py có quyền truy cập vào cùng một logger instance. Điều này có nghĩa là tôi có thể khai báo tất cả các logger mà tôi muốn trước khi tôi cần dùng đến.

Lưu ý: cách tốt nhất là sử dụng __name__ để đặt tên cho logger như thế này:logging.getLogger(__name__). Bằng cách này, mỗi modul có logger name riêng.

Công việc cốt lõi của Logger là gửi LogRecords cho những người xử lý khác nhau. LogRecords lưu lại chi tiết về những gì đã xảy ra trong hệ thống khi chạy.

logger.info("Emitting Info LogRecord")
logger.error("Emitting Error LogRecord")

Trên đây là một ví dụ về ý nghĩa của việc bắn ra một sự kiện. Các sự kiện được bắn ra với một level đi kèm. Điều này cho phép biết mức độ khẩn cấp của LogRecord (Log Level). Vấn đề với đoạn code trên là logger hoàn toàn không rõ về đích đến của những bản ghi này như console hay file.

Handlers

Handlers kiểm soát nơi LogRecords trả ra. Handlers là các đối tượng độc lập có thể được gắn vào các logger instance.

logger = logging.getLogger(__name__) 
fileHandle = logging.FileHandler('logging.log') 
logger.addHandler(fileHandle)

Đoạn mã trên nói với logger rằng, bất cứ khi nào a LogRecord được tạo, nó sẽ được gửi đến tệp logging.log. Vấn đề ngay bây giờ là nó logging.log sẽ bị nhồi nhét rất nhiều nhật ký.

Đặt log level mà Handlers phải quản lý sẽ giúp tôi lọc được log của mình theo level.

logger = logging.getLogger(__name__)
fileHandle = logging.FileHandler('logging.log')
fileHandle.setLevel(logging.WARNING)
logger.addHandler(fileHandle)
logger.info("Nó sẽ không hiển thị logging.log")
logger.error("Nó sẽ hiển thị")

Trên đây là cách lọc log sử dụng log level . Như ở phần log levels thì ERROR nó cao hơn WARNING nên nó sẽ được xử lý còn INFO thì thấp hơn WARNING nên sẽ bị loại bỏ.

Có một số loại handlers có thể được sử dụng ví dụ như StreamHandler, FileHandler. Để biết danh sách đầy đủ, hãy xem handlers document trong python: handlers

StreamHandlersẽ gửi output tới console. FileHandler gửi thông tin đến một tệp đầu ra. Các trình xử lý động nhất là QueueHandlerQueueListener, nhưng cái này thì mình sẽ nói về nó sau.

Formatters

Giả sử các bạn chỉ có một thông báo cho biết: “Đã xảy ra lỗi” thì gần như cái thông báo này chả có ý nghĩa mọe gì. Vì vậy formatters đã ra đời để giải quyết vấn đề này. Formatters tồn tại trên Handlers khi Handlers xử lý log records.

Các bạn có thể dễ dàng thêm một số thông tin bổ sung khác chẳng hạn như ở đâu, cái gì, tại sao, khi nào và như thế nào.

import logging

logger = logging.getLogger(__name__)
fileHandle = logging.FileHandler('app.log')
fileHandle.setLevel(20)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fileHandle.setFormatter(formatter)
logger.addHandler(fileHandle)
logger.warning("Warning Message")
logger.error("Error Message")
>>> 2023-01-24 22:31:43,182 - __main__ - WARNING - Warning Message
>>> 2023-01-24 22:31:43,182 - __main__ - ERROR - Error Message

Trên đây là đầu ra trong bảng điều khiển đã được tạo.

Formatters ở ví dụ trên thì đã được chèn thêm thời gian, tên logger, level của record và thông báo.

QueueHandler và QueueListener

Có một vấn đề xảy ra đó chính là bất cứ khi nào chương trình chạy đến đoạn code logger.info(“message”), chương trình của bạn sẽ bị block cho đến khi xử lý xong việc ghi log. Nó sẽ không phải là một vấn đề lớn nếu bạn chỉ xuất log ra console nhưng mà thì khi đang chạy ngon mà server sập cái thì log sẽ hát vang lên bàii “có không giữ mất đừng tìm”.

Nếu bạn gửi log đến cơ sở dữ liệu, việc đợi record được tạo có thể mất một chút thời gian, chương trình của bạn sẽ bị block mỗi khi log record được tạo. QueueHandlerQueueListener sẽ giải quyết vấn đề này. QueueHandler đặt message vào một hàng đợi tồn tại trên một Thread riêng biệt. Sau đó, QueueListener gửi Log Record cho tất cả các handler khác. Bằng cách này, chương trình của bạn không bị block để chờ Log Record được tạo. Để tìm hiểu chi tiết hơn về việc sử dụng QueueHandler and QueueListener thì có thể xem thêm ở link này

Sử dụng File để khởi tạo loggers

Điều cuối cùng cần làm là tạo một File để khởi chạy tất cả các loggers. Việc này sẽ giúp tránh được việc lặp lại code, quản lý và thay đổi dễ dàng, giúp tiết kiệm thời gian và công sức. Các bạn hoàn toàn có thể tạo và quản lý tất cả các logger chỉ ở một nơi duy nhất. Có rất nhiều cách để lưu trữ cấu hình loggers cho dự án như sử dụng dictionary, JSON hay yaml file để khởi tạo loggers. Dưới đây là một ví dụ với yaml file:

# Logger Config.yaml
version: 1
objects:
  queue:
    class: queue.Queue
    maxsize: 1000
formatters:
  simple:
    format: '%(asctime)s - %(levelname)s - %(name)s - %(message)s'
handlers:
  discord:
    class: utilities.log_utils.logger_util.DiscordHandler
    level: ERROR
    formatter: simple
  queue_listener:
    class: utilities.log_utils.logger_util.QueueListenerHandler
    handlers:
      - cfg://handlers.console
      - cfg://handlers.file
      - cfg://handlers.discord
    queue: cfg://objects.queue
loggers:
  __main__:
    level: WARNING
    handlers:
      - queue_listener
    propagate: false
  ieddit:
    level: WARNING
    handlers:
      - queue_listener
    propagate: false

Cuối cùng thì các bạn chỉ cần viết một file để đọc config từ yaml file, dictionary hay JSON để khởi tạo loggers và sau đó chỉ cần import và chạy một lần duy nhất trong dự án. Ví dụ như sau:

# logger_init.py
import config
import yaml
import logging

with open('logger_config.yaml', 'r') as stream:
  try:
    logging_config = yaml.load(stream, Loader=yaml.SafeLoader)
  except yaml.YAMLError as exc:
    print("Error Loading Logger Config")
    pass
logging.config.dictConfig(logging_config)

Mọi người có thể tham khảo code mẫu về sử dụng logging với python ở đường dẫn sau: GITHUB

tags: Python cơ bản, Python, Programming, Logging, Loggers, FileHandlers, Handlers, Formatters, QueueHandler , QueueListener, Log Levels

Nguồn : Mediummmm

Nguồn: viblo.asia

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *