详解Python自建logging模块


Posted in Python onJanuary 29, 2018

简单使用

最开始,我们用最短的代码体验一下logging的基本功能。

import logging
logger = logging.getLogger()
logging.basicConfig()
logger.setLevel('DEBUG')
logger.debug('logsomething')
#输出
out>>DEBG:root:logsomething

第一步,通过logging.getLogger函数,获取一个loger对象,但这个对象暂时是无法使用的。
第二步,logging.basicConfig函数,进行一系列默认的配置,包括format、handler等。
第三步,logger调用setLevel函数定义日志级别为DEBUG 最后,调用debug函数,输出一条debug级别的message,显示在了标准输出上。 logging中的日志级别

logging在生成日志的时候,有一个日志级别的机制,默认有以下几个日志级别:

CRITICAL = 50
ERROR = 40
WARNING = 30
INFO 20
DEBUG = 10
NOTEST = 0

每一个logger对象,都有一个日志级别,它只会输出高于它level的日志。如果一个logger的level是INFO,那么调用logger.debug()是无法输出日志的,而logger.warning()能够输出。

一般来说,以上的6个日志级别完全满足我们日常使用了。

logging中的基础类

logging是python的一个基础模块,它在python中的源码位置如下:

#主干代码
/usr/lib/python2.7/logging/__init__.py
#扩展的handler和config
/usr/lib/pyhon2.7/logging/config.py
/usr/lib/python2.7/loging/handlers.py

组成logging的主干的几个基础类都在__init__.py中:

第一个基础类LogRecord

一个LogRecord对象,对应了日志中的一行数据。通常包含:时间、日志级别、message信息、当前执行的模块、行号、函数名...这些信息都包含在一个LogRecord对象里。
LogRecord对象可以想象成一个大字典:

class LogRecord(object):
 #代表一条日志的类
 def getMessage(self):
  #获取self.msg
 def markLogRecord(dict):
 #这个方法很重要,生成一个空的LogRecord,然后通过一个字典,直接更新LogReocrd中的成员变量
 rv = LogRecord(None, None, "", 0, "", (), None, None)
 rv.__dict__.update(dict)
 return rv

第二个基础类Formatter

Formatter对象是用来定义日志格式的,LogRecord保存了很多信息,但是打印日志的时候我们只需要其中几个,Formatter就提供了这样的功能,它依赖于python的一个功能:

#通过字典的方式,输出格式化字符串
print('%(name)s:%(num)d'%{'name':'my_name', 'num' : 100})
out >>>my_name:100
如果说LogRecord是后面的那个字典,那么Formatter就是前面的那个格式字符串...的抽象

重要的代码如下:

class Formatter(object):
 def __init__(self, fmt=None, datefmt = None):
  if fmt:
   self._fmt = fmt
  else:
   #默认的format
   self._fmt = "%(message)s"
 def format(self, record)
  #使用self._fmt进行格式化
  s = self._fmt %record.__dict__
  return s

第三个基础类Filter和Filterer

Filter类,功能很简单。Filter.filter()函数传入一个LogRecord对象,通过筛选返回1,否则返回0.从代码中可以看到,其实是对LogRecord.name的筛选。

Filterer类中有一个Filter对象的列表,它是一组Filter的抽象。

重要的代码如下:

class Filter(object):
 def __init__(self, name=''):
  self.name = name
  self.nlen = len(name)
 def filter(self, record):
  #返回1表示record通过,0表示record不通过
  if self.nlen == 0:
   return 1
  elif self.name == record.name:
   return 1
  #record.name不是以filter开头
  elif record.name.find(self.name, 0, self.nlen) != 0:
   return 0
  #最后一位是否为
  return (record.name[self.nlen] == '.')
class Filterer(object):
 #这个类其实是定义了一个self.filters = []的列表管理多个filter
 def addFilter(self, filter):
 def removefilter(self, filter):
 def filter(self, record):
 #使用列表中所有的filter进行筛选,任何一个失败都会返回0
 #例如:
  #filter.name = 'A', filter2.name='A.B', filter2.name = 'A, B, C'
  #此时record.name = 'A,B,C,D'这样的record才能通过所有filter的筛选

logging中的高级类

有了以上三个基础的类,就可以拼凑一些更重要的高级类了,高级类可以实现logging的重要功能。

