日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Python如何僅用5000行代碼,實現(xiàn)強大的logging模塊?

Python 的 logging 模塊實現(xiàn)了靈活的日志系統(tǒng)。整個模塊僅僅 3 個類,不到 5000 行代碼的樣子,學(xué)習(xí)它可以加深對程序日志的了解,本文分下面幾個部分:

  • logging 簡介

  • logging API 設(shè)計

  • 記錄器對象 Logger

  • 日志記錄對象 LogRecord

  • 處理器對象 Hander

  • 格式器對象 Formatter

  • 滾動日志文件處理器

  • 小結(jié)

  • 小技巧

logging 簡介

本次代碼使用的是 python 3.8.5 的版本,官方中文文檔 3.8.8 。參考鏈接中官方中文文檔非常詳細,建議先看一遍了解日志使用。

功能
logging-module logging的API
Logger 日志記錄器對象類,可以創(chuàng)建一個對象用來記錄日志
LogRecord 日志記錄對象,每條日志記錄都封裝成一個日志記錄對象
Hander 處理器對象,負責日志輸出到流/文件的控制
Formatter 格式器,負責日志記錄的格式化
RotatingFileHandler 按大小滾動的日志文件記錄器
TimedRotatingFileHandler 按時間滾動的日志文件處理器

我們主要研究日志如何輸出到標準窗口這一主線;日志的配置,日志的線程安全及各種特別的Handler等支線可以先忽略。

logging API 設(shè)計

先看看日志使用:

 
 
 
 
  1. import logging
  2. logging.basicConfig(level=logging.INFO, format='%(levelname)-8s %(name)-10s %(asctime)s %(message)s')
  3. lang = {"name": "python", "age":20}
  4. logging.info('This is a info message %s', lang)
  5. logging.debug('This is a debug message')
  6. logging.warning('This is a warning message')
  7. logger = logging.getLogger(__name__)
  8. logger.warning('This is a warning')

輸出內(nèi)容如下:

 
 
 
 
  1. INFO     root       2021-03-04 00:03:53,473 This is a info message {'name': 'python', 'age': 20}
  2. WARNING  root       2021-03-04 00:03:53,473 This is a warning message
  3. WARNING  __main__   2021-03-04 00:03:53,473 This is a warning

可以看到 logging 的使用非常方便,模塊直接提供了一組API。

 
 
 
 
  1. root = RootLogger(WARNING)  # 默認提供的logger
  2. Logger.root = root
  3. Logger.manager = Manager(Logger.root)
  4. def debug(msg, *args, **kwargs): # info,warning等api類似
  5.     if len(root.handlers) == 0:
  6.         basicConfig()  # 默認配置
  7.     root.debug(msg, *args, **kwargs)
  8. def getLogger(name=None):
  9.     if name:
  10.         return Logger.manager.getLogger(name)  # 創(chuàng)建特定的logger
  11.     else:
  12.         return root  # 返回默認的logger

這種API的提供方式,我們在 requests 中也有看到。api中很重要的設(shè)置config的方式:

 
 
 
 
  1. def basicConfig(**kwargs):
  2.     ...
  3.     if handlers is None:
  4.         filename = kwargs.pop("filename", None)
  5.         mode = kwargs.pop("filemode", 'a')
  6.         if filename:
  7.             h = FileHandler(filename, mode)
  8.         else:
  9.             stream = kwargs.pop("stream", None)
  10.             h = StreamHandler(stream)  # 默認的handler
  11.         handlers = [h]
  12.     dfs = kwargs.pop("datefmt", None)
  13.     style = kwargs.pop("style", '%')
  14.     fs = kwargs.pop("format", _STYLES[style][1])
  15.     fmt = Formatter(fs, dfs, style)  # 生成formatter
  16.     for h in handlers:
  17.         if h.formatter is None:
  18.             h.setFormatter(fmt)
  19.         root.addHandler(h)  # 設(shè)置root的handler
  20.     level = kwargs.pop("level", None)
  21.     if level is not None:
  22.         root.setLevel(level)  # 設(shè)置日志級別

