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) |
| print(log1 is log2) |
| |
| |
root 根据日志记录器调用
| root = logging.root |
| root = logging.Logger.root |
| root = logging.getLogger() |
记录器Logger
- 日志记录器都是Logger类的实例,可以通过它实例化得到。但是logging模块也提供了工厂方法。
- Logger实例的构建,使用Logger类也行,但推荐getLogger函数。
| Logger.manager = Manager(Logger.root) |
| 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模块为了使用简单,提供了一些快捷方法,这些方法本质上都用到了记录器实例,即根记录器实 例。
| |
| 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.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 |
| |
| |
| root = logging.getLogger() |
| print(1, root.name, type(root) , root.parent) |
| |
| parent = logging.getLogger('a') |
| print(2, parent.name, type(parent), parent.parent.name , parent.parent is root ) |
| |
| child = logging.getLogger('a.b') |
| print(3, child.name, type(child), child.parent.name , child.parent is parent ) |
级别Level
级别 |
数值 |
CRITICAL |
50 |
ERROR |
40 |
WARNING |
30 |
INFO |
20 |
DEBUG |
10 |
NOTSET |
0 |
级别可以是一个整数。0
表示未设置, 有特殊意义;
级别可以用来表示日志消息级别、记录器级别、处理器级别;
消息级别
记录器级别
- 日志输出必须依靠记录器,记录器设定自己的级别,它决定着消息是否能够通过该日志记录器输出;
- 如果日志记录器未设置自己的级别,默认级别值为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.warning('log test info') |
| 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函数,本质上是对根记录器做最基本配置。
| |
| |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s % (threadName)s [%(message)s]") |
| |
| logging.basicConfig(level=logging.INFO) |
| logging.info('info 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') |
处理器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) |
- 每一个记录器可以按照一定格式输出日志,实际上是按照记录器上的处理器上的设置的格式化器的格式
字符串输出日志信息。
- 如果处理器上没有设置格式化器,会调用缺省_defaultFormatter,而缺省的格式符为
| class PercentStyle(object): |
| default_format = '%(message)s' |
| |
| formatter = logging.Formatter('#%(asctime)s <%(message)s>#') |
| handler.setFormatter(formatter) |
日志流
官方日志流转图

消息传递流程:
- 如果消息在某一个
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 |
| |
| |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(threadName)s [%(message)s]") |
| print(logging.root.handlers) |
| |
| mylogger = logging.getLogger(__name__) |
| mylogger.info('my 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 |
| mylogger.info('my info2 ......') |
| mylogger.warning('my warning ......') |
| mylogger.propagate = True |
| mylogger.info('my info3 ......') |
- 在
logging.handlers
模块中有RoutatingFileHandler
,TimedRoutatingFileHandler
等供使用。
| import logging |
| from logging.handlers import TimedRotatingFileHandler |
| |
| |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(threadName)s [%(message)s]") |
| print(logging.root.handlers) |
| |
| mylogger = logging.getLogger(__name__) |
| |
| |
| handler = TimedRotatingFileHandler("mylog.log",'s',20) |
| handler.setLevel(logging.INFO) |
| |
| formatter = logging.Formatter('#%(asctime)s <%(message)s>#') |
| |
| handler.setFormatter(formatter) |
| |
| mylogger.addHandler(handler) |
| |
| for i in range(20): |
| import time |
| time.sleep(3) |
| mylogger.info('my message {:03} ++'.format(i)) |