文章目录
- 感谢
- 基本概念
- 日志级别
- 四大神器——Logger,Handler, Filter, Formatter
- Logger——记录器
- Handler——处理器
- StreamHandler——流处理器
- FilerHandler——文件处理器
- RotatingFileHandler——循环文件处理器
- TimeRotatingFileHandler——时间循环文件处理器
- 其他Handler
- 主要函数
- Formatter——格式器
- Filter——过滤器
- 实例
- 最简单example
- 基础配置example
- 子模块日志打印(最常用)
- 按时间循环日志(长期运行程序常用)
- 同时打印的屏幕和文件
- 参考资料
感谢
python logging模块使用教程这篇简书文章给了很多帮助,尤其是对于理解logging的四大神器:Logger,Handler,Formatter,Filter——描述的非常清晰了,可以先看下下面这篇文章,本文做了一些归纳和补充。
基本概念
日志级别
日志级别 | 数值 |
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
四大神器——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-logginghttps://www.jianshu.com/p/feb86c06c4f4https://blog.csdn.net/liuchunming033/article/details/39080457