抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

logging模块

父类 含义
Logger - 日志记录器
RootLogger Logger 根日志记录器类
Handler - 处理器,日志的实际处理者。有众多处理器子类
Formatter - 格式化器,日志输出格式控制
Filter - 过滤器,在Logger实例或Handler实例上过滤日志记录
import logging
log1 = logging.getLogger('log1')
print(log1,type(log1))
log2 = logging.getLogger('log1')
print(log2,type(log2))

print(log1 == log2) # True
print(log1 is log2) # True

# 我们平常创建的logger对象,都是通过logging.getLogger(name)方法创建的,只要name相同,返回的logger对象就是同一个。

root 根据日志记录器调用

root = logging.root
root = logging.Logger.root 
root = logging.getLogger() # 一般情况下,使用这种方法获取root logger

记录器Logger

  • 日志记录器都是Logger类的实例,可以通过它实例化得到。但是logging模块也提供了工厂方法。
  • Logger实例的构建,使用Logger类也行,但推荐getLogger函数。
Logger.manager = Manager(Logger.root) # 约1733行,为Logger类注入一个manager类属 性
def getLogger(name=None):
    """
    Return a logger with the specified name, creating it if necessary.
    If no name is specified, return the root logger.
    """
    if name:
        return Logger.manager.getLogger(name)
    else:
return root # 没有指定名称,返回根logger
  • 使用工厂方法返回一个Logger实例。

要记录器

  • logger模块为了使用简单,提供了一些快捷方法,这些方法本质上都用到了记录器实例,即根记录器实 例。
# logging模块代码中有下列代码 
class RootLogger(Logger):
    """
    A root logger is not that different to any other logger, except that
    it must have a logging level and there is only one instance of it in
    the hierarchy.
    """
    def __init__(self, level):
        """
        Initialize the logger with the name "root".
        """
        Logger.__init__(self, "root", level)
root = RootLogger(WARNING) # 根记录器默认级别是警告 Logger.root = root
Logger.manager = Manager(Logger.root)

也就是说,logging模块一旦加载,就立即创建了一个root对象,它是Logger子类RootLogger的实例。日志记录必须使用Logger实例。

实例和名称

  • 每一个Logger实例都有自己的名称,使用getLogger获取记录器实例时,必须指定名称。 在管理器内部维护一个名称和Logger实例的字典。
  • 根记录器的名称就是”root”。
  • 未指定名称,getLogger返回根记录器对象。
import logging
root = logging.root
print(root is logging.Logger.root)
r = logging.getLogger()
print(root is r)
import logging
l1 = logging.getLogger('t1')
l2 = logging.getLogger('t1')
print(id(l1), id(l2), l1 is l2)
l3 = logging.getLogger('t2')
print(id(l3))
print(l1.name, l2.name)

层次结构

记录器的名称另一个作用就是表示Logger实例的层次关系;
Logger是层次结构的,使用.点号分割,如'a''a.b''a.b.c.d'aa.b的父parenta.ba的子 child。对于foo来说,名字为foo.barfoo.bar.bazfoo.bam都是 foo的后代。

import logging
# 父子 层次关系
# 根 logger
root = logging.getLogger()
print(1, root.name, type(root) , root.parent) # 1 root <class 'logging.RootLogger'> None

parent = logging.getLogger('a')
print(2, parent.name, type(parent), parent.parent.name , parent.parent is  root ) # 2 a <class 'logging.Logger'> root True

child = logging.getLogger('a.b')
print(3, child.name, type(child), child.parent.name , child.parent is  parent ) # 3 a.b <class 'logging.Logger'> a True

级别Level

级别 数值
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

级别可以是一个整数。0表示未设置, 有特殊意义;
级别可以用来表示日志消息级别、记录器级别、处理器级别;

消息级别

  • 每一条日志消息被封装成一个LogRecord实例,该实例包含消息本身、消息级别、记录器的name等信息。

  • 消息级别只能说明消息的重要等级,但不一定能输出;

记录器级别

  • 日志输出必须依靠记录器,记录器设定自己的级别,它决定着消息是否能够通过该日志记录器输出;
  • 如果日志记录器未设置自己的级别,默认级别值为0;

