Python 日誌列印之logging.getLogger原始碼分析

授客發表於2021-01-17

日誌列印之logging.getLogger原始碼分析

By:授客 QQ:1033553122

#實踐環境

WIN 10

Python 3.6.5

 

#函式說明

logging.getLogger(name=None)

getLogger函式位於logging/__init__.py指令碼

 

#原始碼分析

 

 

_loggerClass = Logger
# ...略

root = RootLogger(WARNING)
Logger.root = root
Logger.manager = Manager(Logger.root)

# ...略

def getLogger(name=None):
    """
    Return a logger with the specified name, creating it if necessary.

    If no name is specified, return the root logger.
    """
    if name:
        return Logger.manager.getLogger(name)
    else:
        return root 

 

 

 

 

結論:如函式註釋所述,如果呼叫getLogger時,如果沒有指定函式引數(即要獲取的日誌列印器名稱)或者引數值不為真,則預設返回root列印器

 

 

##Logger.manager.getLogger(self, name)原始碼分析

 

該函式位於logging/__init__.py指令碼

 

 

class Manager(object):
    """
    There is [under normal circumstances] just one Manager instance, which
    holds the hierarchy of loggers.
    """
    def __init__(self, rootnode):
        """
        Initialize the manager with the root node of the logger hierarchy.
        """
        self.root = rootnode
        self.disable = 0
        self.emittedNoHandlerWarning = False
        self.loggerDict = {}
        self.loggerClass = None
        self.logRecordFactory = None

    def getLogger(self, name):
        """
        Get a logger with the specified name (channel name), creating it
        if it doesn't yet exist. This name is a dot-separated hierarchical
        name, such as "a", "a.b", "a.b.c" or similar.

        If a PlaceHolder existed for the specified name [i.e. the logger
        didn't exist but a child of it did], replace it with the created
        logger and fix up the parent/child references which pointed to the
        placeholder to now point to the logger.
        """
        rv = None
        if not isinstance(name, str):
            raise TypeError('A logger name must be a string')
        _acquireLock()
        try:
            if name in self.loggerDict:
                rv = self.loggerDict[name]
                if isinstance(rv, PlaceHolder):
                    ph = rv
                    rv = (self.loggerClass or _loggerClass)(name)
                    rv.manager = self
                    self.loggerDict[name] = rv
                    self._fixupChildren(ph, rv)
                    self._fixupParents(rv)
            else:
                rv = (self.loggerClass or _loggerClass)(name) # _loggerClass = Logger
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupParents(rv)
        finally:
            _releaseLock()
        return rv

  

 

###Logger原始碼分析

 

_nameToLevel = {
    'CRITICAL': CRITICAL,
    'FATAL': FATAL,
    'ERROR': ERROR,
    'WARN': WARNING,
    'WARNING': WARNING,
    'INFO': INFO,
    'DEBUG': DEBUG,
    'NOTSET': NOTSET,
}

# ...略

def _checkLevel(level):
    if isinstance(level, int):
        rv = level
    elif str(level) == level:
        if level not in _nameToLevel:
            raise ValueError("Unknown level: %r" % level)
        rv = _nameToLevel[level]
    else:
        raise TypeError("Level not an integer or a valid string: %r" % level)
    return rv

# ...略
class PlaceHolder(object):
    """
    PlaceHolder instances are used in the Manager logger hierarchy to take
    the place of nodes for which no loggers have been defined. This class is
    intended for internal use only and not as part of the public API.
    """
    def __init__(self, alogger):
        """
        Initialize with the specified logger being a child of this placeholder.
        """
        self.loggerMap = { alogger : None }

    def append(self, alogger):
        """
        Add the specified logger as a child of this placeholder.
        """
        if alogger not in self.loggerMap:
            self.loggerMap[alogger] = None



class Logger(Filterer):
    """
    Instances of the Logger class represent a single logging channel. A
    "logging channel" indicates an area of an application. Exactly how an
    "area" is defined is up to the application developer. Since an
    application can have any number of areas, logging channels are identified
    by a unique string. Application areas can be nested (e.g. an area
    of "input processing" might include sub-areas "read CSV files", "read
    XLS files" and "read Gnumeric files"). To cater for this natural nesting,
    channel names are organized into a namespace hierarchy where levels are
    separated by periods, much like the Java or Python package namespace. So
    in the instance given above, channel names might be "input" for the upper
    level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.
    There is no arbitrary limit to the depth of nesting.
    """
    def __init__(self, name, level=NOTSET):
        """
        Initialize the logger with a name and an optional level.
        """
        Filterer.__init__(self)
        self.name = name
        self.level = _checkLevel(level)
        self.parent = None
        self.propagate = True
        self.handlers = []
        self.disabled = False

    # ... 略

  

 

 

