python logging日志打印模块实用手册

感谢

python logging模块使用教程这篇简书文章给了很多帮助,尤其是对于理解logging的四大神器:Logger,Handler,Formatter,Filter——描述的非常清晰了,可以先看下下面这篇文章,本文做了一些归纳和补充。

基本概念

日志级别

日志级别数值
CRITICAL50
ERROR40
WARNING30
INFO20
DEBUG10
NOTSET0

四大神器——Logger,Handler, Filter, Formatter

Logger——记录器,暴露了应用程序代码能直接使用的接口。
Handler——处理器,将(记录器产生的)日志记录发送至合适的目的地。
Filter——过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。
Formatter ——格式化器,指明了最终输出中日志记录的布局。

Logger——记录器

创建方式:logger = logging.getLogger(name)

Logger类永远不会直接实例化,而是始终通过模块级函数实例化 logging.getLogger(name),getLogger()具有相同名称的多次调用将始终返回对同一Logger对象的引用。 如果没有显式创建,默认创建一个名称为root的logger,并应用默认的日志级别(WARN),默认处理器Handler(StreamHandler,即屏幕打印),和默认格式化器Formatter(日志级别:logger名称:消息)。

主要函数用法
setLevel(level)设置记录器级别,高于level的日志才会被记录。如果Handler设置了更高的日志级别,则以Handler日志级别为准。
getChild(suffix)返回后代记录器,logging.getLogger(‘abc’).getChild(‘def.ghi’)与logging.getLogger(‘abc.def.ghi’)一致。
addFilter(filter)添加过滤器
removeFilter(filter)删除过滤器
addHandler(handler)添加处理器
removeHandler(handler)删除处理器

Handler——处理器

与Logger类一样,Handler不能被直接实例化,更甚于,Handler只是作为基类使用,实际使用的是其子类,常用的有StreamHanlder, FileHandler,RotatingFileHandler,TimedRotatingFileHandler

Handler类型主要用途
StreamHandler输出日志到标准输出设备(如屏幕)
FileHandler输出日志到文件
RotatingFileHandler输出到文件,支持文件达到一定大小后创建一个新的日志文件
TimedRotatingFileHandler输出到文件,支持经过一段时间后创建一个新的日志文件
StreamHandler——流处理器

创建方式:sh = logging.StreamHandler(stream=None) #如果不指定stream,默认输出到sys.stdout,通常就是屏幕

主要函数用法
emit(record)输出日志流到终端
flush()清除日志流
FilerHandler——文件处理器

创建方式:fh = logging.FileHandler(filename,mode=‘a’,encoding=None,delay=False)

主要函数用法
close()关闭文件
emit(record)输出文件流到终端
RotatingFileHandler——循环文件处理器

长期运行的程序(如web服务),日志文件会越来越大,大到可能难以读写——循环处理器就是解决这一问题——自动切分日志。

创建方式:rh = logging.RotatingFileHandler(filename,mode=‘a’,maxBytes=0, backupCount=0, encoding=None, delay=False)
maxBytes是当前日志最大字节数,backupCount是老日志保留个数。

  • 如果maxBytes或者backupCount任意一个等于0,则永不会触发循环,日志无限增长。
  • 如果当前日志达到设定的非零maxBytes,则新建一个日志文件,老的日志文件被重命名为后缀‘.1’,’.2’…等,如果新的日志又达到设定大小,则再新建一个日志,当前所有backup日志的后缀+1,最多保持backupCount个后缀,注意当前日志是没有后缀的。
TimeRotatingFileHandler——时间循环文件处理器

