[Python] logging 教學

程式語言:Python
Package:logging

官方文件

功能:顯示不同層級的訊息以供 debug
import logging
 
# 基礎設定
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                    datefmt='%m-%d %H:%M',
                    handlers = [logging.FileHandler('my.log', 'w', 'utf-8'),])

# 定義 handler 輸出 sys.stderr
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 設定輸出格式
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
# handler 設定輸出格式
console.setFormatter(formatter)
# 加入 hander 到 root logger
logging.getLogger('').addHandler(console)
 
# root 輸出
logging.info('道可道非常道')
 
# 定義另兩個 logger
logger1 = logging.getLogger('myapp.area1')
logger2 = logging.getLogger('myapp.area2')
 
logger1.debug('天高地遠')
logger1.info('天龍地虎')
logger2.warning('天發殺機')
logger2.error('地動天搖')
終端結果
root        : INFO     道可道非常道
myapp.area1 : INFO     天龍地虎
myapp.area2 : WARNING  天發殺機
myapp.area2 : ERROR    地動天搖
日誌文件結果
08-20 11:25 root         INFO     道可道非常道
08-20 11:25 myapp.area1  DEBUG    天高地遠
08-20 11:25 myapp.area1  INFO     天龍地虎
08-20 11:25 myapp.area2  WARNING  天發殺機
08-20 11:25 myapp.area2  ERROR    地動天搖

Logger Objects

永遠不要直接初始化 Logger Object,應該通過 logging.getLogger(name) 來得到
以相同的名稱多次調用 getLogger() 將永遠返回相同 Logger 對象

name 可以是層次結構的值,比如 foo.bar.baz(也可以只是普通的 foo)
層次列表下游的 loggers 是上游 loggers 的子孫
例如,對於名稱為 foo 的 logger,名稱為 foo.bar、foo.bar.baz 和 foo.bam 的 logger 都是 foo 的後代。

