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'
,a
是a.b
的父parent
,a.b
是a
的子 child
。对于foo
来说,名字为foo.bar
、foo.bar.baz
、foo.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
模块提供了debug
、info
、warning
、error
、critical
等快捷函数,可以快速产生相应级别消息。 本质上这些方法使用的都是根记录器对象。
格式字符串
属性名 | 格式 | 描述 |
---|---|---|
消息内容 | %(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 名字 |
注意:funcName
、threadName
、processName
都是小驼峰。
基本配置
- 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
的,如果父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.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))