可以看到,日志的配置主要包括下面幾項:

  • level 日志級別

  • format 信息格式化模版

  • filename 輸出到文件

  • datefmt %Y-%m-%d %H:%M:%S,uuu 時間的格式模版
  • style [ % , { ,$] 格式樣板

演示代碼輸出中,可以看到debug日志沒有顯示,是因為 debug < info :

 
 
 
 
  1. CRITICAL = 50
  2. FATAL = CRITICAL
  3. ERROR = 40
  4. WARNING = 30
  5. WARN = WARNING
  6. INFO = 20
  7. DEBUG = 10
  8. NOTSET = 0

記錄器對象 Logger

查看Logger之前,先看logger對象的管理類Manager

 
 
 
 
  1. _loggerClass = Logger
  2. class Manager(object):
  3.     def __init__(self, rootnode):
  4.         self.root = rootnode
  5.         self.disable = 0
  6.         self.loggerDict = {}  # 所有日志記錄對象的字典
  7.     ...
  8.     def getLogger(self, name):
  9.         rv = None
  10.         if name in self.loggerDict:
  11.             rv = self.loggerDict[name]  # 獲取已經(jīng)創(chuàng)建過的同名logger
  12.             ...
  13.         else:
  14.             rv = (self.loggerClass or _loggerClass)(name)  # 創(chuàng)建新的logger
  15.             rv.manager = self
  16.             self.loggerDict[name] = rv
  17.             ...
  18.         return rv

日志過濾器

 
 
 
 
  1. class Filterer(object):
  2.     def __init__(self):
  3.         self.filters = []
  4.     def addFilter(self, filter):
  5.         self.filters.append(filter)
  6.     def removeFilter(self, filter):
  7.         self.filters.remove(filter)
  8.     def filter(self, record):
  9.         rv = True
  10.         for f in self.filters:  # 過濾日志
  11.             if hasattr(f, 'filter'):
  12.                 result = f.filter(record)
  13.             else:
  14.                 result = f(record) # assume callable - will raise if not
  15.             if not result:
  16.                 rv = False
  17.                 break
  18.         return r

核心的 Logger 實際上只是一個控制中心:

 
 
 
 
  1. class Logger(Filterer):  # logger可以過濾日志
  2.     def __init__(self, name, level=NOTSET):
  3.         Filterer.__init__(self)
  4.         self.name = name
  5.         self.level = _checkLevel(level)
  6.         self.parent = None  # 日志可以有層級
  7.         self.propagate = True
  8.         self.handlers = []  # 可以輸出到多個handler
  9.         self.disabled = False  # 可以關(guān)閉
  10.         self._cache = {}
  11.     
  12.     def debug(self, msg, *args, **kwargs):  # 輸出debug日志
  13.         if self.isEnabledFor(DEBUG):
  14.             self._log(DEBUG, msg, args, **kwargs)

logger可以判斷日志級別:

 
 
 
 
  1. def isEnabledFor(self, level):
  2.     if self.disabled:
  3.         return False
  4.     try:
  5.         return self._cache[level]
  6.     except KeyError:
  7.         try:
  8.             if self.manager.disable >= level:
  9.                 is_enabled = self._cache[level] = False
  10.             else:
  11.                 is_enabled = self._cache[level] = (
  12.                     level >= self.getEffectiveLevel()
  13.                 )
  14.         return is_enabled
  15. def getEffectiveLevel(self):
  16.     logger = self
  17.     while logger:
  18.         if logger.level:
  19.             return logger.level
  20.         logger = logger.parent
  21.     return NOTSET

日志輸出:

 
 
 
 
  1. def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False,
  2.          stacklevel=1):
  3.     ...
  4.     fn, lno, func = "(unknown file)", 0, "(unknown function)"
  5.     ...
  6.     # 生成日志記錄
  7.     record = self.makeRecord(self.name, level, fn, lno, msg, args,
  8.                              exc_info, func, extra, sinfo)
  9.     # 使用handler處理日志
  10.     self.handle(record)

