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
.
Level | Giá trị |
---|---|
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 10 |
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à QueueHandler
và QueueListener
, 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. QueueHandler
và QueueListener
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