Python 日誌列印之logging.config.dictConfig使用總結

授客發表於2021-01-09

日誌列印之logging.config.dictConfig使用總結

By:授客 QQ:1033553122

#實踐環境

WIN 10

Python 3.6.5

 

#函式說明

logging.config.dictConfig(config)

dictConfig函式位於logging.config模組,該函式通過字典引數config對logging進行配置。3.2版本新增的函式

 

##引數說明

config 字典型別,包含以下key:

 

    • version - 表示版本,該鍵值為從1開始的整數。該key必選,除此之外,其它key都是可選。

 

    • formatters - 日誌格式化器,其value值為一個字典,該字典的每個鍵值對都代表一個Formatter,鍵值對中,key代表Formatter ID(自定義ID),value為字典,描述如何配置相應的Formatter例項。預設格式為 ‘%(message)s

 

    • filters - 日誌過濾器,其value值為一個字典,該字典的每個鍵值對都代表一個Filter,鍵值對中,key代表Filter ID(自定義ID),value為字典,描述如何配置相應的Filter例項。

 

    • handlers - 日誌處理器,其value值為一個字典,該字典的每個鍵值對都代表一個Handler,鍵值對中,key代表Handler ID(自定義ID),value為字典,描述如何配置相應的Handler例項,包含以下配置key:

 

    • class (必選). 日誌處理器類全稱
    • level (可選). 指定該日誌處理器需要處理哪些級別的日誌,低於該級別的日誌將不被該handler處理。level可以為代表日誌級別的整數或者表大寫字串,字串日誌級別和數字日誌級別對應關係如下:               

    CRITICAL = 50

    FATAL = CRITICAL

    ERROR = 40

    WARNING = 30

    WARN = WARNING

    INFO = 20

    DEBUG = 10

    NOTSET = 0

 

    下同,不再贅述.

 

    • formatter (可選). 指定該日誌處理器使用的日誌格式化器
    • filters (可選). 制定該日誌處理器使用的日誌過濾器

 

# 上述的class配置項的值,可以使用自定義Handler類,此時,如果自定義Handler類的__init__建構函式還需要其它引數來初始化類例項,可以繼續添自定義引數,這些自定義引數被當做關鍵字引數會自動傳遞給建構函式。

 

一個例子:

    "handlers": {
        "console":{
            "class":"study.MyLogHandler",
            "formatter":"brief",
            "level":"INFO"
        },
        "file": {
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "precise",
      "filename": "logconfig.log",
      "maxBytes": 1024,
      "backupCount": 3
        }
    }

  

 

id為console的日誌處理器被例項化為一個logging.StreamHandler,使用sys.stout作為基礎例項流。id為file的日誌處理器則被例項化為具有關鍵字引數filename ='logconfig.log',maxBytes = 1024,backupCount = 3的 logging.handlers.RotatingFileHandler

 

 

 

    • loggers - 日誌記錄器,其value值為一個字典,該字典的每個鍵值對都代表一個Handler,鍵值對中,key代表Handler ID,value為字典,描述如何配置相應的Logger例項,包含以下配置key:

 

      • level (可選). 指定該日誌記錄器需要記錄哪些級別的日誌,低於該級別的日誌將不被該logger記錄。
      • propagate (可選). 指定該日誌記錄器的propagation配置,為布林值,即True 或 False,用於控制是否向上遍歷父輩日誌列印器,進而控制當前日誌列印器是否共享父輩列印器的日誌處理器。True,向上遍歷,否則不向上遍歷。
      • filters (可選). 指定該日誌記錄器使用的日誌過濾器
      • handlers (可選). 制定該日誌記錄器使用的日誌處理器

 

    • root - root logger配置。除了不支援propagate配置項以外,該配置的處理過程同處理其它logger的配置一樣,配置規則也一樣

 

    • incremental - 用於判斷該config配置是否解釋為現有配置的增量配置,還是覆蓋原有配置。預設為False,即使用現有fileConfig()API使用的相同語義替換現有配置

 

 

  • disable_existing_loggers - 其value為布林值,表示是否禁用現有日誌記錄器(root logger除外),預設值為True,即禁用。如果incremental 鍵值為True,則忽略該配置項

 

 

 

 

 

 

#程式碼示例1

 

study.py

 

study.py

#!/usr/bin/env python
# -*- coding:utf-8 -*-


'''
@CreateTime: 2020/12/29 14:08
@Author : shouke
'''

import logging
import logging.config

LOGGING_CONFIG = {
    "version": 1,
    "formatters": {
        "default": {
            'format':'%(asctime)s %(filename)s %(lineno)s %(levelname)s %(message)s',
        },
        "plain": {
            "format": "%(message)s",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "default",
        },
        "console_plain": {
            "class": "logging.StreamHandler",
            "level":logging.INFO,
            "formatter": "plain"
        },
        "file":{
            "class": "logging.FileHandler",
            "level":20,
            "filename": "./log.txt",
            "formatter": "default",
        }
    },
    "loggers": {
        "console_logger": {
            "handlers": ["console"],
            "level": "INFO",
            "propagate": False,
        },
        "console_plain_logger": {
            "handlers": ["console_plain"],
            "level": "DEBUG",
            "propagate": False,
        },
        "file_logger":{
            "handlers": ["file"],
            "level": "INFO",
            "propagate": False,
        }
    },
    "disable_existing_loggers": True,
}

# 執行測試
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("console_logger")
logger.debug('debug message')
logger.info('info message')
logger.warn('warning message')
logger.error('error message')
logger.critical('critical message')

  

 

執行study.py,結果輸出如下

2021-01-09 10:01:59,123 study.py 66 INFO info message

