說一說Python logging

來源:互聯網
上載者:User
最近有個需求是把以前字串輸出的log 改為json 格式,看了別人的例子,還是有些比較茫然,索性就把logging 整個翻了一邊,做點小總結.

初看log

在程式中, log 的用處寫代碼的你用你知道,log 有等級,DEBUG, INFO,...之類,還會記錄時間,log 發生的位置,在Python 中用的多的就是logging 這個標準庫中的包了.當打log 的時候究竟發生了什麼? 是如何把不同層級的log 輸出到不同檔案裡,還能在控制台輸出.......

最簡單的用法

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
1,第一行匯入包 2,第二行利用basicConfig 對輸出的格式,和輸出層級做了限制 3, 後面分別輸出了三條不同層級的 log

Logging Levels

共有幾個等級, 每個等級對應一個Int 型整數 ,每個等級都會有一個方法與之對應,這樣輸出的內容就有了不同的等級.

logger 流程,

整個過程,還是不是很詳細,貼個圖吧, 現在看還太早,也說不清真箇過程到底發生了什麼,先放著,回頭來看會比較好懂. loger flow

讀代碼

代碼結構

logging 在源碼中有三個檔案,結構如下:

├── config.py
├── handlers.py
└── __init__.py
_int.py中實現了基礎功能,主要的邏輯就在這個檔案中 handlers.py 是一些Handlers (用處後面會明白)用起來很方便的. config.py 是對配置做處理的方法.

objects

LogRecord Objects

每一次log 都會執行個體化一個Record 對象,這個對象有很多屬性,最後對LogRecord 做一下format 就輸出了,格式化的log ,裡面就基本就是這個對象的屬性了。

class LogRecord(object):  def __init__(self, name, level, pathname, lineno,         msg, args, exc_info, func=None):    ct = time.time()    self.name = name    self.msg = msg    if (args and len(args) == 1 and isinstance(args[0], collections.Mapping)      and args[0]):      args = args[0]    self.args = args    self.levelname = getLevelName(level)    self.levelno = level    self.pathname = pathname    try:      self.filename = os.path.basename(pathname)      self.module = os.path.splitext(self.filename)[0]    except (TypeError, ValueError, AttributeError):      self.filename = pathname      self.module = "Unknown module"    self.exc_info = exc_info    self.exc_text = None   # used to cache the traceback text    self.lineno = lineno    self.funcName = func    self.created = ct    self.msecs = (ct - long(ct)) * 1000    self.relativeCreated = (self.created - _startTime) * 1000    if logThreads and thread:      self.thread = thread.get_ident()      self.threadName = threading.current_thread().name    else:      self.thread = None      self.threadName = None    if not logMultiprocessing:      self.processName = None    else:      self.processName = 'MainProcess'      mp = sys.modules.get('multiprocessing')      if mp is not None:        try:          self.processName = mp.current_process().name        except StandardError:          pass    if logProcesses and hasattr(os, 'getpid'):      self.process = os.getpid()    else:      self.process = None  def __str__(self):    return ''%(self.name, self.levelno,      self.pathname, self.lineno, self.msg)  def getMessage(self):     pass

看代碼就發現, 這個類沒做什麼事情,就是一個model 而已, 有一個得到msg 的方法

Formatter Objects

Formatter 就是對Record 專門格式化的對象,它有一個format 方法,我們實現這個方法就能 做到不同的輸出,我的需求是做json 格式的log 其實關鍵就在寫一個Formatter 就好了

class Formatter(object):  converter = time.localtime  def __init__(self, fmt=None, datefmt=None):    if fmt:      self._fmt = fmt    else:      self._fmt = "%(message)s"    self.datefmt = datefmt  def formatTime(self, record, datefmt=None):    pass  def formatException(self, ei):    pass  def usesTime(self):    return self._fmt.find("%(asctime)") >= 0  def format(self, record):    pass

刪掉原始碼中的實現細節,這個類裡面主要的是format 方法,這是預設最基本的Formater ,還有專門對exception ,時間做格式化的方法。具體是哪個,看方法名就很清楚了,具體每個方法怎麼實現的,一眼也就懂了。fmt 是制定格式化的,具體怎麼指定在最基礎的用法中就有例子,datefmt 是對時間格式的指定。

