python logging模块使用教程这篇简书文章给了很多帮助,尤其是对于理解logging的四大神器:Logger,Handler,Formatter,Filter——描述的非常清晰了,可以先看下下面这篇文章,本文做了一些归纳和补充。
日志级别 | 数值 |
---|---|
CRITICAL | 50 |
ERROR | 40 |
WARNING | 30 |
INFO | 20 |
DEBUG | 10 |
NOTSET | 0 |
Logger——记录器,暴露了应用程序代码能直接使用的接口。
Handler——处理器,将(记录器产生的)日志记录发送至合适的目的地。
Filter——过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。
Formatter ——格式化器,指明了最终输出中日志记录的布局。
创建方式: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) | 删除处理器 |
与Logger类一样,Handler不能被直接实例化,更甚于,Handler只是作为基类使用,实际使用的是其子类,常用的有StreamHanlder, FileHandler,RotatingFileHandler,TimedRotatingFileHandler。
Handler类型 | 主要用途 |
---|---|
StreamHandler | 输出日志到标准输出设备(如屏幕) |
FileHandler | 输出日志到文件 |
RotatingFileHandler | 输出到文件,支持文件达到一定大小后创建一个新的日志文件 |
TimedRotatingFileHandler | 输出到文件,支持经过一段时间后创建一个新的日志文件 |
创建方式:sh = logging.StreamHandler(stream=None) #如果不指定stream,默认输出到sys.stdout,通常就是屏幕
主要函数 | 用法 |
---|---|
emit(record) | 输出日志流到终端 |
flush() | 清除日志流 |
创建方式:fh = logging.FileHandler(filename,mode=‘a’,encoding=None,delay=False)
主要函数 | 用法 |
---|---|
close() | 关闭文件 |
emit(record) | 输出文件流到终端 |
长期运行的程序(如web服务),日志文件会越来越大,大到可能难以读写——循环处理器就是解决这一问题——自动切分日志。
创建方式:rh = logging.RotatingFileHandler(filename,mode=‘a’,maxBytes=0, backupCount=0, encoding=None, delay=False)
maxBytes是当前日志最大字节数,backupCount是老日志保留个数。
创建方式: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没有指定时有效) | 开始循环时刻 |
logging库还有些更多Handler,比如
主要函数 | 用法 |
---|---|
setLevel(level) | 设置记录器级别,高于level的日志才会被记录。如果Handler设置了更高的日志级别,则以Handler日志级别为准。 |
addFilter(filter) | 添加过滤器 |
removeFilter(filter) | 删除过滤器 |
addHandler(handler) | 添加处理器 |
removeHandler(handler) | 删除处理器 |
createLock() | 初始化一个线程锁,可用于序列化对可能不是线程安全的底层I / O功能的访问 |
acquire() | 获取使用创建的线程锁 |
release() | 释放获取的线程锁 |
创建方式: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= logging.Filter(name=’’)
包含了name的记录将会被过滤掉,不管是什么日志级别;如name=’’,则不过滤。
四大神器的关系大概是:
Logger可以包含一个或多个Handler和Filter,一个Handler可以新增多个Formatter和Filter,而且日志级别将会继承。
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
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
上面这段我们注意两个点:
关于上面的第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接口调用日志看起来累赘一些,但确实更灵活,还是有两个点需要注意:
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