Handler——抽象了log的输出过程 Handler类继承自Filterer。Handler类时log输出这个过程的抽象。
同时Handler类具有一个成员变量self.level,在第二节讨论的日志级别的机制,就是在Handler中实现的。
Handler有一个emit(record)函数,这个函数负责输出log,必须在Handler的子类中实现。

重要代码如下:

class Handler(Filterer):
 def __init__(self, level = NOTEST)
  #handler必须有level属性
  self.level = _checkLevel(level)
 def format(self, record):
  #使用self.formatter, formattercord
 def handler(self, record):
  #如果通过filter的筛选,则emit这条log
  rv = self.filter(record)
  self.emit(record)
 def emit(self, record):
  #等待子类去实现

接下来看两个简单的handler的子类,其中在logging源码中,有一个handler.py专门定义了很多复杂的handler,有的可以将log缓存在内存中,有的可以将log做rotation等。

StreamHandler
最简单的handler实现,将log写入一个流,默认的stream是sys.stderr

重要的代码如下:

class StreamHandler(Handler):
 def __init__(self, stream = None):
  if stream is None:
   stream = sys.stderr
  self.stream = stream
 def emit(self, record):
  #将record的信息写入流
  #处理一些编码的异常
  fs = '%s\n' #每条日志都有换行
  stream = self.stream
  stream.write(fs%msg)

FileHandler

将log输出到文件的handler,继承StreamHandler

重要代码如下:

class FileHandler(StreamHandler):
 def __init__(self, filename, mode='a')
  #append方式打开一个文件
  StreamHandler.__init__(self, self._open())
 def emit(self, record):
  #和streamhandler保持一致
  StreamHandler.emit(self, record)

Logger——一个独立的log管道

什么是logger?

+ logger类继承自Filterer,

+ logger对象有logger.level日志级别

+ logger对象控制多个handler:logger.handlers = []

+ logger对象之间存在福字关系

简单的来说,logger这个类,集中了我们以上所有的LogRecord、Filter类、Formatter类、handler类。首先,logger根据输入生成一个LogRecord读写,经过Filter和Formatter之后,再通过self.handlers列表中的所有handler,把log发送出去。

一个logger中可能有多个handler,可以实现把一份log放到任意的位置。

class Logger(Filterer):
 def __init__(self, name, level=NOTEST)
  #handler列表
  self.handlers = []
  self.level = _checklevel(level)
 def addHandler(self, hdlr):
 def removeHandler(self, hdlr):
 def _log(self, level, msg, args, exc_info=None, extra=None):
  #在_log函数中创建了一个LogRecord对象
  record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
  #交给handle函数
  self.handle(record)
 def handle(self, reord):
  #进行filter,然后调用callHandlers
  if(not self.disabled) and self.filter(record):
   self.callHandlers(record)
 def callHandlers(self, record):
  #从当前logger到所有的父logger,递归的handl传入的record
  c = self
  while c:
   for hdlr in c.handlers:
    hdlr.handle(record) #进入handler的emit函数发送log
   ....
   c = c.parent

LoggerAdapter——对标准logger的一个扩展

LogRecord这个大字典中提供的成员变量已经很多,但是,如果在输出log时候仍然希望能够夹带一些自己想要看到的更多信息,例如产生这个log的时候,调用某些函数去获得其他信息,那么就可以把这些添加到Logger中,LoggerAdapter这个类就起到这个作用。

LoggerAdapter这个类很有意思,如果不做什么改动,那么LoggerAdapter类和Logger并没有什么区别。LoggerAdapter只是对Logger类进行了一下包装。

LoggerAdapter的用法其实是在它的成员函数process()的注释中已经说明了:

def process(self, msg, kwargs):
 '''
 Normally,you'll only need to overwrite this one method in a LoggerAdapter subclass for your specific needs.
 '''

也就是说重写process函数,以下是一个例子:

import logging
import random
L=logging.getLogger('name')
#定义一个函数,生成0~1000的随机数
def func():
 return random.randint(1,1000)
class myLogger(logging.LoggerAdapter):
 #继承LoggerAdapter,重写process,生成随机数添加到msg前面
 def process(self,msg,kwargs):
  return '(%d),%s' % (self.extra['name'](),msg) ,kwargs
