代碼解析python標準庫logging模塊
問題1:如何獲取caller的(文件名,行號,函數名)?
當新增一條log記錄時,最終將調用Logger類的_log方法,這個方法首先會創建一個LogRecord對象。LogRecord對象需要(filename, lineno, funcname)參數信息。這是通過如下語句得到的:
fn, lno, func = self.findCaller()
findCaller內容如下:
f = currentframe() #f是frame對象,每個方法調用生成一個frame對象,放在程序堆棧中。 if f is not None: f = f.f_back rv = "(unknown file)", 0, "(unknown function)" while hasattr(f, "f_code"): co = f.f_code #獲取code對象,它包含filename屬性,funcname屬性 filename = os.path.normcase(co.co_filename) if filename == _srcfile: #_srcfile是這個模塊文件自己的文件名,當文件名不再相同時 f = f.f_back # 得到外部調用者的frame,這就是需要的。 continue rv = (filename, f.f_lineno, co.co_name) break return rv
currentframe函數的定義:
def currentframe(): """Return the frame object for the caller's stack frame.""" try: raise Exception #拋出異常,將生成traceback對象,其中包含frame對象。 except: #sys.exc_traceback.tb_frame當前的frame, f_back調用著的frame return sys.exc_traceback.tb_frame.f_back #sys._getframe(3)返回的並不是當前的frame,3應該是計算好瞭的,減少循環的次數,返回的是logger.error()的frame if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3)
問題2: Logger對象的層級,父子關系如何實現的?
首先,logging模塊中logger層級關系是一個樹形關系的結構,這個關系的樹根是'root'。
root = RootLogger(WARNING) #RootLogger類是Logger的子類,無特殊功能,隻是定義名字為‘root'。 Logger.root = root Logger.manager = Manager(Logger.root)
當調用logging.getLogger(),用以獲取某個Logger時,如果參數為空,則返回‘root’。否則,調用Manager的getLogger()方法獲取Logger。
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
Manager的getLogger()定義如下:
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 _acquireLock() try: if name in self.loggerDict: rv = self.loggerDict[name] if isinstance(rv, PlaceHolder): ph = rv rv = _loggerClass(name) rv.manager = self self.loggerDict[name] = rv self._fixupChildren(ph, rv) self._fixupParents(rv) else: rv = _loggerClass(name) rv.manager = self self.loggerDict[name] = rv self._fixupParents(rv) finally: _releaseLock() return rv
Manager對象中的loggerDict字典,存放logger名字和logger對象的映射關系。PlaceHolder類,是一個容器。
例如,名字為'sell'的PlaceHolder對象,首先還不存在'sell'的logger,然後,所以以'sell‘開頭的logger在這個對象內都存在一個引用,如'sell.food','sell.cloth.china'等已有的logger對象。 當調用getLogger()獲取一個未存在的logger時,如名字為'level1.level2', 首先創建一個名字為'level1.level2'的logger對象,並存於loggerDict中。然後,調用_fixupParents()。
_fixupParents()的作用:
在這個名字的層級鏈上,找到第一個logger對象,將其作為父親,並返回。鏈上不是logger對象的名字,創建一個PlaceHolder對象(如果未創建),將自己加入其中。
例如,新增‘level1.level2.level3’的logger,調用_fixupParents將創建一個名字為'level1.level2‘的PlaceHolder對象,創建一個名字為’level1‘的PlaceHolder對象,並將’level1.level2.level3‘這個logger分別加入以上兩個PlaceHolder對象容器內,將它的父親設定為’root‘。
總之,_fixupParents是使logger對象指向真正的父親節點(logger對象),並將logger自己加入到所有上層的PlaceHolder對象容器內。
如果獲取一個名字已存在於loggerDict中,並且這個名字對應的是一個先前創建的PlaceHolder對象。首先,創建一個對應名字的logger對象。然後,調用_fixupChild(),修正這個PlaceHolder對象所包含的下遊logger對象的父親。最後,調用_fixupParent(),作用與上一步相同。
父子層級關系,主要作用是,當logger對象的propagate屬性值1(默認值)時,每條logRecord記錄都會傳給父logger處理。這樣可以隻需要定義好‘root’根logger對象,其他的logger定義個名字,根據模塊名,類名等,然後綁定一個NullHandler。最後,所有的logRecord將交給’root‘統一處理處理。這是多模塊產生統一格式log的方式。
以上就是代碼解析python標準庫logging模塊的詳細內容,更多關於python標準庫logging模塊的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 如何理解python接口自動化之logging日志模塊
- python 日志模塊logging的使用場景及示例
- Python 內置logging 使用詳細介紹
- python打印日志方法的使用教程(logging模塊)
- Python日志模塊logging用法