建議的構造方式 logging.getLogger(__name__) ,__name__ 是 module 的名字。
  • class logging.Logger
    • Logger.propagate
      • True 表示會再將消息往上傳,但消息將直接傳遞給父代的 handler,不會考慮父代 logger 的級別和 filter
    • Logger.setLevel(lvl)
      • 設置 logger 的級別為 lvl
      • 嚴重程度低於 lvl 的日誌消息將被忽略
      • 根 logger 預設為 WARNING,普通 logger 預設為 NOTSET,則會往上尋找,直到父輩非 NOTSET 或是到達根 logger
      • lvl
      • LevelNumeric value
        CRITICAL 50
        ERROR 40
        WARNING 30
        INFO 20
        DEBUG 10
        NOTSET 0
    • Logger.isEnabledFor(lvl)
      • 是否 lvl 級別的訊息會被處理
    • Logger.getEffectiveLevel()
      • 得到有效的 lvl
    • Logger.getChild(suffix)
      • 返回子代
      • 兩者相同
        • logging.getLogger('abc').getChild('def.ghi')
        • logging.getLogger('abc.def.ghi')
    • Logger.debug(msg, *args, **kwargs)
      • 記錄級別為 DEBUG 的消息
      • msg
        • 消息格式字符串
      • *args
        • 通過字符串格式操作符合併到 msg 的參數
      • **kwargs
        • exc_info
          • True:Exception 異常會被添加到消息
        • extra
          • 傳遞 dict 附加到消息
          • extra 的 dict key 不應該與內建的衝突
      import logging
      
      FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
      logging.basicConfig(format=FORMAT)
      d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
      logger = logging.getLogger('tcpserver')
      logger.warning('Protocol problem: %s', 'connection reset', extra=d)
      # 2006-02-08 22:20:02,165 192.168.0.1 fbloggs  Protocol problem: connection reset
      
    • Logger.info(msg, *args, **kwargs)
      • 記錄級別為 INFO 的消息
      • 其參數的解釋與 debug() 相同
    • Logger.warning(msg, *args, **kwargs)
      • 記錄級別為 WARNING 的消息
      • 其參數的解釋與 debug() 相同
    •  Logger.error(msg, *args, **kwargs)
      • 記錄級別為 ERROR 的消息
      • 其參數的解釋與 debug() 相同 
    • Logger.critical(msg, *args, **kwargs)
      • 記錄級別為 CRITICAL 的消息
      • 其參數的解釋與 debug() 相同
    • Logger.log(lvl, msg, *args, **kwargs)
      • 記錄級別為 lvl (int) 的消息
      • 其參數的解釋與 debug() 相同 
    • Logger.exception(msg, *args, **kwargs)
      • 記錄以 ERROR 為級別的異常消息,應該只在 excpet 中調用
      • 其參數的解釋與 debug() 相同
      • 同 exc_info 設為 True
    • Logger.addFilter(filt)
      • 加入 filter
      import logging
      
      class NoParsingFilter(logging.Filter):
          def filter(self, record):
              return not record.getMessage().startswith('parsing')
      
      logger = logging.getLogger('test')
      logger.addFilter(NoParsingFilter())
      print(logger.filters)
      # [<__main__.NoParsingFilter object at 0x0000000002AE7C88>] 
      
    • Logger.removeFilter(filt)
      • 移除 filter
      import logging
      
      class NoParsingFilter(logging.Filter):
          def filter(self, record):
              return not record.getMessage().startswith('parsing')
      
      logger = logging.getLogger('test')
      logger.addFilter(NoParsingFilter())
      print(logger.filters)
      # [<__main__.NoParsingFilter object at 0x0000000002AE7C88>] 
      logger.removeFilter(logger.filters[0])
      print(logger.filters)
      # []
      
    • Logger.filters
      • 此 logger 擁有的 filters
    • Logger.filter(record)
      • 對 logRecord 應用 filters
        如果符合返回 True,並傳遞給 handlers
        任一個 filter 返回 False,將不會做任何處理
    • Logger.addHandler(hdlr)
      • 加入 handler
    • Logger.removeHandler(hdlr)
      • 刪除 handler
    • Logger.handlers
      • 此 logger 擁有的 handlers 
    • Logger.hasHandlers()
      • 是否擁有 handlers
    • Logger.findCaller(stack_info=False)
      • 回傳 4 元素 tuple,檔名, 行數, 函數名 和 stack 訊息,stack_info 需設為 True 才會有 stack 訊息,不然皆為 None
    • Logger.handle(record)
      • 處理一個 logRecord,將之傳給該 logger 及其父輩的所有 handler,直到propagate 為 False
    • Logger.makeRecord(name, lvl, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None)
      • 這是一個工廠方法,可以在子類中覆蓋它來創建特定的 logRecord 實例

Handler Objects

請勿直接初始化 Handler
  • class Handler(level=NOTSET)
    • 可用 handler
    • createLock()
      • 初始化線程鎖,用以序列化訪問底層的可能非線程安全的I/O機制
    • acquire()
      • 獲取 createLock() 創建的線程鎖
    • release()
      • 釋放由 acquire() 獲取的線程鎖
    • setLevel(lvl)
      • 設置 handler 級別為 lvl
    • setFormatter(form)
      • 設置 formatter
    • addFilter(filt)
      • 加入 filter
    • removeFilter(filt)
      • 移除 filter
    • filter(record)
      • 將 handler 的 filter 應用到 logRecord
    • flush()
      • 確保所有的日誌輸出被刷新。base 不做任何事情,需實現
    • close()
      • 清除 handler 所使用的資源
    • handle(record)
      • 處理一個 logRecord,符合 filters 則記錄。包含對 I/O 線程鎖的獲取/釋放過程
    • handleError(record)
      • 在 handler 中調用 emit() 發生異常時,應調用該方法
    • format(record)
      • 格式化 logRecord
    • emit(record)
      • 做記錄日誌 logRecord 真正需要的動作。base 只拋出 NotImplementedError,需實現