2021-01-09 10:01:59,123 study.py 67 WARNING warning message

2021-01-09 10:01:59,123 study.py 68 ERROR error message

2021-01-09 10:01:59,123 study.py 69 CRITICAL critical message

 

 

 

#程式碼示例2

 

基於程式碼示例1,修改LOGGING_CONFIG及getLogger函式引數

 

LOGGING_CONFIG = {
    "version": 1,
    "formatters": {
        "default": {
            'format':'%(asctime)s %(filename)s %(lineno)s %(levelname)s %(message)s',
        }
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "level": "INFO",
            "formatter": "default",
        }
    },
    "disable_existing_loggers": True,
    "root": {
        "handlers": ["console"],
        "level": "DEBUG"
    },
}

# 執行測試
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("root")
logger.debug('debug message')
logger.info('info message')
logger.warn('warning message')
logger.error('error message')
logger.critical('critical message')

  

 

執行study.py,結果輸出如下

2021-01-09 10:33:03,456 study.py 38 INFO info message

2021-01-09 10:33:03,456 study.py 39 WARNING warning message

2021-01-09 10:33:03,456 study.py 40 ERROR error message

2021-01-09 10:33:03,456 study.py 41 CRITICAL critical message

 

 

 

# 原始碼的角度分析propagate配置項

 

Logger類,位於logging/__init__.py

class Logger(Filterer):  
    #...略  

    def debug(self, msg, *args, **kwargs):
        """
        Log 'msg % args' with severity 'DEBUG'.

        To pass exception information, use the keyword argument exc_info with
        a true value, e.g.

        logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
        """
        if self.isEnabledFor(DEBUG):
            self._log(DEBUG, msg, args, **kwargs)

    def info(self, msg, *args, **kwargs):
        """
        Log 'msg % args' with severity 'INFO'.

        To pass exception information, use the keyword argument exc_info with
        a true value, e.g.

        logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
        """
        if self.isEnabledFor(INFO):
            self._log(INFO, msg, args, **kwargs)
    
    #...略 

    def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
        """
        Low-level logging routine which creates a LogRecord and then calls
        all the handlers of this logger to handle the record.
        """
        sinfo = None
        if _srcfile:
            #IronPython doesn't track Python frames, so findCaller raises an
            #exception on some versions of IronPython. We trap it here so that
            #IronPython can use logging.
            try:
                fn, lno, func, sinfo = self.findCaller(stack_info)
            except ValueError: # pragma: no cover
                fn, lno, func = "(unknown file)", 0, "(unknown function)"
        else: # pragma: no cover
            fn, lno, func = "(unknown file)", 0, "(unknown function)"
        if exc_info:
            if isinstance(exc_info, BaseException):
                exc_info = (type(exc_info), exc_info, exc_info.__traceback__)
            elif not isinstance(exc_info, tuple):
                exc_info = sys.exc_info()
        record = self.makeRecord(self.name, level, fn, lno, msg, args,
                                 exc_info, func, extra, sinfo)
        self.handle(record)

    def handle(self, record):
        """
        Call the handlers for the specified record.

        This method is used for unpickled records received from a socket, as
        well as those created locally. Logger-level filtering is applied.
        """
        if (not self.disabled) and self.filter(record):
            self.callHandlers(record)


    def hasHandlers(self):
        """
        See if this logger has any handlers configured.

        Loop through all handlers for this logger and its parents in the
        logger hierarchy. Return True if a handler was found, else False.
        Stop searching up the hierarchy whenever a logger with the "propagate"
        attribute set to zero is found - that will be the last logger which
        is checked for the existence of handlers.
        """
        c = self
        rv = False
        while c:
            if c.handlers:
                rv = True
                break
            if not c.propagate:
                break
            else:
                c = c.parent
        return rv


    def callHandlers(self, record):
        """
        Pass a record to all relevant handlers.

        Loop through all handlers for this logger and its parents in the
        logger hierarchy. If no handler was found, output a one-off error
        message to sys.stderr. Stop searching up the hierarchy whenever a
        logger with the "propagate" attribute set to zero is found - that
        will be the last logger whose handlers are called.
        """
        c = self
        found = 0
        while c:
            for hdlr in c.handlers:
                found = found + 1
                if record.levelno >= hdlr.level:
                    hdlr.handle(record)
            if not c.propagate: 
                c = None    #break out
            else:
                c = c.parent
        if (found == 0):
            if lastResort:
                if record.levelno >= lastResort.level:
                    lastResort.handle(record)
            elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
                sys.stderr.write("No handlers could be found for logger"
                                 " \"%s\"\n" % self.name)
                self.manager.emittedNoHandlerWarning = True

  

 

 

預設的,當通過logger.debug,logger.info的方式列印日誌時,會先判斷對應日誌級別是否開啟,如果開啟,則呼叫logger例項的_log方法,接著經過一連串的函式呼叫(self._log() -> self.handle -> self.callHandlers),如上,self.callHandlers中,會先遍歷當前日誌列印器自身的所有日誌處理器,處理日誌訊息,然後判斷propagate屬性是否為True,如果為True,則獲取上級日誌列印器,繼續遍歷其日誌處理器,處理訊息,否則不遍歷上級

 

另外,檢視hasHandlers函式可知,判斷一個logger是否有日誌處理器,也用到了propagate,如果propagate為True,則遍歷父級日誌列印器,看其是否存在日誌處理器,如果父級或者父輩日誌列印器存在日誌處理器,則判斷該logger擁有日誌處理器。

 

由此可見,propagate功能就是用於控制是否向上遍歷父輩日誌列印器,進而控制當前日誌列印器是否共享父輩列印器的日誌處理器。

 

相關文章