国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

Python向日志輸出中添加上下文信息

2019-11-25 16:09:25
字體:
供稿:網(wǎng)友

除了傳遞給日志記錄函數(shù)的參數(shù)(如msg)外,有時候我們還想在日志輸出中包含一些額外的上下文信息。比如,在一個網(wǎng)絡(luò)應用中,可能希望在日志中記錄客戶端的特定信息,如:遠程客戶端的IP地址和用戶名。這里我們來介紹以下幾種實現(xiàn)方式:

  • 通過向日志記錄函數(shù)傳遞一個extra參數(shù)引入上下文信息
  • 使用LoggerAdapters引入上下文信息
  • 使用Filters引入上下文信息

一、通過向日志記錄函數(shù)傳遞一個extra參數(shù)引入上下文信息

前面我們提到過,可以通過向日志記錄函數(shù)傳遞一個extra參數(shù)來實現(xiàn)向日志輸出中添加額外的上下文信息,如:

import loggingimport sysfmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")h_console = logging.StreamHandler(sys.stdout)h_console.setFormatter(fmt)logger = logging.getLogger("myPro")logger.setLevel(logging.DEBUG)logger.addHandler(h_console)extra_dict = {"ip": "113.208.78.29", "username": "Petter"}logger.debug("User Login!", extra=extra_dict)extra_dict = {"ip": "223.190.65.139", "username": "Jerry"}logger.info("User Access!", extra=extra_dict)

輸出:

2017-05-14 15:47:25,562 - myPro - 113.208.78.29 - Petter - User Login!
2017-05-14 15:47:25,562 - myPro - 223.190.65.139 - Jerry - User Access!

但是用這種方式來傳遞信息并不是那么方便,因為每次調(diào)用日志記錄方法都要傳遞一個extra關(guān)鍵詞參數(shù)。即便沒有需要插入的上下文信息也是如此,因為該logger設(shè)置的formatter格式中指定的字段必須要存在。所以,我們推薦使用下面兩種方式來實現(xiàn)上下文信息的引入。

也許可以嘗試在每次創(chuàng)建連接時都創(chuàng)建一個Logger實例來解決上面存在的問題,但是這顯然不是一個好的解決方案,因為這些Logger實例并不會進行垃圾回收。盡管這在實踐中不是個問題,但是當Logger數(shù)量變得不可控將會非常難以管理。

二、使用LoggerAdapters引入上下文信息

使用LoggerAdapter類來傳遞上下文信息到日志事件的信息中是一個非常簡單的方式,可以把它看做第一種實現(xiàn)方式的優(yōu)化版--因為它為extra提供了一個默認值。這個類設(shè)計的類似于Logger,因此我們可以像使用Logger類的實例那樣來調(diào)用debug(), info(), warning(),error(), exception(), critical()和log()方法。當創(chuàng)建一個LoggerAdapter的實例時,我們需要傳遞一個Logger實例和一個包含上下文信息的類字典對象給該類的實例構(gòu)建方法。當調(diào)用LoggerAdapter實例的一個日志記錄方法時,該方法會在對日志日志消息和字典對象進行處理后,調(diào)用構(gòu)建該實例時傳遞給該實例的logger對象的同名的日志記錄方法。下面是LoggerAdapter類中幾個方法的定義:

class LoggerAdapter(object): """ An adapter for loggers which makes it easier to specify contextual information in logging output. """ def __init__(self, logger, extra):  """  Initialize the adapter with a logger and a dict-like object which  provides contextual information. This constructor signature allows  easy stacking of LoggerAdapters, if so desired.  You can effectively pass keyword arguments as shown in the  following example:  adapter = LoggerAdapter(someLogger, dict(p1=v1, p2="v2"))  """  self.logger = logger  self.extra = extra def process(self, msg, kwargs):  """  Process the logging message and keyword arguments passed in to  a logging call to insert contextual information. You can either  manipulate the message itself, the keyword args or both. Return  the message and kwargs modified (or not) to suit your needs.  Normally, you'll only need to override this one method in a  LoggerAdapter subclass for your specific needs.  """  kwargs["extra"] = self.extra  return msg, kwargs def debug(self, msg, *args, **kwargs):  """  Delegate a debug call to the underlying logger, after adding  contextual information from this adapter instance.  """  msg, kwargs = self.process(msg, kwargs)  self.logger.debug(msg, *args, **kwargs)