记录器有效级别

  • 如果日志记录器未设置自己的级别,默认级别值为0,等效级别就继承自己的父记录器的非0级别,如果设置了自己的级别且不为0,那么等效级别就是自己设置的级别。
class Logger:
    def getEffectiveLevel(self):
        """
        Get the effective level for this logger.
        Loop through this logger and its parents in the logger hierarchy,
        looking for a non-zero logging level. Return the first one found.
        """
        logger = self
        while logger:
            if logger.level:
                return logger.level
            logger = logger.parent
        return NOTSET

只有日志级别高于产生日志的记录器有效级别才有资格输出

log = logging.getLogger('m1')
print(log.level, log.getEffectiveLevel())
#log.setLevel(40)
log.warning('log test info') # msg 20 >= log Effective level 
log1 = logging.getLogger('m1.m2')
print(log1.level, log1.getEffectiveLevel())

处理器级别

  • 每一个Logger实例其中真正处理日志的是处理器Handler,每一个处理器也有级别,它控制日志消息是否能通过该处理器Handler输出。

根记录器使用

产生日志

  • logging模块提供了debuginfowarningerrorcritical等快捷函数,可以快速产生相应级别消息。 本质上这些方法使用的都是根记录器对象。

格式字符串

属性名 格式 描述
消息内容 %(message)s The logged message, computed as msg % args. 当调用 Formatter.format() 时设置
日志级别数值 %(levelno)s 消息的级别数字,对应 DEBUG, INFO, WARNING, ERROR, CRITICAL
行号 %(lineno)d 日志调用所在的源码行号
模块 %(module)s 日志调用所在的模块名
进程ID %(process)d 进程 ID
线程ID %(thread)d 线程 ID
线程名 %(threadName)s 线程名字
可读时间 %(asctime)s 创建 LogRecord 时的可读时间,默认格式为 ‘2003-07-08 16:49:45,896’
日志级别名称 %(levelname)s 消息的级别名称,’DEBUG’, ‘INFO’, ‘WARNING’, ‘ERROR’, ‘CRITICAL’
进程名 %(processName)s 进程名
logger名字 %(name)s logger 名字

注意:funcNamethreadNameprocessName都是小驼峰。

基本配置

  • logging模块提供basicConfig函数,本质上是对根记录器做最基本配置。
# import logging
# 根logger
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s % (threadName)s [%(message)s]") # 设置输出消息的格式
# 注意basicConfig只能调用一次
logging.basicConfig(level=logging.INFO) # 设置级别,默认WARNING logging.basicConfig(filename="/tmp/test.log", filemode='w') # 输出到文件
logging.info('info msg') # info函数第一个参数就是格式字符串中的%(message)s logging.debug('debug msg') # 日志消息级别不够
  • basicConfig函数执行完后,就会为root提供一个处理器,那么basicConfig函数就不能再调用了。 因此,这时要注意先配置,再使用
FORMAT = '[%(asctime)s]\t %(name)s %(threadName)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT, datefmt='%Y-%m-%d %H:%M:%S')
logging.info('test message') # [2023-02-23 15:41:36]    root MainThread test message

处理器Handler

日志记录器南要处理器来处理消息,处理器决定着日志消息输出的设置;
Handler控制日志信息的输出目的地,可以是控制台、文件。

  • 可以单独设置level
  • 可以单独设置格式
  • 可以设置过滤器

  • Handler类层次

  • Handler

    • StreamHandler # 不指定使用sys.stderr
      • FileHandler # 文件
      • _StderrHandler # 标准输出
    • NullHandler # 什么都不做

日志输出其实是Handler做的,也就是真正干活的是Handler。

basicConfig函数执行后,默认会生成一个StreamHandler实例,如果设置了filename,则只会生成一个FileHandler实例。

每个记录器实例可以设置多个Handler实例。

# 定义处理器
handler = logging.FileHandler('o:/test.log', 'w', 'utf-8') handler.setLevel(logging.WARNING) # 设置处理器级别