Filter Objects

可用來過濾訊息
  • class logging.Filter(name='')
    • 返回 Filter 類實例
    • name
      • 若是一個 logger 的名字,該 logger 及其子代皆通過該過濾器
        若是空字符串,所有的事件都允許
    • filter(record)
      • 特定的記錄是否要記下來? 0 為否,非 0 為是
      • 可在此修改字串或添加變數
    import logging
    from random import choice
    
    class ContextFilter(logging.Filter):
        """
        This is a filter which injects contextual information into the log.
    
        Rather than use actual contextual information, we just use random
        data in this demo.
        """
    
        USERS = ['jim', 'fred', 'sheila']
        IPS = ['123.231.231.123', '127.0.0.1', '192.168.0.1']
    
        def filter(self, record):
    
            record.ip = choice(ContextFilter.IPS)
            record.user = choice(ContextFilter.USERS)
            return True
    
    if __name__ == '__main__':
        levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
        logging.basicConfig(level=logging.DEBUG,
                            format='%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s')
        a1 = logging.getLogger('a.b.c')
        a2 = logging.getLogger('d.e.f')
    
        f = ContextFilter()
        a1.addFilter(f)
        a2.addFilter(f)
        a1.debug('A debug message')
        a1.info('An info message with %s', 'some parameters')
        for x in range(10):
            lvl = choice(levels)
            lvlname = logging.getLevelName(lvl)
            a2.log(lvl, 'A message at %s level with %d %s', lvlname, 2, 'parameters')
    
    # 2010-09-06 22:38:15,292 a.b.c DEBUG    IP: 123.231.231.123 User: fred     A debug message
    # 2010-09-06 22:38:15,300 a.b.c INFO     IP: 192.168.0.1     User: sheila   An info message with some parameters
    # 2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1       User: sheila   A message at CRITICAL level with 2 parameters
    # 2010-09-06 22:38:15,300 d.e.f ERROR    IP: 127.0.0.1       User: jim      A message at ERROR level with 2 parameters
    # 2010-09-06 22:38:15,300 d.e.f DEBUG    IP: 127.0.0.1       User: sheila   A message at DEBUG level with 2 parameters
    # 2010-09-06 22:38:15,300 d.e.f ERROR    IP: 123.231.231.123 User: fred     A message at ERROR level with 2 parameters
    # 2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 192.168.0.1     User: jim      A message at CRITICAL level with 2 parameters
    # 2010-09-06 22:38:15,300 d.e.f CRITICAL IP: 127.0.0.1       User: sheila   A message at CRITICAL level with 2 parameters
    # 2010-09-06 22:38:15,300 d.e.f DEBUG    IP: 192.168.0.1     User: jim      A message at DEBUG level with 2 parameters
    # 2010-09-06 22:38:15,301 d.e.f ERROR    IP: 127.0.0.1       User: sheila   A message at ERROR level with 2 parameters
    # 2010-09-06 22:38:15,301 d.e.f DEBUG    IP: 123.231.231.123 User: fred     A message at DEBUG level with 2 parameters
    # 2010-09-06 22:38:15,301 d.e.f INFO     IP: 123.231.231.123 User: fred     A message at INFO level with 2 parameters
    

Formatter Objects