通過分析上面的代碼可以得出以下結(jié)論:

  • 上下文信息是在LoggerAdapter類的process()方法中被添加到日志記錄的輸出消息中的,如果要實現(xiàn)自定義需求只需要實現(xiàn)LoggerAdapter的子類并重寫process()方法即可;
  • process()方法的默認實現(xiàn)中,沒有修改msg的值,只是為關(guān)鍵詞參數(shù)插入了一個名為extra的 key,這個extra的值為傳遞給LoggerAdapter類構(gòu)造方法的參數(shù)值;
  • LoggerAdapter類構(gòu)建方法所接收的extra參數(shù),實際上就是為了滿足logger的formatter格式要求所提供的默認上下文信息。

關(guān)于上面提到的第3個結(jié)論,我們來看個例子:

import loggingimport sys# 初始化一個要傳遞給LoggerAdapter構(gòu)造方法的logger實例fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s")h_console = logging.StreamHandler(sys.stdout)h_console.setFormatter(fmt)init_logger = logging.getLogger("myPro")init_logger.setLevel(logging.DEBUG)init_logger.addHandler(h_console)# 初始化一個要傳遞給LoggerAdapter構(gòu)造方法的上下文字典對象extra_dict = {"ip": "IP", "username": "USERNAME"}# 獲取一個LoggerAdapter類的實例logger = logging.LoggerAdapter(init_logger, extra_dict)# 應用中的日志記錄方法調(diào)用logger.info("User Login!")logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"})logger.extra = {"ip": "113.208.78.29", "username": "Petter"}logger.info("User Login!")logger.info("User Login!")

輸出結(jié)果:

# 使用extra默認值:{"ip": "IP", "username": "USERNAME"}2017-05-14 17:23:15,148 - myPro - IP - USERNAME - User Login!# info(msg, extra)方法中傳遞的extra方法沒有覆蓋默認值2017-05-14 17:23:15,148 - myPro - IP - USERNAME - User Login!# extra默認值被修改了2017-05-14 17:23:15,148 - myPro - 113.208.78.29 - Petter - User Login!2017-05-14 17:23:15,148 - myPro - 113.208.78.29 - Petter - User Login!

根據(jù)上面的程序輸出結(jié)果,我們會發(fā)現(xiàn)一個問題:傳遞給LoggerAdapter類構(gòu)造方法的extra參數(shù)值不能被LoggerAdapter實例的日志記錄函數(shù)(如上面調(diào)用的info()方法)中的extra參數(shù)覆蓋,只能通過修改LoggerAdapter實例的extra屬性來修改默認值(如上面使用的logger.extra=xxx),但是這也就意味著默認值被修改了。

解決這個問題的思路應該是:實現(xiàn)一個LoggerAdapter的子類,重寫process()方法。其中對于kwargs參數(shù)的操作應該是先判斷其本身是否包含extra關(guān)鍵字,如果包含則不使用默認值進行替換;如果kwargs參數(shù)中不包含extra關(guān)鍵字則取默認值。來看具體實現(xiàn):

import loggingimport sysclass MyLoggerAdapter(logging.LoggerAdapter): def process(self, msg, kwargs):  if 'extra' not in kwargs:   kwargs["extra"] = self.extra  return msg, kwargsif __name__ == '__main__': # 初始化一個要傳遞給LoggerAdapter構(gòu)造方法的logger實例 fmt = logging.Formatter("%(asctime)s - %(name)s - %(ip)s - %(username)s - %(message)s") h_console = logging.StreamHandler(sys.stdout) h_console.setFormatter(fmt) init_logger = logging.getLogger("myPro") init_logger.setLevel(logging.DEBUG) init_logger.addHandler(h_console) # 初始化一個要傳遞給LoggerAdapter構(gòu)造方法的上下文字典對象 extra_dict = {"ip": "IP", "username": "USERNAME"} # 獲取一個自定義LoggerAdapter類的實例 logger = MyLoggerAdapter(init_logger, extra_dict) # 應用中的日志記錄方法調(diào)用 logger.info("User Login!") logger.info("User Login!", extra={"ip": "113.208.78.29", "username": "Petter"}) logger.info("User Login!") logger.info("User Login!")

輸出結(jié)果:

