本次分析一下Logger.info
的流程app
Logger.info
源碼: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)
註釋中反應了能夠經過 msg
和不定參數args
來進行日誌的格式化。
真實的調用爲:_log
方法:socket
Logger._log
方法: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)
最後兩行:函數
record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra, sinfo)
oop
self.handle(record)
ui
def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None): """ A factory method which can be overridden in subclasses to create specialized LogRecords. """ rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func, sinfo) if extra is not None: for key in extra: if (key in ["message", "asctime"]) or (key in rv.__dict__): raise KeyError("Attempt to overwrite %r in LogRecord" % key) rv.__dict__[key] = extra[key] return rv
調用_logRecordFactory
初始化一個日誌記錄實例,_logRecordFactory
其實就是LogRecord
類,初始化時,可能包含logger的name, level、調用的函數、行號、日誌字符串、模板參數、堆棧信息等。
再看extra
信息,extra
到底有何用?如今從代碼中能夠看到,只是更新到生成的日誌記錄實例的__dict__
中去.猜想:確定會在生成最終的日誌字符串的時候會用到。繼續往下看。this
self.handle(record)
:Logger
繼承自Filterer
,spa
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 filter(self, record): """ Determine if a record is loggable by consulting all the filters. The default is to allow the record to be logged; any filter can veto this and the record is then dropped. Returns a zero value if a record is to be dropped, else non-zero. .. versionchanged:: 3.2 Allow filters to be just callables. """ rv = True for f in self.filters: if hasattr(f, 'filter'): result = f.filter(record) else: result = f(record) # assume callable - will raise if not if not result: rv = False break return rv
能夠看到, 若是在handler中的filter中若是有返回爲False或空,則會屏蔽對應的record,返回True或部位空的值,則會將record放行。那麼咱們就能夠自定義本身的filter。rest
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
的全部handlers
去處理record
,for
循環部分,以後,若是當前的logger
的propagate
的值爲False
或空,則不向logger
的父logger
傳遞,即向上傳遞。日誌
Handler
中的 handler(record)
部分:def handle(self, record): """ Conditionally emit the specified logging record. Emission depends on filters which may have been added to the handler. Wrap the actual emission of the record with acquisition/release of the I/O thread lock. Returns whether the filter passed the record for emission. """ rv = self.filter(record) if rv: self.acquire() try: self.emit(record) finally: self.release() return rv
能夠看到, Handler
在處理record
時, 會去加鎖,而後調用self.emit(record)
方法去處理。code
emit(record)
def emit(self, record): """ Do whatever it takes to actually log the specified logging record. This version is intended to be implemented by subclasses and so raises a NotImplementedError. """ raise NotImplementedError('emit must be implemented ' 'by Handler subclasses')
看到須要由子類去實現,以StreamHandler
爲例子:
def emit(self, record): """ Emit a record. If a formatter is specified, it is used to format the record. The record is then written to the stream with a trailing newline. If exception information is present, it is formatted using traceback.print_exception and appended to the stream. If the stream has an 'encoding' attribute, it is used to determine how to do the output to the stream. """ try: msg = self.format(record) stream = self.stream stream.write(msg) stream.write(self.terminator) self.flush() except Exception: self.handleError(record)
Handler.format(record)
:def format(self, record): """ Format the specified record. If a formatter is set, use it. Otherwise, use the default formatter for the module. """ if self.formatter: fmt = self.formatter else: fmt = _defaultFormatter return fmt.format(record)
若是handler有自定義的formatter就用自定義的,若是沒有則用默認的Formatter
的實例, 初始化元源碼爲:
def __init__(self, fmt=None, datefmt=None, style='%'): """ Initialize the formatter with specified format strings. Initialize the formatter either with the specified format string, or a default as described above. Allow for specialized date formatting with the optional datefmt argument (if omitted, you get the ISO8601 format). Use a style parameter of '%', '{' or '$' to specify that you want to use one of %-formatting, :meth:`str.format` (``{}``) formatting or :class:`string.Template` formatting in your format string. .. versionchanged:: 3.2 Added the ``style`` parameter. """ if style not in _STYLES: raise ValueError('Style must be one of: %s' % ','.join( _STYLES.keys())) self._style = _STYLES[style][0](fmt) self._fmt = self._style._fmt self.datefmt = datefmt
有三個參數:
fmt
: 格式化模板datefmt
: 時間格式化參數style
: 日誌格式化的樣式。style有三種:
_STYLES = { '%': (PercentStyle, BASIC_FORMAT), '{': (StrFormatStyle, '{levelname}:{name}:{message}'), '$': (StringTemplateStyle, '${levelname}:${name}:${message}'),
能夠看出對應到:%
操做符的格式化, format
方法的格式化以及Template
的格式化。
Formatter的format
方法源碼爲:
def format(self, record): """ Format the specified record as text. The record's attribute dictionary is used as the operand to a string formatting operation which yields the returned string. Before formatting the dictionary, a couple of preparatory steps are carried out. The message attribute of the record is computed using LogRecord.getMessage(). If the formatting string uses the time (as determined by a call to usesTime(), formatTime() is called to format the event time. If there is exception information, it is formatted using formatException() and appended to the message. """ record.message = record.getMessage() if self.usesTime(): record.asctime = self.formatTime(record, self.datefmt) s = self.formatMessage(record) if record.exc_info: # Cache the traceback text to avoid converting it multiple times # (it's constant anyway) if not record.exc_text: record.exc_text = self.formatException(record.exc_info) if record.exc_text: if s[-1:] != "\n": s = s + "\n" s = s + record.exc_text if record.stack_info: if s[-1:] != "\n": s = s + "\n" s = s + self.formatStack(record.stack_info)
看到會調用record.getMessage()
,這裏僅僅是獲取咱們須要的日誌信息。
以後會調用s = self.formatMessage(record)
:
def formatMessage(self, record): return self._style.format(record)
實際上是調用了當前style的format
方法,以%
這一類型爲例PercentStyle
:
class PercentStyle(object): default_format = '%(message)s' asctime_format = '%(asctime)s' asctime_search = '%(asctime)' def __init__(self, fmt): self._fmt = fmt or self.default_format def usesTime(self): return self._fmt.find(self.asctime_search) >= 0 def format(self, record): return self._fmt % record.__dict__
從其中的format
方法能夠看出,是針對record
的__dict__
屬性中的全部參數進行格式化,這下,就清楚了以前的extra
參數是幹嗎用的了:能夠在formatter
中加入本身自定義的一些參數,如固定的用戶信息等等。
以後,將最終的message flush到對應的Stream裏面去就好了,就是整個流程:
請你們多多指點。