logging--日志记录模块

logging–日志记录工具

1.日志级别:

级别数值何时使用
CRITICAL50严重的错误,尽当诊断问题时适用
ERROR40由于严重的错误,程序的某些功能已经不能正常运行
WARNING30(默认级别)表明有已经或即将发生的意外,程序仍按预期运行
INFO20确认程序按照预期运行
DEBUG10细节信息,仅当诊断问题时使用
NOTSET0未设置级别

日志级别是产生日志事件的严重程度,严重程度低于设置值的日志消息将被忽略

日志输出的一些常用方法:
debug()、info()、warning()、error()、critical()

日志的默认级别源于

root = RootLogger(WARNING)

2.格式字符串

格式含义
%(message)s日志消息内容(默认的输出格式)
%(asctime)s创建LogRecord的可读时间
%(funcName)s日志调用所在的函数名
%(levelname)s消息的级别名称,例如"DEBUG","INFO"等
%(lineno)s消息的级别数字,对应上面的级别, 代码的行号
%(module)s模块(filename的名字部分)
%(process)d进程ID
%(thread)d线程ID
%(processName)s进程名
%(threadName)s线程名
%(name)slogger名字

3.logging.basicConfig()基本配置

参数含义
filename设置日志输出到的文件位置
filemode设置日志输出到文件的模式,默认为"a"
format设置日志的输出格式
datefmt单独设置时间格式, 类似"%Y-%m-%d %H:%M:%S"
style
level日志流的等级
handlers控制日志流的具体输出位置,注意类型应该为列表,注意次参数和filename以及stream有冲突,不能共存
stream设置日志输出到的stream流的位置

