日志级别
- CRITICAL = 50
- FATAL = CRITICAL
- ERROR = 40
- WARNING = 30
- WARN = WARNING
- INFO = 20
- DEBUG = 10
- NOTSET = 0
import logging
- 当代码import logging时,其实已经创建了一个Logger对象,名称为root的Logger对象
- root = RootLogger(WARNING)会创建日志级别是WARNING的logger对象,名称是root,作为默认的日志处理对象
- 为Logger类添加2个属性root、manager,manager对象主要是Logger对象管理,构成父子关系。
获取Logger对象
- 通过logging.getLogger()方法获取
- 当不传递name时,返回root Logger对象
- 当传递name时,就会去Manager对象getLogger查找
- 1.从上面的分析可以得到manager对象中已经有了名为root的日志记录器
- 2.manager内部有一个loggerDict字典,用来维护名字和Logger对象的映射,比如root: Logger对象地址。
- 3.如果你传入的name不在loggerDict中,就会创建一个Logger对象,然后添加到loggerDict中,最后指向一个父Logger对象(Logger类中parent属性)
- 4.如果你传入的name是’a.b.c’这种以点为分隔符的字符串,除了会添加’a.b.c’: Logger对象到LoggerDict中之外,还会添加’a’: PlaceHolder对象,’a.b’: PlaceHolder对象。当下次访问时就会创建’a’或’a.b’的Logger对象,并维护父子关系
- 5.manager对象内部维护一个父子关系的指向,root就是祖先Logger,因为parent属性为None,名称’a’的Logger对象是名称’a.b’的Logger对象的父亲,’a.b’是’a.b.c.’的父亲
- 6.当传入的名字存在manager的loggerDict中时,就会返回
总结:通过logging.getLogger()方法获取Logger实例,不传入name,默认得到的是root Logger,传入name,没有该logger实例会创建,由manager中loggerDict维护,并维护父子关系
日志输出
import logging
logging.info("info")
logging.warning("warning")
复制代码
- 通过上面2行代码简单讲解下,为什么info的信息没有输出,warning可以输出
- 从这3行代码得知logging.info方法实际上用的root Logger的info方法
- handlers是一个处理器列表,处理器是真正处理日志的对象
- 当没有定义处理器时,会先执行baseConfig()方法初始化
- 从方法帮助文档可得,该方法可传入的参数
- filename:默认日志输出到错误输出,定义该参数会把日志输出到文件
- filemode:打开文件模式,默认追加
- format:日志输出格式,上文中的默认格式其实没啥用,因为少了时间戳,没有时间戳的日志确实没啥用,下面列举下常用的格式,后面写如何使用
- style:没啥用
- level:设置root Logger对象的level
- stream:因为默认用的streamHandler将日记输出到错误输出,可以自定义streamHandler替换原来的
- handlers:自定义的handlers列表
- force:把handlers列表的handler去除 (python3.8后添加)
- format格式
属性名 | 描述 |
---|---|
%(message)s | 信息体 |
%(asctime)s | 日志记录创建时间 |
%(funcName)s | 函数名 |
%(levelname)s | 日志级别 |
%(levelno)s | 日志级别数字 |
%(lineno)d | 行号 |
%(module)s | 行号 |
%(process)d | 进程id |
%(thread)d | 线程id |
%(processName)s | 进程名 |
%(threadName)s | 线程名 |
%(name)s | Logger实例名 |
-
看一下basicConfig()流程
- 当root.handlers为空时,才进入初始化流程,如果执行过basicConfig()方法,将不会在执行,因为执行完这个方法后就会有handler对象并添加到handlers列表中
- 下面几行关于stream参数和filename参数判断,反正不能同时传入值
- 在往下根据没有没有传入filename的值来创建fileHanlder或streamHandler,如果看下StreamHandler类的实例化,发现没有传入stream值时,默认是错误输出,handler的level是0(这个后面有用)
- _STYLES是一个字典,style的值只能是’%’,'{‘,’$’,默认的样式就是BASIC_FORMAT
- 接着会给handler设置格式,然后root Logger添加handler
- 最后设置日志level,如果还有其他参数就会报错
- 执行完basicConfig方法后回到logging.info方法,调用root Logger的info方法
- 这里是日志输出的第一道关卡
- Logger类中disabled属性应该设置日志对象是否启用的
- _cache是一个字典,记录不同的level是否通行,比如:INFO:False,WARNING:True
- self.manager.disable默认是0(NOTSET)
- getEffectiveLevel方法是返回Logger对象有效level,如果有就会返回,没有就向父Logger查找,找到第一个有效level返回。比如:Logger a的level是30,Logger a.b如果没有设置level,那么a.b的有效level就是30。
- 回到isEnabledFor方法,简单理解就是判断传入的日志level和Logger设置的level谁大谁小,比如上面传入的是INFO,而root的level是WARNING,INFO对应的值20小于WARNING30,所以返回False
- 当isEnabledFor返回True后,执行self._log方法,注意最后2行即可,makeRecord方法封装信息,handle方法处理record信息
- handle方法
-
callHandlers方法会把封装的消息传递给所有的handler,根据handler的level和record封装的level比大小,只有handler的level大于record封装的level才会处理,propagate表示传播属性,会把record传递给父Logger处理,默认是True,root Logger没有父Logger。
-
最后,用一张官方的图表示日志输出流程
-
总结下步骤
- 1.Logger对象设置的level和你传入的level比大小,只有大于等于Logger的level才能进行下一步
- 2.封装信息,如果没有被过滤掉就会交给所有的handler处理
- 3.判断handler的level是否大于等于record的level,小的话就执行handler
- 4.Logger的propagate是否为True,为True将会调用父Logger的handler处理record,逻辑和前几个步骤一样
实战
import logging logFormat = '{"timeStamp": %(asctime)s, "level": %(levelname)s, "message": %(message)s}' logging.basicConfig(level=logging.INFO, format=logFormat) # 不传name得到root Logger root = logging.getLogger() # 父Logger是root log1 = logging.getLogger("a") # 执行下面代码将不会调用root的handler处理record # log1.propagate = False # 父Logger是a log2 = logging.getLogger('a.b') logging.info("root Logger info msg") log1.info("log1 Logger info msg") log2.info("log2 Logger info msg") 复制代码
- 通过basicConfig方法初始化root Logger
- log1 Logger是调用root的handler输出的,log2同理,log1和log2的有效level是INFO(root的level)
- 当执行log1.propagate = False,log1和log2将不会输出,log2.propagate = False将不会输出log2信息
import logging
logFormat = '{"timeStamp": %(asctime)s, "level": %(levelname)s, "message": %(message)s}'
logging.basicConfig(level=logging.INFO, format=logFormat)
# 不传name得到root Logger
root = logging.getLogger()
# 创建handler
myHandler = logging.FileHandler("./log.txt", mode="w", encoding="utf=8")
fileFormat = '{"logType": "file","timeStamp": %(asctime)s, "level": %(levelname)s, "message": %(message)s}'
fileFormater = logging.Formatter(fileFormat)
myHandler.setFormatter(fileFormater)
myHandler.setLevel(logging.WARNING)
# 添加handler
root.addHandler(myHandler)
# test
root.info("test root logger msg")
root.warning("test warning logger msg")
复制代码
- 简单的定义一个文件输出的handler
- 控制台会输出info和wanring信息,但是log.txt中只会有warning的信息,因为INFO小于WARNING
- 更多的handler类型在logging包下的hadlers.py中
- 看下基于时间切割日志文件的handler
import logging
from logging.handlers import TimedRotatingFileHandler
logFormat = '{"timeStamp": %(asctime)s, "level": %(levelname)s, "message": %(message)s}'
logging.basicConfig(level=logging.INFO, format=logFormat)
# 不传name得到root Logger
root = logging.getLogger()
# 创建handler
myHandler = TimedRotatingFileHandler("./log.txt", when="s", interval=5)
fileFormat = '{"logType": "file","timeStamp": %(asctime)s, "level": %(levelname)s, "message": %(message)s}'
fileFormater = logging.Formatter(fileFormat)
myHandler.setFormatter(fileFormater)
myHandler.setLevel(logging.WARNING)
# 添加handler
root.addHandler(myHandler)
# test
root.info("test root logger msg")
root.warning("test warning logger msg")
复制代码
- 和上一个filehandler很像,但是每隔5s就会创建一个新的文件
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END