创建方式:rh = logging.TimedRotatingFileHandler(filename, when=‘h’, interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
when和interval共同决定循环时间间隔,when是单位,interval是数量。循环后老日志会自动加上一个后缀,当前日志是没有后缀的。

when间隔类型atTime用途
‘S’-
‘M’-
‘H’小时-
‘D’-
‘W0’ -‘ W6’周一-周六开始循环时刻
‘midnight’午夜(只有atTime没有指定时有效)开始循环时刻
  • 当when设置为W0到W6时,interval失效。backupCount=0则永远不会删除老日志。
  • 系统将老日志自动保存,添加一个以’%Y-%m-%d_%H-%M-%S’格式的拓展名。
  • 如果when设置成了1分钟,但一分钟内程序没有打印日志,则也不会自动循环(这个好理解,不然会造成很多空日志文件)。
其他Handler

logging库还有些更多Handler,比如

  • SocketHandler——支持TCP发送日志消息
  • DatagramHandler——支持UDP接口发送日志消息
  • SysLogHandler——支持远程或本地Unix系统日志消息
  • SMTPHandler——支持通过邮件发送日志消息
  • MemoryHandler——支持在内存中缓存日志消息
  • HTTPHandler——支持通过Web服务器Get或Post日志消息
  • QueueHandler——支持发送日志到消息队列,常用于Web服务中需要及时响应但后台处理可能很慢的情形下。( 与QueueListener配合使用 )
主要函数
主要函数用法
setLevel(level)设置记录器级别,高于level的日志才会被记录。如果Handler设置了更高的日志级别,则以Handler日志级别为准。
addFilter(filter)添加过滤器
removeFilter(filter)删除过滤器
addHandler(handler)添加处理器
removeHandler(handler)删除处理器
createLock()初始化一个线程锁,可用于序列化对可能不是线程安全的底层I / O功能的访问
acquire()获取使用创建的线程锁
release()释放获取的线程锁

Formatter——格式器

创建方式:formatter = logging.Formatter(fmt=None, datefmt=None)
fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用’%(message)s’。如果不指明datefmt,将使用ISO8601日期格式。

日志格式含义
%(levelno)s打印日志级别的数值
%(levelname)s打印日志级别名称
%(pathname)s打印当前执行程序的路径
%(filename)s打印当前执行程序名称
%(funcName)s打印日志的当前函数
%(lineno)d打印日志的当前行号
%(asctime)s打印日志的时间
%(thread)d打印线程id
%(threadName)s打印线程名称
%(process)d打印进程ID
%(message)s打印日志信息
%(module)s当前模块名
%(name)s打印这条消息的Logger名

Filter——过滤器

创建方式:filter= logging.Filter(name=’’)
包含了name的记录将会被过滤掉,不管是什么日志级别;如name=’’,则不过滤。

四大神器的关系大概是:

Logger可以包含一个或多个Handler和Filter,一个Handler可以新增多个Formatter和Filter,而且日志级别将会继承。

实例

最简单example

import logging
logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message') 

没有定义任何Logger、Handler、Formatter和Filter,直接用logging静态地调用日志输出接口。在这种情况下,隐式地会创建默认Logger名称为root,日志默认级别Warning,默认Handler为StreamHandler即打印到屏幕,采用默认Formatter——“Level:Logger name:Message”,默认Filter不过滤。因此输出:

WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

基础配置example

import logging
logging.basicConfig(level=logging.DEBUG, filename='debug.log',\
                        format='%(asctime)s:%(levelname)s:%(message)s',\
                        datefmt='%Y-%m-%d %H:%M:%S')
logging.debug('debug message')
logging.info('info message')
logging.warn('warn message')
logging.error('error message')
logging.critical('critical message')

标准输出(屏幕)未显示任何信息,当前工作目录下生成debug.log,内容为:

INFO:root:info message
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

子模块日志打印(最常用)

工作中通常会有多个模块都需要输出日志,默认属性Logger.propagate=True决定了,子模块的日志会继承父模块的设置,包括日志输出文件,格式等,而且打印顺序就是模块调用顺序。我们看一个例子,定义util.py和main.py

# util.py
import logging
def fun():
    logging.info('this is a log in util module')
# main.py
import logging
import util
logging.basicConfig(level=logging.INFO,\
                    filename='log.txt',\
                    filemode='w',\
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s: %(message)s')
def main():
    logging.info('main module start')
    util.fun()
    logging.info('main module stop')
if __name__ == '__main__':
    main()

运行main.py,输出log.txt文件:

019-04-14 17:39:28,330 - main.py[line:9] - root - INFO: main module start
2019-04-14 17:39:28,330 - util.py[line:4] - root - INFO: this is a log in util module
2019-04-14 17:39:28,330 - main.py[line:11] - root - INFO: main module stop

上面这段我们注意两个点:

  • 日志输出格式,我们加上了执行时间,文件名,行数,logger实例名,这些在调试的时候方便定位问题。
  • 没有显示实例化Logger,直接用logging调用接口函数打印,根据规则会隐式地创建Logger实例,默认名root,子模块继承了父模块的日志打印格式,子模块没有定义自己的日志格式。

关于上面的第2点,有一风险点——如果子模块有时候需要独立运行(这种情况实际很常见),同时也想定义自己的日志格式,我们能不能给子模块也加上basicConfig,例如:

# util.py
import logging
logging.basicConfig(level=logging.INFO,\
                    filename='log.txt',\
                    filemode='w',\
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s: %(message)s')
def fun():
    logging.info('this is a log in util module')
if __name == '__main__':
    fun()

运行util.py,可以正常打印。但如果此时运行main.py,则无法打印日志——父子模块的日志都不能打印。也就是说,basicConfig只能在父模块中定义,后代模块中不能重复设定,否则会发生冲突。——那么,如何解决父子模块日志冲突的问题?——显式地实例化Logger,如下:

# util.py
import logging

logger = logging.getLogger('Main.Son') #注意命名方式,父logger.子logger
logger.setLevel(logging.INFO) # 设置logger级别为INFO
fh = logging.FileHandler('test.log',mode='w') # 创建一个文件处理器
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
fh.setFormatter(formatter)#设置文件处理器格式
logger.addHandler(fh)#将处理器添加到记录器
def fun():
    logger.info('this is a log in util module')#注意这里是logger,不再是logging
if __name == '__main__':
    fun()
# main.py
import logging
import util

logger = logging.getLogger('Main') #创建名为Main的Logger实例
logger.setLevel(logging.INFO) # 设置logger级别为INFO
fh = logging.FileHandler('test.log',mode='w') # 创建一个处理器
#设置日志格式,我加了一个%(name)s字段,因为我的主程序可能调用子模块,子模块有自己的Logger name,子模块的日志也会打印到父模块的日志文件,用这个名字来区分一下。
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s: %(message)s') #注意,与util.py格式略有不同,特意加了一个%(name)s表示logger名
fh.setFormatter(formatter)
logger.addHandler(fh)#将处理器添加到记录器
def main():
    logger.info('main module start')
    util.fun()
    logger.info('main module stop')
if __name__ == '__main__':
    main()

单独运行util.py,输出:

2019-04-15 09:10:46,970 - logging_util.py[line:18] - INFO: this is a log in util module

运行main.py,输出:

2019-04-15 09:13:07,974 - logging_main.py[line:23] - Main - INFO: main module start
2019-04-15 09:13:07,974 - logging_util.py[line:18] - Main.Son - INFO: this is a log in util module
2019-04-15 09:13:07,974 - logging_main.py[line:25] - Main - INFO: main module stop

以上运行结果说明,显式地实例化logger比默认logging接口调用日志看起来累赘一些,但确实更灵活,还是有两个点需要注意:

  • util.py和main.py特意设置了不同的Formatter,如果单独执行util.py则其中Formatter生效(没有打印logger名),如果通过main.py调用util模块,则Formatter继承自main。
  • 子模块要想继承父模块的日志配置,必须要在logger命名时体现出来,比如main.py的logger名为Main,util.py的logger名为Main.Son,名字可以自定义。

按时间循环日志(长期运行程序常用)

import logging
import config #自定义模块

logger = logging.getLogger() #不指定Logger name,默认为root
logger.setLevel(logging.INFO)
log_path = config.log_path
fh = TimedRotatingFileHandler(log_path,"W0",1,10) #设置每周一循环一次,保留老文件10个(也就是10周)。
fh.suffix = "%Y-%m-%d" #默认suffix是strftime格式:"%Y-%m-%d_%H-%M-%S",我改成以天为单位
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)

同时打印的屏幕和文件

import logging
import time
import config #自定义模块

logger = logging.getLogger('Main') # 创建一个名为Mian的logger,不指定的话默认为root
logger.setLevel(logging.INFO) # 设置logger级别为INFO
fh = logging.FileHandler(config.log_path) # 创建一个文件处理器
sh = logging.StreamHandler() #创建一个流处理器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 
fh.setFormatter(formatter)
sh.setFormatter(formatter)
logger.addHandler(fh)#将文件处理器添加到记录器——用于输出到文件
logger.addHandler(sh)#将流处理器添加到记录器——用于输出到屏幕

参考资料

https://docs.python.org/3/library/logging.html#module-logging
https://www.jianshu.com/p/feb86c06c4f4
https://blog.csdn.net/liuchunming033/article/details/39080457

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值