详解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 Tkinter简单布局实例教程
Sep 03 Python
各个系统下的Python解释器相关安装方法
Oct 12 Python
Python-嵌套列表list的全面解析
Jun 08 Python
Python读取图片属性信息的实现方法
Sep 11 Python
linux平台使用Python制作BT种子并获取BT种子信息的方法
Jan 20 Python
python访问抓取网页常用命令总结
Apr 11 Python
Python虚拟环境virtualenv的安装与使用详解
May 28 Python
python使用Tkinter实现在线音乐播放器
Jan 30 Python
浅谈python3.6的tkinter运行问题
Feb 22 Python
Django框架之中间件MiddleWare的实现
Dec 30 Python
解决Python 写文件报错TypeError的问题
Oct 23 Python
Python实现网络聊天室的示例代码(支持多人聊天与私聊)
Jan 27 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
一个可以找出源代码中所有中文的工具
2006/10/25 PHP
php初学者写及时补给skype用户充话费的小程序
2008/11/02 PHP
php实现从ftp服务器上下载文件树到本地电脑的程序
2009/02/10 PHP
php 正则表达式小结
2009/08/31 PHP
PHP实现动态web服务器方法
2015/07/29 PHP
PHP编程之设置apache虚拟目录
2016/07/08 PHP
jQuery 源码分析笔记(5) jQuery.support
2011/06/19 Javascript
Jquery attr("checked") 返回checked或undefined 获取选中失效
2013/10/10 Javascript
JS和函数式语言的三特性
2014/03/05 Javascript
javascript和jquery实现设置和移除文本框默认值效果代码
2015/01/13 Javascript
EasyUI闪屏EasyUI页面加载提示(原理+代码+效果图)
2016/02/21 Javascript
JavaScript中 ES6 generator数据类型详解
2016/08/11 Javascript
轻松实现js选项卡切换效果
2016/09/24 Javascript
JS实现的简易拖放效果示例
2016/12/29 Javascript
利用 spin.js 生成等待效果(js 等待效果)
2017/06/25 Javascript
js 发布订阅模式的实例讲解
2017/09/10 Javascript
JavaScript对象拷贝与Object.assign用法实例分析
2018/06/20 Javascript
深入浅析Node.js 事件循环、定时器和process.nextTick()
2018/10/22 Javascript
跟老齐学Python之正规地说一句话
2014/09/28 Python
python实现模拟按键,自动翻页看u17漫画
2015/03/17 Python
Python中getattr函数和hasattr函数作用详解
2016/06/14 Python
Python向Excel中插入图片的简单实现方法
2018/04/24 Python
Pyqt5实现英文学习词典
2019/06/24 Python
pytorch梯度剪裁方式
2020/02/04 Python
Django如何使用asyncio协程和ThreadPoolExecutor多线程
2020/10/12 Python
应届生人事助理求职信
2013/11/09 职场文书
中学家长会邀请函
2014/02/03 职场文书
数学高效课堂实施方案
2014/03/29 职场文书
心理健康活动总结
2014/04/30 职场文书
高校群众路线教育实践活动剖析材料
2014/10/10 职场文书
三峡人家导游词
2015/01/31 职场文书
离婚答辩状范文
2015/05/22 职场文书
2015年小学生国庆节演讲稿
2015/07/30 职场文书
SQL Server中交叉联接的用法详解
2021/04/22 SQL Server
SpringBoot整合JWT的入门指南
2021/06/29 Java/Android
mysql中关键词exists的用法实例详解
2022/06/10 MySQL