[外链图片转存失败(img-xYL4Pawo-1563281494060)(https://i.loli.net/2019/06/13/5d01ba3081ee129635.png)]

日志库采用模块化方法,并提供积累组件:记录器,处理程序,过滤器和格式化程序之间传递
  • 记录器器暴露了应用程序代码直接使用的接口logger
  • 处理程序将日志记录发送到适当的目标handler
  • 过滤器提供了更精细的附加功能,用于确定输出的日志记录
  • 格式化程序制定最终输出中日志记录的样式

日志事件信息在LogRecord实例的记录器、处理程序、过滤器和格式化程序之间传递

4.Logger对象(记录器)

1.在每个使用日志记录的模块中使用模块级记录器
logger = logging.getLogger(__name__) >> level = 0
logger1 = logging.getLogger() >> level = 30

理解logger对象的创建方式
源码分析:

def getLogger(name=None):
    if name:
        return Logger.manager.getLogger(name) # 等级level默认是0
    else:
        return root # 等级默认是waring(30)
root = RootLogger(WARNING)
Logger.root = root
Logger.manager = Manager(Logger.root)
def getLogger(self, name):
    rv = None
    if not isinstance(name, str):
        raise TypeError('A logger name must be a string')
    _acquireLock()
    try:
        if name in self.loggerDict:
            rv = self.loggerDict[name]
            if isinstance(rv, PlaceHolder):
                ph = rv
                rv = (self.loggerClass or _loggerClass)(name)
                rv.manager = self
                self.loggerDict[name] = rv
                self._fixupChildren(ph, rv)
                self._fixupParents(rv)
        else:
            rv = (self.loggerClass or _loggerClass)(name)
            rv.manager = self
            self.loggerDict[name] = rv
            self._fixupParents(rv)
    finally:
        _releaseLock()
    return rv

分析:
不传入参数创建logger默认返回的是root,而root = RootLogger(WARNING)
RootLogger是Logger的子类,level等级传入的参数为WARNING,所以root默认是根logger的实例

如果传入参数name, 返回的是Logger.manager.getLogger(name), 而Logger.manager = Manager(Logger.root),即Logger.manager是Manager的实例,getLogger为Manager的方法,背后使用了字典self.loggerDict来保证同一个名称返回同一个logger实例(每次返回这个字典的vlaue)

所以:logging.root和logging.getLogger()是一个对象

2.等级

等级分为level和有效level

import logging
import time
import sys

fmt = "%(asctime)s [%(name)s] %(levelname)s %(thread)s %(message)s"
dmt = "%Y-%m-%d,%H:%M:%S"

logging.basicConfig(format=fmt, datefmt=dmt, level=logging.INFO)

logger = logging.getLogger()
logger1 = logging.getLogger("a")
logger2 = logging.getLogger("a.b")

print(logger.level, logger.getEffectiveLevel())  # 20 20
print(logger1.level, logger1.getEffectiveLevel())  # 0 20
print(logger2.level, logger2.getEffectiveLevel())  # 0 20

logger1.setLevel(level)

logger.setLevel(logging.DEBUG)
logger1.setLevel(10)
logger2.setLevel("DEBUG")
logger对象的继承关系(涉及到有效等级)

Logger类的实例他们在概念上以 . 作为分隔符排列在命名空间的层次结构中

源码分析:有效级别的查找顺序

def getEffectiveLevel(self):
    logger = self
    while logger:
        if logger.level:
            return logger.level
        logger = logger.parent   #  一直朝着parent的方向进行查找level
    return NOTSET

日志消息能否输出却取决于当前的有效级别

logger = logging.getLogger()返回的是root  
logger1 = logging.getLogger("a")  
logger2 = logging.getLogger("a.b") 

如上所示,logger1是logger2的父类,logger1的有效level会先看自己有没有配置level,如果有就和当前的level相等; 如果当前level没有配置,就会就近寻找祖先们的level,如果存在那么当前logger实例的有效level就继承,否则一直到最后等于30

如果子logger没有设置setlevel,也就是继承自父logger,会一直继承,也就是父logger改变setlevel,子logger也会改变
记录器的有效级别,用于确定事件(也就是logger.info等创建日志信息)是否传递给记录器的处理程序

5.Handler对象

Handler控制日志信息的输出目的地,可以是控制台、文件

  • 可以单独设置 level 控制日志流的传播
  • 可以单独设置格式, format
  • 可以设置过滤器
5.1.Handler层次:
  • Hnadler
    • StreamHandler # 不指定使用stderr
      • _StderrHandler # 标准错误输出
      • FileHandler # 文件
    • NullHandler # 什么都不做
5.2.handler对象的创建:

常见的主要是用的处理程序类型:

  • logging.StreamHandler
  • logging.FileHandler
5.3.handler对象的配置方法:
配置方法含义
handler1.setLevel()设置level,和记录器的setlevel区别开
handler1.setFormatter()设置格式,选择一个该处理程序使用的Formatter对象
handler1.addFilter()handler1.removeFilter()设置和取消过滤器

注意

  • handler对象没有有效等级属只有level属性
  • logger对象可以使用addHandler方法给自己添加零个或者多个处理程序对象(handler对象)
  • handler的默认等级是0

logger对象的创建,是有层级结构的
logger对象的属性propagate可以控制日志流的传播
logger.debug() >> 消息的level为DEBUG(10)

记录流程:

记录器和处理程序中的日志时间信息流程如下图所示:

日志流输出图

1.如果消息想要在某个logger对象上产生,首先消息的level要和logger对象的有效等级先比较,如果大于等于,就可以生成日志,否则传播结束
注意这个level比较,只需要过了当前的logger的有效level即可,后面所有的父logger都不需要比较了,相当于过了当前这一logger关就可以了
注意创建logger对象过程中的有效level的传递过程

2.如果loggers对象的handlers属性为空列表[],那么会看logging.basicConfig
会默认创建一个StreamHandler(会将当前logger的消息一般输出到控制台),注意如果logging.basicConfig设置了filename,就会创建一个FileHandler对象,如果设置了format参数,就会用它生成一个Formatter对象,否则会生成缺省Formatter,并把它加入到刚才创建的Handler上
注意:如果root的handlers不为空,logging.basicConfig什么都不做

3.如果loggers对象的handlers属性不是空列表,将消息的level和handler的level相比较,如果大于等于,就可以按照handler输出到日志
查看logger的属性propagate,如果为False,流程结束 为True就会继续寻找他的父类logger,把父logger的所有handler进行挨个等级比较,满足即输出,否则就一直寻找到root logger,进行handler比较,满足即输出

小知识补充:
关于sys模块的stdout、stdin、stderr, file-like对象
可以输出的都是可写的, 输入的都是可读的

6.Formatter对象

在logging模块中

class Formatter(object):
    def __init__(self, fmt=None, datefmt=None, style='%')
        pass

关于基本配置logging.basicConfig中的输出目的地问题

源码分析:

# 在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增加一个输出到文件的FileHandler;
如果没有设置文件名,则为根Logger加一个StreamHandler,默认输出到sys.stderr
也就是说根Logger一定会至少有一个handler的

注意参数

7.Filter对象

  • filter对象的创建
    filter = logging.Filter()
  • 可以给handler对象增加过滤器,所以这种过滤器之形象某一个handler,不会影响整个处理流程,属于handler级别
    handler.addFilter(filter)
  • 可以给logger对象添加过滤器,当前对象在日志流的输出就会受影响,即如果被过滤掉某个logger,日志流在当前logger处会中断
    logger.addFilter(filter)

源码分析(创建filter对象部分):

class Filter(object): 
    def __init__(self, name=''):
        self.name = name   # 这里name是logging.Filter传入的name
        self.nlen = len(name)

    def filter(self, record):
        if self.nlen == 0:
            return True
        elif self.name == record.name: #record.name 是logger对象上的name
            return True
        elif record.name.find(self.name, 0, self.nlen) != 0: 
        # 等价于record.name.startswith(self.name) 
            return False
        return (record.name[self.nlen] == ".")
        # 表示self.name的下一个字符为 . 则返回True,就过滤了

logging模块最困难的是掌握日志流的传播顺序,模块里面涉及的细则有点多,源码里面使用了很多继承和高级编程技巧,可以帮我们更快的掌握

更多请参考官方文档-日志基础教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值