close

powerful-programming-language-word-python-450w-401797897

(圖片來源)

在寫程式的時候使用 print 大法,大概是每個 programmer 都不陌生的招式,簡單又快速。但是有時候,在程式運行的過程中,會產生一些 event 或寶貴的 message 值得記錄下來做事後分析,這些就不是 print 大法可以勝任了,Python 有提供 logging 這個強大的 module 來讓大家使用,但是正因為他提供的功能很多,導致 configure logger 的時候有點複雜,讓許多新手望之卻步,在這裡簡單介紹大家如何使用 Python 的 logging module。

 

一、 訊息 5 大程度

二、Logging Level

三、Logging basicConfig

四、Logging fileConfig

五、個人使用方式

 

一、訊息 5 大程度

logging 為開發者提供了 5 種程度不同的描述來紀錄訊息,分別是 debug、info、waring、error、critical,這五種的嚴重比較程度,如下:

DEBUG < INFO < WARNING < ERROR / EXCEPTION < CRITICAL

(ps. exception 會額外紀錄當前拋出的例外訊息, 適用於 try except)

代碼範例:

import logging

logging.debug('Hello debug!')
logging.info('Hello info!')
logging.warning('Hello warning!')
logging.error('Hello error!')
logging.critical('Hello critical!')

結果:

 

二、Logging Level

看完上面的執行結果,眼尖的你可能會有疑問,為什麼 debug、info 的訊息沒有印出來呢? 這就跟 logger 的 level 有關係了。logger 有提供 level 可以過濾掉嚴重程度沒那麼高的 message。

舉例來說,當我們把 logger 的 level 設為 logging.INFO 的時候,所有 debug() 的 message,將會被自動忽略,只有 info(), warning(), error(), critical() 才會處理。如果我們把 level 設成 logging.ERROR 的時候,所有 debug(), info(), warning() 的訊息將會被忽略。而在我們沒有設定 level 的時候,預設會是 logging.WARNING,這就是為什麼 debug、info 沒有印出來的原因。

實作範例中若手動將 logging level 設定為 INFO, 則 logging.info 的訊息就會印出來了!

代碼範例:

import logging
logging.basicConfig(level=logging.INFO)

logging.debug('Hello debug!')
logging.info('Hello info!')
logging.warning('Hello warning!')
logging.error('Hello error!')
logging.critical('Hello critical!')

結果:

實用情況:

在開發的時候使用 DEBUG level,等到上 production 的時候,會把 level 設成 WARNING。這樣有個好處是,不用修改任何 code 就可以把一些 debug 資訊都隱藏起來,比起用 print 大法,再一個一個找出來刪掉來要來得方便。

 

三、Logging basicConfig

在修改 logging level 的範例中,可以看到我有使用 basicConfig 的功能,除了 level 可以調用的參數如下:

參數 描述
filename log 檔名
filemode 寫入的方式 (w, w+, a+)
format 指定表示的方式
datefmt 時間的表示
style ormatter 使用 % or {} or $
level 訊息顯示程度
stream 指定輸出的 stream,與 filename 不相容,無法共用
handlers 指定 handlers,與 filename & stream 不相容,無法共用

代碼範例:

import logging
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M',
                    handlers=[logging.FileHandler('my.log', 'w', 'utf-8'), ])

logging.debug('Hello debug!')
logging.info('Hello info!')
logging.warning('Hello warning!')
logging.error('Hello error!')
logging.critical('Hello critical!')

結果:

 

四、Logging fileConfig

使用 basicConfig 可以對 logging 進行一些設定,但此方法除了不易閱讀代碼,也會造成後續維護的不方便,而替代的方案就是 fileConfig,在第一次建置 config 檔案的時候會稍微麻煩,但會使代碼更加簡潔,也方便後續維護。

代碼範例:

import logging
from logging.config import fileConfig

fileConfig('logging_config.ini')
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

logging_config.ini:

[loggers]
keys=root

[handlers]
keys=stream_handler

[formatters]
keys=formatter

[logger_root]
level=DEBUG
handlers=stream_handler

[handler_stream_handler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)

[formatter_formatter]
format=%(asctime)s %(name)s %(levelname)s %(message)s

結果:

重點說明:

1. loggers下面一定要有 root,若打別的名稱會錯誤

2. 有 keys 的設定,就表示要在檔案中另外設定相對的內容

假如設定了handlers

[handlers]

keys: console, file

那檔案就必須相對的要設定 [handler_console] 跟 [handler_file],而 keys 其實可以自訂 不一定要打console or file

3. handler 設定一定要有 args 參數,沒有值也要給個 args=() 不然會顯示錯誤

 

五、個人使用方式

由於個人實務上會使用到每日排程,來達到程式自動化處理事情的情況,像這種程式在背景處理的案例,最適合使用 logging 功能來記錄每次執行的訊息,也方便日後再除錯的時候有個參考依據,以下會以爬 yahoo 網站來當作範例,檔案架構如下:

logger.py:

import logging
import os
from datetime import datetime

dir_path = 'D:/pythonProject/itkm/logger/logs/'   # 設定 logs 目錄
filename = "{:%Y-%m-%d}".format(datetime.now()) + '.log'     # 設定檔名

def create_logger(log_folder):
    # config
    logging.captureWarnings(True)   # 捕捉 py waring message
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    my_logger = logging.getLogger('py.warnings')    # 捕捉 py waring message
    my_logger.setLevel(logging.INFO)

    # 若不存在目錄則新建
    if not os.path.exists(dir_path+log_folder):
        os.makedirs(dir_path+log_folder)

    # file handler
    fileHandler = logging.FileHandler(dir_path+log_folder+'/'+filename, 'w', 'utf-8')
    fileHandler.setFormatter(formatter)
    my_logger.addHandler(fileHandler)

    # console handler
    consoleHandler = logging.StreamHandler()
    consoleHandler.setLevel(logging.DEBUG)
    consoleHandler.setFormatter(formatter)
    my_logger.addHandler(consoleHandler)

    return my_logger

tutorial.py:

from logs.logger import create_logger
import requests
requests.packages.urllib3.disable_warnings()    # 關閉警告訊息

def main(logger):
    url = "https://tw.yahoo.com/"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36'}
    results = requests.get(url, verify=False, headers=headers)
    results.encoding = 'utf-8'  # 修改編碼
    data = results.text
    logger.info(data)   # 將 yahoo html 記到 log file 中

if __name__ == '__main__':
    logger = create_logger('tutorial')  # 在 logs 目錄下建立 tutorial 目錄
    logger.info('Start \n')

    try:
        main(logger)
    except Exception as e:
        logger.exception("Runtime Error Message:")

    logger.info("Export Done! \n")

執行後的檔案架構如下:

log file 內容:

每個人、每種情況適合使用的方式的不盡相同,沒有最好的方式,只有最適合的方法,如果你也覺得我的方式不錯,歡迎拿去做使用,在使用上有任何問題也都可以盡情發問!

※注意

此篇文章是以 Python 3 來做教學

參考:

Python 3 官方教學

Python 2 官方教學

[Python] logging 教學

基本 Python logging module 入門

docs.python-guide.org : Logging

hhtucode : [Python] Basic Logging 

 

arrow
arrow

    Mayuge 發表在 痞客邦 留言(0) 人氣()