日志記錄的生產(chǎn),就是創(chuàng)建一個LogRecord對象:

 
 
 
 
  1. _logRecordFactory = LogRecord
  2. def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
  3.                func=None, extra=None, sinfo=None):
  4.     ...
  5.     rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
  6.                          sinfo)
  7.     ...
  8.     return rv

使用logger對象的所有handler處理日志:

 
 
 
 
  1. def handle(self, record):
  2.     c = self
  3.     found = 0
  4.     while c:
  5.         for hdlr in c.handlers:  # 使用所有的handler處理日志
  6.             found = found + 1
  7.             if record.levelno >= hdlr.level:
  8.                 hdlr.handle(record)

root-logger的handler是在config中配置的:

 
 
 
 
  1. def basicConfig(**kwargs):
  2.     ...
  3.     root.addHandler(h)  # 設(shè)置root的handler

日志記錄對象 LogRecord

日志記錄對象非常簡單:

 
 
 
 
  1. class LogRecord(object):
  2.     def __init__(self, name, level, pathname, lineno,
  3.                  msg, args, exc_info, func=None, sinfo=None, **kwargs):
  4.         ct = time.time()
  5.         self.name = name  # logger名稱
  6.         self.msg = msg  # 日志標識信息
  7.         ...
  8.         self.args = args  # 變量
  9.         self.levelname = getLevelName(level)
  10.         ...
  11.     
  12.     def getMessage(self):
  13.         msg = str(self.msg)
  14.         if self.args:
  15.             msg = msg % self.args  # 格式化消息
  16.         return msg

處理器對象 Hander

頂級Handler定義了Handler的模版方法

 
 
 
 
  1. class Handler(Filterer):  # 處理器也可以過濾日志
  2.     def __init__(self, level=NOTSET):
  3.         Filterer.__init__(self)
  4.         self._name = None
  5.         self.level = _checkLevel(level)  # handler也有日志級別
  6.         self.formatter = None
  7.         _addHandlerRef(self)
  8.         self.createLock()
  9.         
  10.     def handle(self, record):  # 處理日志
  11.         rv = self.filter(record)  # 過濾日志
  12.         if rv:
  13.             self.acquire()  # 申請鎖
  14.             try:
  15.                 self.emit(record)  # 提交記錄,由不同子類實現(xiàn) 
  16.             finally:
  17.                 self.release()  # 釋放鎖
  18.         return rv

默認的console流 StreamHandler

 
 
 
 
  1. class StreamHandler(Handler):
  2.     terminator = '\n'  # 自動換行
  3.     def __init__(self, stream=None):
  4.         Handler.__init__(self)
  5.         if stream is None:
  6.             stream = sys.stderr  # 默認使用stderr輸出
  7.         self.stream = stream
  8.     
  9.     def emit(self, record):
  10.         try:
  11.             msg = self.format(record)  # 格式化日志記錄
  12.             stream = self.stream
  13.             stream.write(msg + self.terminator)  # 寫日志
  14.             self.flush()  # 刷新寫緩存
  15.         except Exception:
  16.             ...
  17.     
  18.     def format(self, record):
  19.         if self.formatter:
  20.             fmt = self.formatter
  21.         else:
  22.             fmt = _defaultFormatter
  23.         return fmt.format(record)  # 使用格式化器格式化日志記錄

為什么使用stderr,可以看下面的測試中的輸出都是到console:

 
 
 
 
  1. print("haha")
  2. print("fatal error", file=sys.stderr)
  3. sys.stderr.write("fatal error\n")

格式器對象 Formatter

格式化器主要使用Formatter和Style實現(xiàn)

 
 
 
 
  1. class Formatter(object):
  2.     def __init__(self, fmt=None, datefmt=None, style='%', validate=True):
  3.         self._style = _STYLES[style][0](fmt)
  4.         self._fmt = self._style._fmt
  5.         self.datefmt = datefmt
  6.     
  7.     def format(self, record):
  8.         record.message = record.getMessage()
  9.         s = self.formatMessage(record)
  10.         return s
  11.         
  12.     def formatMessage(self, record):
  13.         return self._style.format(record)  # 格式化