結論:如果呼叫logging.getLogger()時,有指定日誌列印器名稱,且名稱為真(不為空字串,0,False等False值),

1)如果名稱為不存在的日誌列印器名稱,則,且引數值為真,但是即要獲取的日誌列印器名稱)或者引數值不為真,則建立一個名為給定引數值的日誌列印器,該日誌列印器,預設級別預設為NOTSET,disable_existing_loggers配置為False,propagate配置為True。然後在日誌列印器字典中記錄該名稱和日誌列印器的對映關係,接著呼叫 _fixupParents(建立的日誌列印器例項)類例項方法--為日誌列印器設定上級日誌列印器,最後返回該日誌列印器。

2)如果名稱已存在日誌列印器名稱,則獲取該日誌列印器,然後判斷日誌列印器是否為PlaceHolder類例項,如果是,則建立一個名為所給引數值的日誌列印器,同第1)點,該日誌列印器,預設級別預設為NOTSET,disable_existing_loggers配置為False,propagate配置為True。然後在日誌列印器字典中記錄該名稱和日誌列印器的對映關係,接著呼叫 _fixupParents(建立的列印器例項)類例項方法,_fixupChildren(PlaceHolder類例項--根據名稱獲取的日誌列印器,新建的日誌列印器例項)--為新建日誌列印器設定上級日誌列印器,PlaceHolder類例項現有下級PlaceHolder日誌列印器例項重新設定上級日誌列印器,最後返回該日誌列印器。

 

 

###_fixupParents及_fixupChildren函式原始碼分析

# _fixupParents

# ...略
class Manager(object):
    # ...略
    def _fixupParents(self, alogger):
        """
        Ensure that there are either loggers or placeholders all the way
        from the specified logger to the root of the logger hierarchy.
        """
        name = alogger.name # 獲取日誌列印器名稱
        i = name.rfind(".") 
        rv = None # 存放alogger日誌列印器的上級日誌列印器
        while (i > 0) and not rv: # 如果名稱中存在英文的點,並且找到上級日誌列印器
            substr = name[:i] # 獲取名稱中位於最後一個英文的點的左側字串(暫且稱至為 點分上級)
            if substr not in self.loggerDict: # 如果 點分上級 不存在日誌列印器字典中
                self.loggerDict[substr] = PlaceHolder(alogger) # 建立PlaceHolder例項作為 點分上級 對應的日誌列印器 # 繼續查詢點分上級日誌列印器 # 注意,這裡的PlaceHolder僅是佔位用,不是真的列印器,這裡為了方便描述,暫且稱之為PlaceHolder日誌列印器
            else: # 否則
                obj = self.loggerDict[substr] # 獲取 點分上級 對應的日誌列印器
                if isinstance(obj, Logger): # 如果為Logger例項,如果是,則跳出迴圈,執行 # 為日誌列印器設定上級
                    rv = obj
                else: # 否則
                    assert isinstance(obj, PlaceHolder) # 斷言它為PlaceHolder的例項
                    obj.append(alogger) # 把日誌列印器新增為點分上級對應的PlaceHolder日誌列印器例項的下級日誌列印器 執行 # 繼續查詢點分上級日誌列印器
            i = name.rfind(".", 0, i - 1) # 繼續查詢點分上級日誌列印器
        if not rv: # 找不到點分上級、或者遍歷完所有點分上級,都沒找到上級日誌列印器
            rv = self.root # 則 把root日誌列印器設定為alogger日誌列印器的上級日誌列印器
        alogger.parent = rv # 為日誌列印器設定上級



    def _fixupChildren(self, ph, alogger):
        """
        Ensure that children of the placeholder ph are connected to the
        specified logger.
        """
        name = alogger.name  # 獲取日誌列印器名稱
        namelen = len(name)  # 獲取日誌列印器名稱長度 
        for c in ph.loggerMap.keys(): # 遍歷獲取的PlaceHolder日誌列印器例項的子級日誌列印器
            #The if means ... if not c.parent.name.startswith(nm)
            if c.parent.name[:namelen] != name: # 如果PlaceHolder日誌列印器例項名稱不以alogger日誌列印器名稱為字首,
                alogger.parent = c.parent # 那麼,設定alogger日誌列印器的上級日誌列印器為PlaceHolder日誌列印器
                c.parent = alogger # 設定alogger日誌列印器為PlaceHolder日誌列印器原有下級PlaceHolder日誌列印器的上級

  

 

結論:日誌列印器都是分父子級的,這個父子層級是怎麼形成的,參見上述函式程式碼註解

 

 

 

相關文章