自定义twistd plugin的log格式

近学习twisted,从最初的直接调用reactor,到twisted app,最后到twisted plugin,一路用下来感觉还是挺顺手的。不过有一个问题挺郁闷的,就是没弄清楚怎么自定义输出的log格式。

我的需求应该还是挺简单的:

  1. 输出如同 20|YY-DD-MM HH:mm:ss|Foo service starting 这样的log格式,前面的数值和 logging 里面的相应log level对应
  2. 可以根据运行时的设置忽略某些level的消息

由于咋一眼没在文档里找到自定义的方法,便Google了一阵子,可只找到在maillist [1] 里有人和我有同样的需求,解决办法却没有见到……

于是只好从源码入手了,在twistd的代码里面,发现了如下片断:

class UnixAppLogger(app.AppLogger):
  # ......
  def _getLogObserver(self):
      # ......
      return log.FileLogObserver(logFile).emit

# ......
class UnixApplicationRunner(app.ApplicationRunner):
  """
  An ApplicationRunner which does Unix-specific things, like fork,
  shed privileges, and maintain a PID file.
  """
  loggerFactory = UnixAppLogger
  # ......

于是咱知道了twistd默认用了 FileLogObserver ,再去看 twisted.python.log ,发现格式代码果然在这儿躺着!

class FileLogObserver:
  # ......
  def emit(self, eventDict):
      # ......
      msgStr = _safeFormat("[%(system)s] %(text)s\n", fmtDict)

      util.untilConcludes(self.write, timeStr + " " + msgStr)
      # ......

找到了格式代码,这才发现twistd是绑死了log格式了。那咋整嘞?别忘了Python是一个动态语言,让我们来个monkey patch吧。

在项目的PYTHONPATH下建立一个名为 log_patch.py 的文件,首先要定义log处理函数。这儿在 FileLogObserver 中的 emit 的基础上进行修改:

from twisted.python import log, util
import logging

LEGAL_LOG_LEVEL = (logging.DEBUG, logging.ERROR, logging.INFO)

def emit(self, eventDict):
  text = log.textFromEventDict(eventDict)
  if text is None:
      return

  if 'logLevel' in eventDict:
      level = eventDict['logLevel']
      if not level in LEGAL_LOG_LEVEL:
          level = logging.INFO
  elif eventDict['isError']:
      level = logging.ERROR
  else:
      level = logging.INFO

  # if level < log.level: # 在patch之前,log是没有这个属性的
  #     return

  timeStr = self.formatTime(eventDict['time'])
  fmtDict = {'system': eventDict['system'], 'text': text.replace("\n", "\n\t")}
  msgStr = log._safeFormat("%(text)s\n", fmtDict)

  util.untilConcludes(self.write, '%02d|%s %s' % (level, timeStr, msgStr))
  util.untilConcludes(self.flush)

定义好处理函数,接下来就是动态地把 FileLogObserveremit 函数修改掉了:

# monkey patch twistd log format
if not hasattr(log.FileLogObserver, 'log_patch'):
  log.FileLogObserver.emit = emit
  # 阅读源码可以发现,FileLogObserver会先尝试使用timeFormat字段来格式化时间
  log.FileLogObserver.timeFormat = '%D %H:%M:%S'
  log.FileLogObserver.log_patch = True

格式处理这样就已经完成了,要使其生效,可以在自定义plugin使用的service的python文件里加上

import logpatch

试一下启动自定义的plugin

$ twistd -n foo

这下应该可以在标准输出中看到我们自定义的格式了:)

这个做完了,过滤低于运行级level的log信息就很容易实现了,首先需要之前把 emit函数 中注释的部分启用,然后添加一个设置运行级的函数

# ......
LEGAL_LOG_LEVEL = (logging.DEBUG, logging.ERROR, logging.INFO)
LOG_LEVEL_ARG = dict(enumerate(LEGAL_LOG_LEVEL))

def emit(self, eventDict):
  # ......
  if level < log.level:
      return
  # ......

def parseLogLevel(level = None):
  try:
      return LOG_LEVEL_ARG[int(level)]
  except:
      return logging.INFO

def setLogLevel(level = None, need_parse = False):
  if need_parse:
      level = parseLogLevel(level)
  elif not level in LEGAL_LOG_LEVEL:
      level = logging.INFO
  log.level = level

然后把我们的猴子改一下:

# monkey patch twistd log format
if not hasattr(log.FileLogObserver, 'log_patch'):
  log.FileLogObserver.emit = emit
  # 阅读源码可以发现,FileLogObserver会先尝试使用timeFormat字段来格式化时间
  log.FileLogObserver.timeFormat = '%D %H:%M:%S'
  setLogLevel()
  log.FileLogObserver.log_patch = True

现在我们的plugin的log就会默认忽略低于INFO级别的消息了。嗯?你说需要DEBUG怎么办?给plugin加个命令行参数,在启动service的时候根据命令行设置一下不就行了?你瞧 setLogLevel 不还提供了一个 need_parse 的参数来解析命令行输入么,我贴代码的时候还是想的满周到的,嗯:)

Note

后来翻看twisted的文档,发现应该是可以通过为log添加observer来实现自定义的目的的,又是在我做完了这一切之后,我囧……

[1]http://twistedmatrix.com/pipermail/twisted-python/2009-June/019805.html

发表评论

评论备注:

  1. 留言时的头像是Gravatar提供的服务。
  2. By submitting a comment here you grant this site a perpetual license to reproduce your words and name/web site in attribution. So, you don't fully own your words, so to speak.