設定字串的表示方法
  • class logging.Formatter(fmt=None, datefmt=None, style='%')
    • 回傳一個設定好的 Formatter instance
    • formatter = logging.Formatter('{asctime:s}', datefmt='%a, %d %b %Y %H:%M:%S', style='{')
      •  Sat, 20 Aug 2016 13:46:51
    • 參數
      • fmt
        • 字串格式 
      • datefmt
        • 日期時間格式
      • style
        • %
          • 最原始的字串方法
        • {
          • string.format
        • $
          • string.Template
      • format(record)
        • 格式化 logRecord,並返回字串結果
      • formatTime(record, datefmt=None)
        • 被 format() 調用,以格式化時間
      • formatException(exc_info)
        • 格式化 Exception,並返回字串結果
      • formatStack(stack_info)
        • 格式化 stack 訊息,並返回字串結果

LogRecord Objects

Logger 每次記錄日誌時都會建立 LogRecord 實例,也可以調用 makeLogRecord() 來手動創建 LogRecord 實例(例如從網絡上收到序列化過的事件)
  • class logging.LogRecord(name, level, pathname, lineno, msg, args, exc_info, func=None, sinfo=None)
    • 參數
      • name
        • 該 LogRecord 的 logger 的名字
      • level
        • 記錄事件的數字級別(DEBUG、INFO等)
        • 被轉成 LogRecord 的兩個屬性
          • levelno 表示數字值
          • levelname 表示對應的級別名稱
      • pathname
        • 文件的完整路徑名
      • lineno
        • 文件的行號
      • msg
        • 事件的消息,可為格式化字符串
      • args
        • msg 的參數
      • exc_info
        • 帶當前異常消息的異常 tuple,如果沒有異常為 None
      • func
        • 所在的函數/方法名
      • sinfo
        • stack 資訊
    • 方法
      • getMessage()
        • 返回格式化後的消息
    • 屬性
    • Attribute nameFormat Description
      args 不應該被格式化 參數 tuple
      exc_info 不應該被格式化 異常 tuple
      msg 不應該被格式化 原始的格式化字符串
      stack_info 不應該被格式化 Stack 訊息
      asctime %(asctime)s 時間日期,默認形式為『2003-07-08 16:49:45,896』(逗號後面的數字表示時間的毫秒部分)
      created %(created)f LogRecord 創建的時間
      filename %(filename)s module 的文件名
      funcName %(funcName)s 函數名
      levelname %(levelname)s 消息級別
      levelno %(levelno)s 消息級別數字
      lineno %(lineno)d 行數
      module %(module)s module 名
      msecs %(msecs)d LogRecord 創建時間中的毫秒部分
      message %(message)s 用戶輸出的消息
      name %(name)s logger 名字
      pathname %(pathname)s 文件的完整路徑名(如果可用)
      process %(process)d 進程 ID (如果可用)
      processName %(processName)s 進程名(如果可用)
      relativeCreated %(relativeCreated)d LogRecord 創建離 logging loaded 的毫秒數
      thread %(thread)d 線程 ID (如果可用)
      threadName %(threadName)s 線程名字(如果可用)

LoggerAdapter Objects

給 logger 調用以供重新修改消息
  • class logging.LoggerAdapter(logger, extra)
    • 以底層的 Logger 物件和一個類字典對象來初始化
    • process(msg, kwargs)
      • 修改傳給日誌調用的消息和關鍵字參數,以插入上下文消息,返回值是(msg, kwargs)
    import logging
    
    class CustomAdapter(logging.LoggerAdapter):
        """
        This example adapter expects the passed in dict-like object to have a
        'connid' key, whose value in brackets is prepended to the log message.
        """
        def process(self, msg, kwargs):
            return '[%s] %s' % (self.extra['connid'], msg), kwargs
    
    logger = logging.getLogger(__name__)
    adapter = CustomAdapter(logger, {'connid': 123})
    # adapter.process('456', {'1':'2'})
    # ('[123] 456', {'1': '2'})
    

Module logging

  • 方法
    • logging.getLogger(name=None)
      • 得到 logger,無名字表示回傳 root
    • logging.getLoggerClass()
      • 返回標準的 Logger class,或最後一次傳給 setLoggerClass() 的 class
      • class MyLogger(logging.getLoggerClass()):
            # ... 覆寫行為
        
    • logging.setLoggerClass(klass)
      • 指定 logger 的 class 為 klass,需在建立任何 logger 前調用
    • logging.getLogRecordFactory()
      • 返回標準建立 LogRecord 的 callable,或最後一次傳給 setLogRecordFactory() 的 callable
    • logging.setLogRecordFactory(factory)
      • 指定建立 logRecord 的 callable
      • factory(name, level, fn, lno, msg, args, exc_info, func=None, sinfo=None, **kwargs)
        • name
          • logger 名字
        • level
          • logging level (numeric)
        • fn
          • file 的完整路徑
        • lno
          • 行數
        • msg
          • 事件的消息,可為格式化字符串
        • args
          • msg 的參數
        • exc_info
          • An exception tuple or None.
        • func
          • 函數名字
        • sinfo
          • stack 資訊
        • kwargs
          • 額外的 dict
    • logging.debug(msg, *args, **kwargs)
      • 根 logger 記錄級別為 DEBUG 的消息
      • msg
        • 消息格式字符串
      • *args
        • 通過字符串格式操作符合併到 msg 的參數
      • **kwargs
        • exc_info
          • True:Exception 異常會被添加到消息
        • extra
          • 傳遞 dict 附加到消息
          • extra 的 dict key 不應該與內建的衝突
      import logging
      
      FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
      logging.basicConfig(format=FORMAT)
      d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
      logging.warning('Protocol problem: %s', 'connection reset', extra=d)
      # 2006-02-08 22:20:02,165 192.168.0.1 fbloggs  Protocol problem: connection reset
      
    • logging.info(msg, *args, **kwargs)
      • 根 logger 記錄級別為 INFO 的消息 
      • 參數同 debug()
    • logging.warning(msg, *args, **kwargs) 
      • 根 logger 記錄級別為 INFO 的消息 
      • 參數同 debug()
    • logging.error(msg, *args, **kwargs)
      • 根 logger 記錄級別為 ERROR 的消息 
      • 參數同 debug()
    • logging.critical(msg, *args, **kwargs)
      • 根 logger 記錄級別為 CRITICAL 的消息 
      • 參數同 debug()
    • logging.exception(msg, *args, **kwargs)
      • 根 logger 記錄級別為 ERROR 的 Exception 異常消息 
      • 參數同 debug()
    • logging.log(level, msg, *args, **kwargs)
      • 根 logger 記錄級別為 level 的消息 
      • 參數同 debug()
    • logging.disable(lvl)
      • 對所有 logger 禁用級別 lvl 以下的訊息,包括 lvl
        如果 logging.disable(logging.NOTSET) 被調用,所有 logger 回復原本 lvl
    • logging.addLevelName(lvl, levelName)
      • 自定級別,但最好不要這麼做
    • logging.getLevelName(lvl)
      • 得到級別名字
    • logging.makeLogRecord(attrdict)
      • 創建並返回 LogRecord 實例,attrdict 定義了其屬性
        可以在網絡上傳輸序列化過的 LogRecord 屬性字典,然後在接受端重建LogRecord 實例
    • logging.basicConfig(**kwargs)
      • 配置基本設定
      • 若根 logger 已經配置了 handler 則該函數什麼都不做,故若已初始化,則無法再次初始化
      • 參數
        • filename
          • 存入檔名
        • filemode 
          • 寫入的方式 (w, w+, a+)
        • format
          • 指定表示的方式
        • datefmt
          • 時間的表示
        • style 
          • formatter 使用 % or {} or $
        • level
          • 級別
        • stream
          • 指定輸出的 stream,與 filename 不相容,無法共用
        • handlers
          • 指定 handlers,與 filename & stream 不相容,無法共用
    • logging.shutdown()
      • 關閉所有 handlers,調用後不應該再繼續使用此日誌系統
  • 屬性
    • logging.lastResort
      • debug logging 用

參考

15.7. logging — Python的日誌工具¶
Python模塊學習——logging

留言