Style類

 
 
 
 
  1. class PercentStyle(object):
  2.     default_format = '%(message)s'
  3.     asctime_format = '%(asctime)s'
  4.     asctime_search = '%(asctime)'
  5.     validation_pattern = re.compile(r'%\(\w+\)[#0+ -]*(\*|\d+)?(\.(\*|\d+))?[diouxefgcrsa%]', re.I)
  6.     def __init__(self, fmt):
  7.         self._fmt = fmt or self.default_format
  8.     def usesTime(self):
  9.         return self._fmt.find(self.asctime_search) >= 0
  10.     def validate(self):
  11.         """Validate the input format, ensure it matches the correct style"""
  12.         if not self.validation_pattern.search(self._fmt):
  13.             raise ValueError("Invalid format '%s' for '%s' style" % (self._fmt, self.default_format[0]))
  14.     def _format(self, record):
  15.         return self._fmt % record.__dict__  # 格式化日志記錄對象
  16.     def format(self, record):
  17.         try:
  18.             return self._format(record)
  19.         except KeyError as e:
  20.             raise ValueError('Formatting field not found in record: %s' % e)

滾動日志文件處理器

線上的日志持續(xù)輸出到一個文件的話,會讓文件巨大,即有加劇了丟失的風險,也難以處理。通常有按照大小滾動或者按照日期滾動的方法,這個功能非常重要。先看滾動日志處理器模版:

 
 
 
 
  1. class BaseRotatingHandler(logging.FileHandler):
  2.     def emit(self, record):
  3.         try:
  4.             if self.shouldRollover(record): # 判斷是否需要滾動
  5.                 self.doRollover()  # 滾動日志
  6.             logging.FileHandler.emit(self, record)  # 輸出日志
  7.         except Exception:
  8.             self.handleError(record)
  9.     
  10.     def rotate(self, source, dest):
  11.         if not callable(self.rotator):
  12.             if os.path.exists(source):
  13.                 os.rename(source, dest)  # 重命名日志文件
  14.         else:
  15.             self.rotator(source, dest)

按大小滾動 RotatingFileHandler

按照文件大小滾動的處理器:

 
 
 
 
  1. class RotatingFileHandler(BaseRotatingHandler):
  2.     def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
  3.         if maxBytes > 0:
  4.             mode = 'a'
  5.         BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
  6.         self.maxBytes = maxBytes  # 單個文件大小上限
  7.         self.backupCount = backupCount  # 日志備份數(shù)量
  8.         
  9.     def doRollover(self):  # 執(zhí)行滾動
  10.         if self.stream:
  11.             self.stream.close()  # 關(guān)閉當前的流
  12.             self.stream = None
  13.         if self.backupCount > 0:
  14.             for i in range(self.backupCount - 1, 0, -1):
  15.                 sfn = self.rotation_filename("%s.%d" % (self.baseFilename, i))
  16.                 dfn = self.rotation_filename("%s.%d" % (self.baseFilename,
  17.                                                         i + 1))
  18.                 if os.path.exists(sfn):
  19.                     if os.path.exists(dfn):
  20.                         os.remove(dfn)
  21.                     os.rename(sfn, dfn)
  22.             dfn = self.rotation_filename(self.baseFilename + ".1")
  23.             if os.path.exists(dfn):
  24.                 os.remove(dfn)
  25.             self.rotate(self.baseFilename, dfn)  # 重命名文件
  26.         if not self.delay:
  27.             self.stream = self._open()  # 如果shouldRollover延遲,可以打開新的流
  28.     def shouldRollover(self, record):  # 判斷是否需要滾動
  29.         if self.stream is None:  # 立即打開流
  30.             self.stream = self._open()
  31.         if self.maxBytes > 0:   
  32.             msg = "%s\n" % self.format(record)
  33.             self.stream.seek(0, 2)  #due to non-posix-compliant Windows feature
  34.             if self.stream.tell() + len(msg) >= self.maxBytes:  # 判斷大小
  35.                 return 1
  36.         return 0

文件大小滾動就是在記錄日志時候判斷文檔是否超過上限,超過則重命名舊日志,生成新日志。

按照日期滾動 TimedRotatingFileHandler