格式化器Formatter

  • 每一个记录器可以按照一定格式输出日志,实际上是按照记录器上的处理器上的设置的格式化器的格式
    字符串输出日志信息。
  • 如果处理器上没有设置格式化器,会调用缺省_defaultFormatter,而缺省的格式符为
class PercentStyle(object):
    default_format = '%(message)s'
# 定义格式化器
formatter = logging.Formatter('#%(asctime)s <%(message)s>#') # 为处理器设置格式化器
handler.setFormatter(formatter)

日志流

官方日志流转图

  • 继承关系及信息传递

  • 每一个Logger实例的level如同入口,让水流进来,如果这个门槛太高,信息就进不来。例如 log3.warning('log3'),如果log3定义的级别高,就不会有信息通过log3。如果level没有设置,就用父logger的,如果父loggerlevel没有设置,继续找父的父的,最终可以找到root上,如果root设置了就用它的,如果root没有设置,root的默认值是WARNING

消息传递流程:

  1. 如果消息在某一个logger对象上产生,这个logger就是当前logger,首先消息level要和当前 loggerEffectiveLevel比较,如果低于当前loggerEffectiveLevel,则流程结束;否则生成log记录。
  2. 日志记录会交给当前logger的所有handler处理,记录还要和每一个handler的级别分别比较,低的不处理,否则按照handler输出日志记录。
  3. 当前logger的所有handler处理完后,就要看自己的propagate属性,如果是True表示向父 logger传递这个日志记录,否则到此流程结束。
  4. 如果日志记录传递到了父logger,不需要和父loggerlevel比较,而是直接交给父的所有 handler,父logger成为当前logger。重复2、3步骤,直到当前logger的父loggerNone退出,也就是说当前logger最后一般是root logger(是否能到root logger要看中间的logger是否允许propagate)。
  • Logger实例初始的propagate属性为True,即允许向父logger传递消息。
  • logging.basicConfig函数如果root没有handler,就默认创建一个StreamHandler,如果设置了filename,就创建一个 FileHandler。如果设置了format参数,就会用它生成一个Formatter对象,否则会生成缺省 Formatter,并把这个formatter加入到刚才创建的handler上,然后把这些handler加入到 root.handlers列表上。level是设置给root logger的。如果root.handlers列表不为空,logging.basicConfig的调用什么都不做。

日志示例

import logging

# 根logger
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(threadName)s [%(message)s]")
print(logging.root.handlers)

mylogger = logging.getLogger(__name__)# level为0
mylogger.info('my info ......') # 实际上是调用了root logger的info方法
print('='* 30)


# 定义处理器

handler = logging.FileHandler('./test.log','w','utf-8')
handler.setLevel(logging.WARNING) #设置处理器级别
# 定义格式化器
formatter = logging.Formatter('#%(asctime)s <%(message)s>#')
# 为处理器设置格式化器
handler.setFormatter(formatter)
# 为日志记录器增加处理器

mylogger.addHandler(handler)
mylogger.propagate = False # 阻断向父logger传递
mylogger.info('my info2 ......') 
mylogger.warning('my warning ......')
mylogger.propagate = True  # 恢复向父logger传递
mylogger.info('my info3 ......') # 实际上是调用了root logger的info方法
  • logging.handlers模块中有RoutatingFileHandler,TimedRoutatingFileHandler等供使用。
import logging
from logging.handlers import TimedRotatingFileHandler

# 根logger
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(threadName)s [%(message)s]")
print(logging.root.handlers)

mylogger = logging.getLogger(__name__)# level为0

# 定义处理器
handler = TimedRotatingFileHandler("mylog.log",'s',20)
handler.setLevel(logging.INFO) # 设置处理器的级别
# 定义格式化器
formatter = logging.Formatter('#%(asctime)s <%(message)s>#')
# 为处理器设置格式化器
handler.setFormatter(formatter)
# 为日志记录器增加处理器
mylogger.addHandler(handler)
# mylogger.propagate = True # 是否传递给父logger
for i in range(20):
    import time
    time.sleep(3)
    mylogger.info('my message {:03} ++'.format(i))

评论