Filter Objects

這個類是Logger 和Handler 的基類,主要有一個Filter 方法,和一個filters 屬性

Handler Objects

叫Handler 的類還真的不少,在SocketServer 中也有看到,具體的功能都在Handler 中.在這裡,組合所有的Formatter ,和控制log 的輸出的方向,繼承自Filter.

 def __init__(self, level=NOTSET):    Filterer.__init__(self)    self._name = None    self.level = _checkLevel(level)    self.formatter = None    _addHandlerRef(self)    self.createLock()

在init方法中看到,Handler 也有一個屬性,通過把自身的屬性和LogRecord 的level對比來決定是否處理這個LogRecord 的。每個Handler 都有一個Formatter 屬性,其實就是上面介紹的Formatter 。Handler 就是來控制LogRecord 和Formatter 的,它還可以控制輸出的方式,在後面會有,StreamHandler,FileHandler等。通過名稱也就能明白具體能幹什麼,這就是編程取名的智慧。

Logger Objects

這個類通常會通過getLogger()或者getLogger(name)來得到,不會直接new 一個出來.它會有info(msg, *args, kwargs) ,warn(msg, args, *kwargs)等方法,

  def __init__(self, name, level=NOTSET):    Filterer.__init__(self)    self.name = name    self.level = _checkLevel(level)    self.parent = Noneou    self.handlers = []    self.disabled = 0

從init方法中能看到handlers 屬性,這是一個list ,每個LogRecord 通過Handlers 不同的handlers 就能以不同的格式輸出到不同的地方了。每個Logger 可以通過addHandler(hdlr)方法來添加各種Handler, 知道這些你就基本可以隨意定製化了 下面就是我實現的json 格式的Formater,支援控制台顏色變化,當然前提是你的控制終端支援(Ubuntu14.04測試通過)