按照日期滾動的處理器:

 
 
 
 
  1. class TimedRotatingFileHandler(BaseRotatingHandler):
  2.     def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
  3.         BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
  4.         self.when = when.upper()
  5.         self.backupCount = backupCount
  6.         self.utc = utc
  7.         self.atTime = atTime
  8.         # 日期設(shè)置,支持多種方式
  9.         if self.when == 'S':
  10.             self.interval = 1 # one second
  11.             self.suffix = "%Y-%m-%d_%H-%M-%S"
  12.             self.extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(\.\w+)?$"
  13.         ...
  14.         self.extMatch = re.compile(self.extMatch, re.ASCII)
  15.         self.interval = self.interval * interval # multiply by units requested
  16.         filename = self.baseFilename
  17.         if os.path.exists(filename):
  18.             t = os.stat(filename)[ST_MTIME]  # 最后修改時間
  19.         else:
  20.             t = int(time.time())
  21.         self.rolloverAt = self.computeRollover(t)  # 提前計算終止時間
  22.     def computeRollover(self, currentTime):
  23.         # 判斷的方法還是很長很復(fù)雜的,先pass
  24.     def shouldRollover(self, record):
  25.         t = int(time.time())
  26.         if t >= self.rolloverAt:  # 判斷是否到期
  27.             return 1
  28.         return 0
  29.     def doRollover(self):
  30.         ...
  31.         dfn = self.rotation_filename(self.baseFilename + "." +
  32.                                      time.strftime(self.suffix, timeTuple))
  33.         #  滾動日志文件
  34.         if os.path.exists(dfn):
  35.             os.remove(dfn)
  36.         self.rotate(self.baseFilename, dfn)
  37.         if self.backupCount > 0:
  38.             for s in self.getFilesToDelete():
  39.                 os.remove(s)
  40.         ...
  41.         # 計算下一個時間點
  42.         newRolloverAt = self.computeRollover(currentTime)
  43.         ...
  44.         self.rolloverAt = newRolloverAt

日期滾動就是計算最后時間點,超過時間點則重新生成新的日志文件。

小結(jié)

logging的處理邏輯大概是這樣的:

  • 創(chuàng)建Logger對象,提供API,用來接收應(yīng)用程序日志

  • Logger對象包括多個Handler

  • 每個Handler有一個Formatter對象

  • 每條日志都會生成一個LogRecord對象

  • 使用不同的Handler對象將LogRecored對象提交到不同的流

  • 每個日志對象通過Formatter格式化輸出

  • 可以使用按日期/文件大小的方式進行日志文件的滾動記錄

小技巧

覆蓋對象的 __reduce__ 方法,讓對象支持 reduce 函數(shù):

 
 
 
 
  1. class RootLogger(Logger):
  2.     def __init__(self, level):
  3.         Logger.__init__(self, "root", level)
  4.     def __reduce__(self):
  5.         return getLogger, ()

線程鎖的創(chuàng)建和釋放:

 
 
 
 
  1. _lock = threading.RLock()
  2. def _acquireLock():
  3.     if _lock:
  4.         _lock.acquire()
  5. def _releaseLock():
  6.     if _lock:
  7.         _lock.release()

線程鎖的使用:

 
 
 
 
  1. def addHandler(self, hdlr):
  2.     _acquireLock()
  3.     try:
  4.         self.handlers.append(hdlr)
  5.     finally:
  6.         _releaseLock()
  7. def removeHandler(self, hdlr):
  8.     _acquireLock()
  9.     try:
  10.         self.handlers.remove(hdlr)
  11.     finally:
  12.         _releaseLock()

參考鏈接

  • Logging in Python https://realpython.com/python-logging/

  • 日志操作手冊 https://docs.python.org/zh-cn/3.8/howto/logging-cookbook.html#cookbook-rotator-namer

  • Python 的日志記錄工具 https://docs.python.org/zh-cn/3.8/library/logging.html


網(wǎng)頁題目:Python如何僅用5000行代碼,實現(xiàn)強大的logging模塊?
標題鏈接:http://www.5511xx.com/article/djisshe.html