#函数对象放入字典中传入 
LA=myLogger(L,{'name':func})
#now,do some logging
LA.debug('some_loging_messsage')
out>>DEBUG:name:(167),some_loging_messsage
Python 相关文章推荐
Python列表append和+的区别浅析
Feb 02 Python
在Django框架中编写Contact表单的教程
Jul 17 Python
用Python3创建httpServer的简单方法
Jun 04 Python
对numpy Array [: ,] 的取值方法详解
Jul 02 Python
Windows 8.1 64bit下搭建 Scrapy 0.22 环境
Nov 18 Python
Python使用Pickle模块进行数据保存和读取的讲解
Apr 09 Python
Python 抓取微信公众号账号信息的方法
Jun 14 Python
python获取地震信息 微信实时推送
Jun 18 Python
python中update的基本使用方法详解
Jul 17 Python
python3获取控制台输入的数据的具体实例
Aug 16 Python
python rsa-oaep加密的示例代码
Sep 23 Python
Python中用xlwt制作表格实例讲解
Nov 05 Python
python抓取网页中链接的静态图片
Jan 29 #Python
Python实现识别手写数字 Python图片读入与处理
Mar 23 #Python
Python实现PS滤镜特效Marble Filter玻璃条纹扭曲效果示例
Jan 29 #Python
Python实现识别手写数字大纲
Jan 29 #Python
django文档学习之applications使用详解
Jan 29 #Python
Python实现PS滤镜Fish lens图像扭曲效果示例
Jan 29 #Python
python实现识别手写数字 python图像识别算法
Mar 23 #Python
You might like
PHP反转字符串函数strrev()函数的用法
2012/02/04 PHP
php中Array2xml类实现数组转化成XML实例
2014/12/08 PHP
全面解读PHP的人气开发框架Laravel
2015/10/15 PHP
深入解析WordPress中加载模板的get_template_part函数
2016/01/11 PHP
详解Yii2.0使用AR联表查询实例
2017/06/16 PHP
常用Extjs工具:Extjs.util.Format使用方法
2012/03/22 Javascript
jquery zTree异步加载简单实例分享
2013/02/05 Javascript
jquery提取元素里的纯文本不包含span等里的内容
2013/09/30 Javascript
jquery左边浮动到一定位置时显示返回顶部按钮
2014/06/05 Javascript
js阻止事件追加的具体实现
2014/10/15 Javascript
jQuery操作JSON的CRUD用法实例
2015/02/25 Javascript
jquery预加载图片的方法
2015/05/27 Javascript
jQuery实现简单的列表式导航菜单效果代码
2015/08/31 Javascript
JavaScript 七大技巧(一)
2015/12/13 Javascript
javascript事件冒泡简单示例
2016/06/20 Javascript
vue指令只能输入正数并且只能输入一个小数点的方法
2018/06/08 Javascript
Vue的路由及路由钩子函数的实现
2019/07/02 Javascript
微信小程序文章列表功能完整实例
2020/06/03 Javascript
python实现从字典中删除元素的方法
2015/05/04 Python
Python下载指定页面上图片的方法
2016/05/12 Python
python redis 删除key脚本的实例
2019/02/19 Python
python实现从wind导入数据
2019/12/03 Python
python DataFrame转dict字典过程详解
2019/12/26 Python
python中执行smtplib失败的处理方法
2020/07/01 Python
Canon佳能美国官方商店:购买数码相机、数码单反相机、镜头和打印机
2016/11/15 全球购物
GANT葡萄牙官方商店:拥有美国运动服传统的生活方式品牌
2018/10/18 全球购物
自考自我鉴定范文
2013/10/30 职场文书
运动会入场词100字
2014/02/06 职场文书
应届中专生自荐书范文
2014/02/13 职场文书
初中班主任评语
2014/04/24 职场文书
2014年团员学习十八大思想汇报
2014/09/13 职场文书
英语专业毕业论文答辩开场白
2015/05/27 职场文书
2016年寒假社会实践活动总结
2015/10/10 职场文书
七年级生物教学反思
2016/02/20 职场文书
Redis如何一键部署脚本
2021/04/12 Redis
PyTorch中permute的使用方法
2022/04/26 Python