# 使用extra默認值:{"ip": "IP", "username": "USERNAME"}2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!# info(msg, extra)方法中傳遞的extra方法已覆蓋默認值2017-05-22 17:35:38,499 - myPro - 113.208.78.29 - Petter - User Login!# extra默認值保持不變2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!2017-05-22 17:35:38,499 - myPro - IP - USERNAME - User Login!

OK! 問題解決了。

其實,如果我們想不受formatter的限制,在日志輸出中實現(xiàn)自由的字段插入,可以通過在自定義LoggerAdapter的子類的process()方法中將字典參數(shù)中的關(guān)鍵字信息拼接到日志事件的消息中。很明顯,這些上下文中的字段信息在日志輸出中的位置是有限制的。而使用'extra'的優(yōu)勢在于,這個類字典對象的值將被合并到這個LogRecord實例的__dict__中,這樣就允許我們通過Formatter實例自定義日志輸出的格式字符串。這雖然使得上下文信息中的字段信息在日志輸出中的位置變得與內(nèi)置字段一樣靈活,但是前提是傳遞給構(gòu)造器方法的這個類字典對象中的key必須是確定且明了的。

三、使用Filters引入上下文信息

另外,我們還可以使用自定義的Filter.Filter實例的方式,在filter(record)方法中修改傳遞過來的LogRecord實例,把要加入的上下文信息作為新的屬性賦值給該實例,這樣就可以通過指定formatter的字符串格式來輸出這些上下文信息了。

我們模仿上面的實現(xiàn),在傳遞個filter(record)方法的LogRecord實例中添加兩個與當前網(wǎng)絡(luò)請求相關(guān)的信息:ip和username。

import loggingfrom random import choiceclass ContextFilter(logging.Filter):  ip = 'IP'  username = 'USER'  def filter(self, record):   record.ip = self.ip   record.username = self.username   return Trueif __name__ == '__main__': levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL) users = ['Tom', 'Jerry', 'Peter'] ips = ['113.108.98.34', '219.238.78.91', '43.123.99.68'] logging.basicConfig(level=logging.DEBUG,      format='%(asctime)-15s %(name)-5s %(levelname)-8s %(ip)-15s %(username)-8s %(message)s') logger = logging.getLogger('myLogger') f = ContextFilter() logger.addFilter(f) logger.debug('A debug message') logger.info('An info message with %s', 'some parameters') for x in range(5):  lvl = choice(levels)  lvlname = logging.getLevelName(lvl)  filter.ip = choice(ips)  filter.username = choice(users)  logger.log(lvl, 'A message at %s level with %d %s', lvlname, 2, 'parameters')

輸出結(jié)果:

2017-05-15 10:21:49,401 myLogger DEBUG    IP              USER     A debug message
2017-05-15 10:21:49,401 myLogger INFO     IP              USER     An info message with some parameters
2017-05-15 10:21:49,401 myLogger INFO     219.238.78.91   Tom      A message at INFO level with 2 parameters
2017-05-15 10:21:49,401 myLogger INFO     219.238.78.91   Peter    A message at INFO level with 2 parameters
2017-05-15 10:21:49,401 myLogger DEBUG    113.108.98.34   Jerry    A message at DEBUG level with 2 parameters
2017-05-15 10:21:49,401 myLogger CRITICAL 43.123.99.68    Tom      A message at CRITICAL level with 2 parameters
2017-05-15 10:21:49,401 myLogger INFO     43.123.99.68    Jerry    A message at INFO level with 2 parameters

需要說明的是: 實際的網(wǎng)絡(luò)應用程序中,可能還要考慮多線程并發(fā)時的線程安全問題,此時可以把連接信息或者自定義過濾器的實例通過threading.local保存到到一個threadlocal中。

以上所述是小編給大家介紹的Python向日志輸出中添加上下文信息,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網(wǎng)網(wǎng)站的支持!

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 新建县| 民勤县| 庐江县| 广河县| 高陵县| 岢岚县| 麦盖提县| 永登县| 晋江市| 卓尼县| 鄯善县| 紫金县| 仁怀市| 江川县| 勐海县| 静海县| 喀什市| 大关县| 株洲市| 将乐县| 吉林市| 蓝田县| 牙克石市| 潢川县| 容城县| 鹤山市| 宝坻区| 内丘县| 顺昌县| 壶关县| 长汀县| 老河口市| 光泽县| 鹤山市| 双鸭山市| 南宫市| 资兴市| 杂多县| 青浦区| 清镇市| 永吉县|