python logging模块使用

日志级别

  • CRITICAL = 50
  • FATAL = CRITICAL
  • ERROR = 40
  • WARNING = 30
  • WARN = WARNING
  • INFO = 20
  • DEBUG = 10
  • NOTSET = 0

import logging

  • 当代码import logging时,其实已经创建了一个Logger对象,名称为root的Logger对象

image.png

  • root = RootLogger(WARNING)会创建日志级别是WARNING的logger对象,名称是root,作为默认的日志处理对象

image.png

  • 为Logger类添加2个属性root、manager,manager对象主要是Logger对象管理,构成父子关系。

获取Logger对象

  • 通过logging.getLogger()方法获取

image.png

  • 当不传递name时,返回root Logger对象
  • 当传递name时,就会去Manager对象getLogger查找

image.png

  • 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")
复制代码

image.png

  • 通过上面2行代码简单讲解下,为什么info的信息没有输出,warning可以输出

image.png

  • 从这3行代码得知logging.info方法实际上用的root Logger的info方法
  • handlers是一个处理器列表,处理器是真正处理日志的对象
  • 当没有定义处理器时,会先执行baseConfig()方法初始化

image.png

  • 从方法帮助文档可得,该方法可传入的参数
    • 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()流程

image.png

  • 当root.handlers为空时,才进入初始化流程,如果执行过basicConfig()方法,将不会在执行,因为执行完这个方法后就会有handler对象并添加到handlers列表中
  • 下面几行关于stream参数和filename参数判断,反正不能同时传入值
  • 在往下根据没有没有传入filename的值来创建fileHanlder或streamHandler,如果看下StreamHandler类的实例化,发现没有传入stream值时,默认是错误输出,handler的level是0(这个后面有用)

image.png

  • _STYLES是一个字典,style的值只能是’%’,'{‘,’$’,默认的样式就是BASIC_FORMAT

image.png

  • 接着会给handler设置格式,然后root Logger添加handler
  • 最后设置日志level,如果还有其他参数就会报错
  • 执行完basicConfig方法后回到logging.info方法,调用root Logger的info方法

image.png

image.png

  • 这里是日志输出的第一道关卡

image.png

  • Logger类中disabled属性应该设置日志对象是否启用的
  • _cache是一个字典,记录不同的level是否通行,比如:INFO:False,WARNING:True
  • self.manager.disable默认是0(NOTSET)

image.png

  • 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信息

image.png

  • handle方法

image.png

image.png

  • callHandlers方法会把封装的消息传递给所有的handler,根据handler的level和record封装的level比大小,只有handler的level大于record封装的level才会处理,propagate表示传播属性,会把record传递给父Logger处理,默认是True,root Logger没有父Logger。

  • 最后,用一张官方的图表示日志输出流程

image.png

  • 总结下步骤

    • 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")
    
    复制代码

image.png

  • 通过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

image.png

  • 更多的handler类型在logging包下的hadlers.py中

image.png

  • 看下基于时间切割日志文件的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就会创建一个新的文件

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享