日志级别
日志级别指的是产生日志的事件的严重程度。设置一个级别后,严重程度低于设置值的日志消息将被忽略。数字越高,级别越高。ERROR服务可能就停了,需要处理了。
debug(),info(),warning(),error()和critical()方法。
import threading import time import logging logging.basicConfig(level=logging.INFO) def add(x,y): logging.info(" {} {}".format(threading.enumerate(),x+y)) t = threading.Timer(1,add,args = (4,5)) t.start() 结果为: INFO:root: [<_MainThread(MainThread, stopped 19268)>, <Timer(Thread-1, started 19292)>] 9
如果改成debug()就不会显示了,因为它的级别更低。所以低于你所设定的级别信息,它就不会打印了。高于会显示。所以上面的例子warning()会显示。
格式字符串(重要)
注意:funcName,threadName,processName都是小驼峰。后面跟的s是字符串显示,d是数字显示。
举例
默认级别
import threading import time import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT) def add(x,y): logging.info(" {} {}".format(threading.enumerate(),x+y)) t = threading.Timer(1,add,args = (4,5)) t.start() 结果为: 2019-11-25 15:23:54,841 12600 [<_MainThread(MainThread, stopped 17084)>, <Timer(Thread-1, started 12600)>] 9
上面的时间打出来不好看,可以在后面这样改。
import threading import time import logging FORMAT = "%(asctime)s %(thread)d %(message)s"#格式字符串 logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="%Y-%m-%d-%H-%M-%S") def add(x,y): logging.info(" {} {}".format(threading.enumerate(),x+y)) t = threading.Timer(1,add,args = (4,5)) t.start() 结果为: 2019-11-25-15-26-49 18492 [<_MainThread(MainThread, stopped 18628)>, <Timer(Thread-1, started 18492)>] 9
后面的数字是毫秒。
还可以将参数输出出来。
import threading import time import logging FORMAT = "%(asctime)s %(thread)d %(message)s"#这里只能用C风格的 logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="%Y-%m-%d-%H-%M-%S") def add(x,y): logging.info(" {} {}".format(threading.enumerate(),x+y)) logging.info("%s %s",x,y) t = threading.Timer(1,add,args = (4,5)) t.start() 结果为: 2019-11-25-15-31-15 18420 [<_MainThread(MainThread, stopped 18988)>, <Timer(Thread-1, started 18420)>] 9 2019-11-25-15-31-15 18420 4 5
import logging FORMAT = "%(asctime)-15s Thread info: %(thread)d %(threadName)s %(message)s"#模块常量,一般大写。 logging.basicConfig(format = FORMAT) logging.info(" i'm {}".format(20))#info不显示,因为级别比warning级别低。 logging.warning(" i'm {}.".format(20))#warning 默认级别 结果为: 2019-11-25 11:07:42,021 Thread info: 9608 MainThread i'm 20.
构建消息
import logging FORMAT = "%(asctime)-15s Thread info: %(thread)d %(threadName)s %(message)s"#15是左右对齐的意思。 logging.basicConfig(format = FORMAT,level = logging.INFO) logging.info(" i'm {}".format(20))#单一字符串 logging.info(" i'm %d %s ,20, 'years old")#c风格 结果为: 2019-11-25 11:11:52,208 Thread info: 5768 MainThread i'm 20 2019-11-25 11:11:52,208 Thread info: 5768 MainThread i'm %d %s ,20, 'years old
上例是最基本的使用方法,大多数时候,使用的是info,正常运行信息的输出。
日志级别和格式字符串扩展的例子。
import logging FORMAT = "%(asctime)-15s Thread info: %(thread)d %(threadName)s %(message)s %(school)s" logging.basicConfig(format = FORMAT,level = logging.WARNING) d = {"school":"xpc"} logging.info("i am %s %s",20,"years old.",extra =d ) logging.warning("i am %s %s",20,"years old.",extra =d ) 结果为: 2019-11-25 11:16:31,293 Thread info: 10472 MainThread i am 20 years old. xpc
修改日期格式
import logging logging.basicConfig(format = "%(asctime)s %(message)s",datefmt="%Y %m %d %I %M:%S") logging.warning("this event was logged") 结果为: 2019 11 25 11 23:30 this event was logged
输出到文件
import logging logging.basicConfig(format = "%(asctime)s %(message)s",filename="f:/xpc.log") for _ in range(5): logging.warning("this event was logged")
上面的代码会直接在F盘中的xpc.log写入日志,如下图。
Logger类
logging模块加载的时候,会创建一个root logger。根logger对象的默认级别是WARNING.
调用logging.basicConfig来调整级别,就是对这个根logger的级别进行修改。
构造
logging.getLogger([name = None])
使用工厂方法返回一个Logger实例。指定name,返回一个名称为name的Logger实例。如果再次使用相同的名字,是实例化一个对象,未指定name,返回根Logger实例。
import threading import time import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="%Y-%m-%d-%H-%M-%S") def add(x,y): logging.info(" {} {}".format(threading.enumerate(),x+y)) logging.info("%s %s",x,y) t = threading.Timer(1,add,args = (4,5)) t.start() log = logging.getLogger("abc")#abc是名字,如果不给,默认是root。 print(log.name) 结果为: abc 2019-11-25-15-45-44 19236 [<_MainThread(MainThread, stopped 18536)>, <Timer(Thread-1, started 19236)>] 9 2019-11-25-15-45-44 19236 4 5
import threading import time import logging log = logging.getLogger("abc")#abc是名字,如果不给,默认是root。 print(log.name) print(log,type(log)) 结果为: abc <Logger abc (WARNING)> <class 'logging.Logger'>
层次结构
Logger是层次结构的,使用.点号分隔,如“a”,"a.b","a.b.c.d",a是a.b的父parent,a.b是a的子child,对于foo来说,名字为foo.bar,foo.bar.baz,foo.bam都是foo的后代。
import logging root = logging.getLogger()#根logger print(root.name,type(root),root.parent,id(root))#根root没有父 logger= logging.getLogger(__name__)#模块级logger print(logger.name,type(logger),id(logger.parent),id(logger)) loggchild = logging.getLogger(__name__+".child")#模块名.child,这是子logger print(loggchild.name,type(loggchild),id(loggchild.parent),id(loggchild)) 结果为: root <class 'logging.RootLogger'> None 36706064 __main__ <class 'logging.Logger'> 36706064 32088752 __main__.child <class 'logging.Logger'> 32088752 32088976
level级别设置
import logging FORMAT = "%(asctime) - 15s Thread info: %(thread)d %(threadName)s %(message)s" logging.basicConfig(format = FORMAT,level=logging.INFO) logger = logging.getLogger(__name__)#创建一个新的logger,未设定级别 print(logger.name,type(logger)) print(logger.getEffectiveLevel())#level 20 logger.info("hello1") logger.setLevel(28)#重修修改level print(logger.getEffectiveLevel())#level 28 logger.info("hello2")#被拦截 logging.warning("hello 3 warning") root = logging.getLogger()#根logger root.info("hello4 info root")#输出成功 结果为: __main__ <class 'logging.Logger'> 2019-11-25 11:46:01,791 Thread info: 12848 MainThread hello1 20 28 2019-11-25 11:46:01,791 Thread info: 12848 MainThread hello 3 warning 2019-11-25 11:46:01,791 Thread info: 12848 MainThread hello4 info root
每一个logger创建后,都有一个等效的level。logger对象可以在创建后动态的修改自己的level。
Handler
Handler控制日志信息的输出目的地,可以是控制台、文件。
可以单独设置level
可以单独设置格式
可以设置过滤器
Handler类继承
日志输出其实Handler做的,也就是真正干活的是Handler。
在logging.basicConfig中,有如下的源码:
if handlers is None: filename = kwargs.pop("filename", None) mode = kwargs.pop("filemode", 'a') if filename: h = FileHandler(filename, mode) else: stream = kwargs.pop("stream", None) h = StreamHandler(stream) handlers = [h]
如果设置文件名,则为根logger加一个输出到文件的Handler;如果没有设置文件名,则为根logger加一个streamhandler,默认输出到sys.stderr.
也就是说,根logger一定会至少有一个handler的。
思考
创建的Handler的初试level是什么?
import logging FORMAT = "%(asctime)s %(name)s %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) logger = logging.getLogger("xpczss") print(logger.name,type(logger)) logger.info("line 1") handler = logging.FileHandler("F:/xpczss.log","w+")#创建Handler logger.addHandler(handler)#给logger对象绑定一个handleer #注意看看控制台,再看看xpczss.log文件,对比差异 #思考这是怎么打印的? logger.info("line 2") 结果为: 控制台打印: xpczss <class 'logging.Logger'> 2019-11-25 13:57:03,111 xpczss line 1 2019-11-25 13:57:03,163 xpczss line 2 f盘xpczss.log打印为: line 2
日志流
level的继承
import logging FORMAT = "%(asctime)s %(name)s %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) root = logging.getLogger() log1 = logging.getLogger("s") log1.setLevel(logging.INFO)#分别取INFO,WARNING,ERROR试一试 #没有设置任何的handler,level #log2有效级别就是log1的ERROR log2 = logging.getLogger("s.s1") log2.warning("log2 warning") 结果为: INFO,WARING下有打印,结果为: 2019-11-25 14:04:15,303 s.s1 log2 warning ERROR没有打印,空白。
logger实例,如果设置了level,就用它和信息的级别比较,否则,继承最近的祖先的level。
继承关系及信息传递
- 每一个logger实例的level如同入口,让水流进来,如果这个门槛太高,信息就进不来,例如log3.warning("log3),如果log3定义的级别高,就不会有信息通过log3.
- 如果level没有设置,就用父logger的,如果父logger的level没有设置,继续找父的父的,最终可以找到root上,如果root设置了就用它的,如果root没有设置,root的默认值是WARNING.
- 消息传递流程
- 如果消息在某一个logger对象上产生,这个logger就是当前logger,首先消息level要和当前logger的EffectiveLevel比较,如果低于当前的logger的EffectiveLevel,则流程结束;否则生成log记录。
- 日志记录会交给当前logger的所有handler处理,记录还要和每一个handler的级别分别比较,低的不处理,否则按照handler输出日志记录。
- 当前logger的所有handler处理完后,就要看自己的propagate属性,如果是true表示向父logger传递这个日志记录,否则到此流程结束。
- 如果日志记录传递到了父logger,不需要和logger的level比较,而是直接交给父的所有handler,父logger成为当前logger。重复2、3步骤,直到当前logger的父logger是None退出,也就是说当前logger最后一般是root logger(否则能到root logger要看中间的logger是否允许propagate)
- logger实例初始的propagate属性为true,即允许向父logger传递消息。
- logging.basicConfing,如果root没有handler,就默认创建一个streamhandler,如果设置了filename,就创建一个filehandler.如果设置了format参数,就会用它生成一个formatter对象,并把这个formatter加入到刚才创建的handler上,然后把这些handler加入到root.handlers列表上,level是设置给root.logger的。如果root.handlers列表不为空,logging.basicConfig调用什么都不做。
官方日志流转图
参考logging.Logger类的callHandlers方法。
实例
import logging logging.basicConfig(format="%(asctime)s %(name)s %(message)s",level=logging.INFO) root = logging.getLogger() root.setLevel(logging.ERROR) print("root",root.handlers) h0 = logging.StreamHandler() h0.setLevel(logging.WARNING) root.addHandler(h0) print("root",root.handlers) for h in root.handlers: print("root handler = {},foramt = {}".format(h,h.formatter) ) log1 = logging.getLogger("s") log1.setLevel(logging.WARNING) h1 = logging.FileHandler("F:/abcd.log") h1.setLevel(logging.WARNING) log1.addHandler(h1) print("log1",log1.handlers) log2 = logging.getLogger("s.s1") log2.setLevel(logging.CRITICAL) h2 = logging.FileHandler("F:/abcd.log") h2.setLevel(logging.WARNING) log2.addHandler(h2) print("log2",log2.handlers) log3 = logging.getLogger("s.s1.s2") log3.setLevel(logging.INFO) print(log3.getEffectiveLevel()) log3.warning("log3") print("log3",log3.handlers) 控制台输出为: root [<StreamHandler <stderr> (NOTSET)>] root [<StreamHandler <stderr> (NOTSET)>, <StreamHandler <stderr> (WARNING)>] 2019-11-25 14:33:03,401 s.s1.s2 log3 log3 root handler = <StreamHandler <stderr> (NOTSET)>,foramt = <logging.Formatter object at 0x0000000001E8ABE0> root handler = <StreamHandler <stderr> (WARNING)>,foramt = None log1 [<FileHandler F:abcd.log (WARNING)>] log2 [<FileHandler F:abcd.log (WARNING)>] 20 log3 [] abcd输出为: log3 log3
Formatter
logging的Formatter类,它允许制定某个格式的字符串,如果提供None,那么%(message)s将会作为默认值。修改上面的例子,让它看的更明显。
import logging logging.basicConfig(format="%(name)s %(asctime)s %(message)s",level=logging.INFO) root = logging.getLogger() root.setLevel(logging.ERROR) print("root",root.handlers) h0 = logging.StreamHandler() h0.setLevel(logging.WARNING) root.addHandler(h0) print("root", root.handlers) for h in root.handlers: print("root handler = {},formatter = {}".format(h,h.formatter)) log1 = logging.getLogger("s") log1.setLevel(logging.ERROR) h1 = logging.FileHandler("F:/abcd.log") h1.setLevel(logging.WARNING) print("log1 formatter",h1.formatter)#没有设置formatter使用缺省值%(message)s log1.addHandler(h1) print("log1",log1.handlers) log2 = logging.getLogger("s.s1") log2.setLevel(logging.CRITICAL) h2 = logging.FileHandler("F:/abcd.log") h2.setLevel(logging.WARNING) print("log2 formatter",h2.formatter) #handler默认无formatter f2 = logging.Formatter("log2 %(name)s %(asctime)s %(message)s") h2.setFormatter(f2) print("log2 formatter",h2.formatter) log2.addHandler(h2) print("log2",log2.handlers) log3 = logging.getLogger("s.s1.s2") log3.setLevel(logging.INFO) print(log3.getEffectiveLevel()) log3.warning("log3") print("log3",log3.handlers) 控制台输出为: root [<StreamHandler <stderr> (NOTSET)>] root [<StreamHandler <stderr> (NOTSET)>, <StreamHandler <stderr> (WARNING)>] root handler = <StreamHandler <stderr> (NOTSET)>,formatter = <logging.Formatter object at 0x00000000026EABE0> root handler = <StreamHandler <stderr> (WARNING)>,formatter = None log1 formatter None log1 [<FileHandler F:abcd.log (WARNING)>] log2 formatter None log2 formatter <logging.Formatter object at 0x00000000028F0C50> log2 [<FileHandler F:abcd.log (WARNING)>] 20 log3 [] s.s1.s2 2019-11-25 14:43:56,329 log3 log3 abcd日志输出为: log3 log3 log2 s.s1.s2 2019-11-25 14:43:56,329 log3 log3
Filter
可以为handler增加过滤器,所以这种过滤器只影响某一个handle,不会影响整个处理流程,但是,如果过滤器增加到logger上,就会影响流程。
import logging FORMAT = "%(asctime) -15s Thread info: %(thread)d %(threadName)s %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) log1 = logging.getLogger("s") log1.setLevel(logging.WARNING)#ERROR试一试 h1 = logging.StreamHandler() h1.setLevel(logging.INFO) fmt1 = logging.Formatter("log1 - h1 %(message)s") h1.setFormatter(fmt1) log1.addHandler(h1) log2 = logging.getLogger("s.s1") #log2.setLevel(logging.CRITICAL) print(log2.getEffectiveLevel())#继承父的level,WARNING h2 = logging.StreamHandler() h2.setLevel(logging.INFO) fmt2 = logging.Formatter("log2 - h2 %(message)2") h2.setFormatter(fmt2) f2 = logging.Filter("s")#过滤器 s,s.s1,s.s2 h2.addFilter(f2) log2.addHandler(h2) log2.warning("log2 warning")
消息log2的,它的名字是s.s1,因此过滤器名字设置为s或s.s1,消息就可以通过,但是如果是其他就不能通过,不设置过滤器名字,所以消息通过。
过滤器核心就一句,在logging.Filter方法中。
完