import reimport loggingimport socketimport jsonimport tracebackimport datetimeimport timetry:  from collections import OrderedDictexcept ImportError:  passRESERVED_ATTRS = (  'args', 'asctime', 'created', 'exc_info', 'exc_text', 'filename',  'funcName', 'levelname', 'levelno', 'lineno', 'module',  'msecs', 'message', 'msg', 'name', 'pathname', 'process',  'processName', 'relativeCreated', 'stack_info', 'thread', 'threadName')RESERVED_ATTR_HASH = dict(zip(RESERVED_ATTRS, RESERVED_ATTRS))COLORS ={  'HEADER' : '\033[95m',  'INFO' : '\033[94m',  'DEBUG' : '\033[92m',  'WARNING' : '\033[93m',  'ERROR' : '\033[91m',  'ENDC' : '\033[0m',}def merge_record_extra(record, target, reserved=RESERVED_ATTR_HASH):  for key, value in record.__dict__.items():    if (key not in reserved      and not (hasattr(key, "startswith")           and key.startswith('_'))):      target[key] = value  return targetdef get_host_info():  host_name = ''  local_ip = ''  try:    host_name = socket.gethostname()    local_ip = socket.gethostbyname(host_name)  except Exception, e:    pass  return host_name, local_ipclass JsonFormatterBase(logging.Formatter):  def __init__(self, *args, **kwargs):    logging.Formatter.__init__(self, *args, **kwargs)    self._required_fields = self.parse()    self._skip_fields = dict(zip(self._required_fields,self._required_fields))    self._skip_fields.update(RESERVED_ATTR_HASH)  def parse(self):    standard_formatters = re.compile(r'\((.+?)\)', re.IGNORECASE)    return standard_formatters.findall(self._fmt)  def add_fields(self, record ):    log_record = {}    for field in self._required_fields:      log_record[field] = record.__dict__.get(field)    host_name , local_ip = get_host_info()    log_record[u'@hostName'] = host_name    log_record[u'@localIp'] = local_ip    return log_record    #merge_record_extra(record, log_record, reserved=self._skip_fields)  def process_log_record(self, log_record):    """    Override this method to implement custom logic    on the possibly ordered dictionary.    """    try:      new_record = OrderedDict()    except Exception, e:      return log_record    key_list = [      'asctime',      'levelname',      '@hostName',      '@localIp',      'threadName',      'thread',      'name',      'pathname',      'lineno',      'message',    ]    for k in key_list:      new_record[k] = log_record.get(k)    new_record.update(log_record)    return new_record  def jsonify_log_record(self, log_record):    """Returns a json string of the log record."""    return json.dumps(log_record, ensure_ascii=False)  def format_col(self, message_str, level_name):    """    是否需要顏色    """    return message_str  def formatTime(self, record, datefmt=None):    ct = self.converter(record.created)    if datefmt:      s = time.strftime(datefmt, ct)    else:      t = time.strftime("%Y-%m-%d %H:%M:%S", ct)      s = "%s.%03d" % (t, record.msecs)    return s  def format(self, record):    if isinstance(record.msg, dict):      record.message = record.msg    elif isinstance(record.msg, list) or isinstance(record.msg, tuple):      record.message = record.msg    elif isinstance(record.msg, basestring):      record.message = record.getMessage().split('\n')    elif isinstance(record.msg, Exception):      record.message = traceback.format_exc(record.msg).split('\n')    else :      record.message = repr(record.msg)    if "asctime" in self._required_fields:      record.asctime = self.formatTime(record, self.datefmt)    #    # if record.exc_info and not message_dict.get('exc_info'):    #   message_dict['message'] = traceback.format_exception(*record.exc_info)    log_record = self.add_fields(record)    log_record = self.process_log_record(log_record)    message_str = self.jsonify_log_record(log_record)    message_str = self.format_col(message_str, level_name=record.levelname)    return message_strclass ConsoleFormater(JsonFormatterBase):  def __init__(self, *args, **kwargs):    JsonFormatterBase.__init__(self, *args, **kwargs)  def format_col(self, message_str, level_name):    if level_name in COLORS.keys():      message_str = COLORS.get(level_name) + message_str + COLORS.get('ENDC')    return message_str  def jsonify_log_record(self, log_record):    return json.dumps(log_record, ensure_ascii=False, indent=4)class JsonFileFormater(JsonFormatterBase):  def __init__(self, *args, **kewars):    JsonFormatterBase.__init__(self, *args, **kewars)  def jsonify_log_record(self, log_record):    return json.dumps(log_record, ensure_ascii=False)

配置

很多時候我們並不是這樣自己去實現一些Handler ,Formater ,之類的代碼,用logging 提供的config 就能做到了,如何寫config下面舉個例子解釋下,

SC_LOGGING_CONF = {  "version": 1,  "disable_existing_loggers": False,  "formatters": {    "simple": {      "format": "%(asctime)s [%(levelname)s] [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] - %(message)s"    }  },  "handlers": {    "console": {      "class": "logging.StreamHandler",      "level": "DEBUG",      "formatter": "simple",      "stream": "ext://sys.stdout"    },    "info_file_handler": {      "class": "logging.handlers.RotatingFileHandler",      "level": "INFO",      "formatter": "simple",      "filename": PATH + "info-" + date.today().isoformat() + ".log",      "maxBytes": 10485760,      "backupCount": 20,      "encoding": "utf8"    },    "error_file_handler": {      "class": "logging.handlers.RotatingFileHandler",      "level": "ERROR",      "formatter": "simple",      "filename": PATH + "errors-" + date.today().isoformat() + ".log",      "maxBytes": 10485760,      "backupCount": 20,      "encoding": "utf8"    }  },    "": {      "level": "INFO",      "handlers": ["console", "info_file_handler", "error_file_handler"]    }  }}

首先定義了一個formater 叫simaple , 然後定義了三個Handler ,分別是輸出到控制台,輸出到檔案和info,error的。

logging.config.dictConfig(CONFIG.SC_LOGGING_CONF)
通過這句就能讓這些配置產生效果了,這也是config.py做的事情,不需要寫很多代碼也能定製個人化的log.。

以上就是本文的全部內容,希望對大家的學習有所協助。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.