python Logging模块补充
logging模块
日志级别
默认的日志级别是:warning
级别 | 数值 |
---|---|
CRITICAL |
50 |
ERROR |
40 |
WARNING |
30 |
INFO |
20 |
DEBUG |
10 |
NOTSET |
0 |
1. 工作流程
logging的几个组件:记录器(Logger
)、处理器(handler
)、过滤器(filter
)
- 当调用记录器
logger.info(...)
去记录log时,会查询记录器是否启用了当前级别(info),如果没用启用当前级别,就停止,否则就创建一个logRecord
对象 - 然后判断 logger 是否添加有
filter
对象,如果有,则这个过滤器是否会过滤掉这个logRecord
对象,如果过滤掉了,那就停止;否则就继续传递给处理器handler
- 继续往下走,判断当前logger是否开启了
propagate
传播功能,如果开启了传播,就查询当前logger
是否有父级logger
对象,如果有,logRecord
同样往上传递给父级对象... - 从第二步的
handler
往右走,处理器同样可以设置log级别,也可以添加过滤器。当logRecord
传递过来时,同样要判断处理器是否开启了当前logRecord
的级别,然后判断是否能通过过滤器,如果条件都满足,此logRecord
才会被记录或者显示出来。
具体的例子代码,可以看下面第三部分:3. 进阶例子:2. 过滤器
2. 简单例子
1. 直接使用
import logging
logging.warning('Watch out!') # 显示在控制台
logging.info('I told you so') # 不会显示,因为默认日志级别是warning
2. 记录到文件
import logging
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.warning('And this, too')
3. 多模块使用
在一个进程内,多个模块之间,共享 logging 的设置
# myapp.py
import logging
import mylib
def main():
logging.basicConfig(filename='myapp.log', level=logging.INFO) # 配置了一次
logging.info('Started')
mylib.do_something()
logging.info('Finished')
if __name__ == '__main__':
main()
# ==================================================================================
# mylib.py
import logging
def do_something():
logging.info('Doing something') # 可以直接使用,不用重新配置
4. 更改消息的格式
format
参数中的 levelname
, message
等,都是 logRecord
对象的属性(见附录),可以自由配置格式
import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')
3. 进阶例子
1. 多模块使用
一个进程内,无论对 logging.getLogger('someLogger')
进行多少次调用,都会返回同一个 logger 对象的引用。不仅在同一个模块内如此,只要是在同一个 Python 解释器进程中,跨模块调用也是一样。同样是引用同一个对象,应用程序也可以在一个模块中定义和配置一个父 logger,而在另一个单独的模块中创建(但不配置)子 logger,对于子 logger 的所有调用都会传给父 logger。以下是主模块:
test.py
import logging
import auxiliary_module
# 创建一个记录器 'spam_application'
logger = logging.getLogger('spam_application')
logger.setLevel(logging.DEBUG)
# 创建一个文件处理器,并设置级别
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# 创建控制台显示的处理器,并设置 error 级别
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# 创建一个格式器,给各个处理器添加格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 将处理器添加到记录器上
logger.addHandler(fh)
logger.addHandler(ch)
logger.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
logger.info('created an instance of auxiliary_module.Auxiliary')
logger.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
logger.info('finished auxiliary_module.Auxiliary.do_something')
logger.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
logger.info('done with auxiliary_module.some_function()')
以下是辅助模块:
auxiliary_module.py
import logging
# 创建一个子级记录器 spam_application.auxiliary; '.XXX'代表子级logger
module_logger = logging.getLogger('spam_application.auxiliary')
class Auxiliary:
def __init__(self):
self.logger = logging.getLogger('spam_application.auxiliary.Auxiliary') # 子级的子级
self.logger.info('creating an instance of Auxiliary')
def do_something(self):
self.logger.info('doing something')
a = 1 + 1
self.logger.info('done doing something')
def some_function():
module_logger.info('received a call to "some_function"')
结果:
2005-03-23 23:47:11,663 - spam_application - INFO -
creating an instance of auxiliary_module.Auxiliary
2005-03-23 23:47:11,665 - spam_application.auxiliary.Auxiliary - INFO -
creating an instance of Auxiliary
2005-03-23 23:47:11,665 - spam_application - INFO -
created an instance of auxiliary_module.Auxiliary
2005-03-23 23:47:11,668 - spam_application - INFO -
calling auxiliary_module.Auxiliary.do_something
2005-03-23 23:47:11,668 - spam_application.auxiliary.Auxiliary - INFO -
doing something
2005-03-23 23:47:11,669 - spam_application.auxiliary.Auxiliary - INFO -
done doing something
2005-03-23 23:47:11,670 - spam_application - INFO -
finished auxiliary_module.Auxiliary.do_something
2005-03-23 23:47:11,671 - spam_application - INFO -
calling auxiliary_module.some_function()
2005-03-23 23:47:11,672 - spam_application.auxiliary - INFO -
received a call to 'some_function'
2005-03-23 23:47:11,673 - spam_application - INFO -
done with auxiliary_module.some_function()
2. 过滤器
过滤器不仅可以过滤筛选logRecord,还可以自定义一些用户的消息
示例1:
import logging
def filter(LogRecord): # 函数以 LogRecord 传递进来的参数
# LogRecord.user = "wang" # 也可以给 LogRecord 添加一些新属性
# print(LogRecord.__dict__)
if LogRecord.levelname in ['ERROR',"CRITICAL"]: # 如果级别不是 error,critical,就过滤掉
return True
return False
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.addFilter(filter) # 可以直接写个函数,甚至匿名函数来当过滤器;不用再写 Filter 类
logger.debug('debug message') # 会被过滤掉
logger.info('info message') # 会被过滤掉
logger.warning('warn message') # 会被过滤掉
logger.error('error message') # 打印出来
logger.critical('critical message') # 打印出来
示例2:
import logging
# 定义一个 Filter
class Filter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
record.user = "wztshine" # 给 LogRecord 对象添加一个新属性:user = "wztshine",谁用了这个过滤器,谁才会有这个属性
if record.levelno >= 40: # 40是Error级别,意思是如果级别大于等于Error,就保留,不过滤掉
return True # True保留,False过滤掉
return False
# 声明一个 Logger,并设置 INFO 级别
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 格式器
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
# 注意最后一个参数:user,是我们在 Filter 里面自己定义的
formatter2 = logging.Formatter('%(name)s - %(levelname)s - %(message)s - %(user)s')
# 用于控制台显示的处理器,设置 WARNING 级别
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
console.setFormatter(formatter)
# 用于记录到文件的处理器,设置 INFO 级别
file = logging.FileHandler("log.txt")
file.setLevel(logging.INFO)
file.addFilter(Filter()) # 添加过滤器
file.setFormatter(formatter2) # 可以使用 formatter2 中的 user 参数
# 将处理器添加到 logger 中
logger.addHandler(console)
logger.addHandler(file)
logger.debug("debug") # 级别小于 logger 设置的 INFO,所以会直接被抛弃
logger.info("info") # 级别等于 logger 设置的 INFO,传递给处理器:console,file;小于 console 设置的 WARNING,被抛弃。等于 file 设置的INFO,但小于 file 的过滤器设置的 Error,被过滤掉
logger.warning("warning") # 级别大于 logger 设置的 INFO,传递给处理器:console,file;等于 console 设置的 WARNING,会显示在控制台。大于 file 设置的INFO,但是小于 file 的过滤器设置的 error,被过滤掉
logger.error("error") # 级别大于 logger 设置的 INFO,传递给处理器:console,file;大于 console 设置的 WARNING,会显示在控制台。大于 file 设置的INFO,等于 file 的过滤器设置的 error,记录到文件
logger.critical("critical") # 级别大于 logger 设置的 INFO,传递给处理器:console,file;大于 console 设置的 WARNING,会显示在控制台。大于 file 设置的INFO,大于 file 的过滤器设置的 error,记录到文件
控制台:
__main__ - WARNING - warning
__main__ - ERROR - error
__main__ - CRITICAL - critical
log.txt 文件:
__main__ - ERROR - error - wztshine
__main__ - CRITICAL - critical - wztshine
3. 多进程使用 QueueHandler
import logging
import logging.config
import logging.handlers
from multiprocessing import Process, Queue
import random
import threading
def logger_thread(q):
while True:
record = q.get() # q 是队列,里面放的是 LogRecord 对象
if record is None:
break
logger = logging.getLogger(record.name) # 获取 logRecord 中存的记录器名
logger.handle(record) # 调用 handle 来处理 logRecord
def worker_process(q):
qh = logging.handlers.QueueHandler(q) # 队列处理器
root = logging.getLogger()
root.setLevel(logging.DEBUG)
root.addHandler(qh)
levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
logging.CRITICAL]
loggers = ['foo', 'foo.bar', 'foo.bar.baz',
'spam', 'spam.ham', 'spam.ham.eggs']
for i in range(10):
lvl = random.choice(levels) # 随机选择一个级别
logger = logging.getLogger(random.choice(loggers)) # 随机从名字中选一个作为记录器的名字
logger.log(lvl, 'Message no. %d', i)
if __name__ == '__main__':
q = Queue()
# logger 设置
d = {
'version': 1,
'formatters': {
'detailed': { # 格式器名称
'class': 'logging.Formatter',
'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s'
}
},
'handlers': {
'console': { # 处理器名称
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter':'detailed'
},
'file': {
'class': 'logging.FileHandler',
'filename': 'mplog.log',
'mode': 'w',
'formatter': 'detailed',
},
'foofile': {
'class': 'logging.FileHandler',
'filename': 'mplog-foo.log',
'mode': 'w',
'formatter': 'detailed',
},
'errors': {
'class': 'logging.FileHandler',
'filename': 'mplog-errors.log',
'mode': 'w',
'level': 'ERROR',
'formatter': 'detailed',
},
},
'loggers': {
'foo': { # 记录器名称
'handlers': ['foofile']
}
},
'root': { # 根记录器设置
'level': 'DEBUG',
'handlers': ['console', 'file', 'errors']
},
}
workers = []
# 开启5个进程
for i in range(5):
wp = Process(target=worker_process, name='worker %d' % (i + 1), args=(q,))
workers.append(wp)
wp.start()
# 加载 log 配置
logging.config.dictConfig(d)
# 开启线程,获取队列里面的 LogRecord 对象,并处理
lp = threading.Thread(target=logger_thread, args=(q,))
lp.start()
# 等待进程结束
for wp in workers:
wp.join()
q.put(None) # 放置一个 None,让线程能够结束,否则会一直等待
lp.join()
logger.setLevel() 不生效?
代码1:
没有设置 logging.basicConfig()
,并且没有添加任何handler
时,默认级别就是warning
,setLevel()
无效
import logging
logger = logging.getLogger()
# 设置logger级别:Debug
logger.setLevel(logging.DEBUG)
logger.debug('debug message') # 没有打印出来
logger.info('info message') # 没有打印出来
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
控制台:
warn message
error message
critical message
代码2:
设置了 logging.basicConfig(level=xxx)
,如果有setLevel()
, 则以setLevel()
为准。
import logging
# 多了一个 basicConfig,设置为 Warning
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger()
# 设置logger级别:Debug
logger.setLevel(logging.DEBUG)
logger.debug('debug message') # 会打印,因为 logger.setLevel() 生效覆盖了之前的配置
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
控制台:
DEBUG:root:debug message
INFO:root:info message
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message
附录1:logRecord对象的属性
属性名称 | 格式 | 描述 |
---|---|---|
args | 此属性不需要用户进行格式化。 | 合并到 msg 以产生 message 的包含参数的元组,或是其中的值将被用于合并的字典(当只有一个参数且其类型为字典时)。 |
asctime | %(asctime)s |
表示 LogRecord 何时被创建的供人查看时间值。 默认形式为 '2003-07-08 16:49:45,896' (逗号之后的数字为时间的毫秒部分)。 |
created | %(created)f |
LogRecord 被创建的时间(即 time.time() 的返回值)。 |
exc_info | 此属性不需要用户进行格式化。 | 异常元组(例如 sys.exc_info )或者如未发生异常则为 None 。 |
filename | %(filename)s |
pathname 的文件名部分。 |
funcName | %(funcName)s |
函数名包括调用日志记录. |
levelname | %(levelname)s |
消息文本记录级别('DEBUG' ,'INFO' ,'WARNING' ,'ERROR' ,'CRITICAL' )。 |
levelno | %(levelno)s |
消息数字的记录级别 (DEBUG , INFO , WARNING , ERROR , CRITICAL ). |
lineno | %(lineno)d |
发出日志记录调用所在的源行号(如果可用)。 |
message | %(message)s |
记入日志的消息,即 msg % args 的结果。 这是在发起调用 Formatter.format() 时设置的。 |
module | %(module)s |
模块 (filename 的名称部分)。 |
msecs | %(msecs)d |
LogRecord 被创建的时间的毫秒部分。 |
msg | 此属性不需要用户进行格式化。 | 在原始日志记录调用中传入的格式字符串。 与 args 合并以产生 message ,或是一个任意对象 (参见 使用任意对象作为消息)。 |
name | %(name)s |
用于记录调用的日志记录器名称。 |
pathname | %(pathname)s |
发出日志记录调用的源文件的完整路径名(如果可用)。 |
process | %(process)d |
进程ID(如果可用) |
processName | %(processName)s |
进程名(如果可用) |
relativeCreated | %(relativeCreated)d |
以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。 |
stack_info | 此属性不需要用户进行格式化。 | 当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。 |
thread | %(thread)d |
线程ID(如果可用) |
threadName | %(threadName)s |
线程名(如果可用) |
附录2:处理器类型
-
StreamHandler
实例发送消息到流(类似文件对象)。 -
FileHandler
实例将消息发送到硬盘文件。 -
BaseRotatingHandler
是轮换日志文件的处理器的基类。它并不应该直接实例化。而应该使用RotatingFileHandler
或TimedRotatingFileHandler
代替它。 -
RotatingFileHandler
实例将消息发送到硬盘文件,支持最大日志文件大小和日志文件轮换。 -
TimedRotatingFileHandler
实例将消息发送到硬盘文件,以特定的时间间隔轮换日志文件。 -
SocketHandler
实例将消息发送到 TCP/IP 套接字。从 3.4 开始,也支持 Unix 域套接字。 -
DatagramHandler
实例将消息发送到 UDP 套接字。从 3.4 开始,也支持 Unix 域套接字。 -
SMTPHandler
实例将消息发送到指定的电子邮件地址。 -
SysLogHandler
实例将消息发送到 Unix syslog 守护程序,可能在远程计算机上。 -
NTEventLogHandler
实例将消息发送到 Windows NT/2000/XP 事件日志。 -
MemoryHandler
实例将消息发送到内存中的缓冲区,只要满足特定条件,缓冲区就会刷新。 -
HTTPHandler
实例使用GET
或POST
方法将消息发送到 HTTP 服务器。 -
WatchedFileHandler
实例会监视他们要写入日志的文件。如果文件发生更改,则会关闭该文件并使用文件名重新打开。此处理器仅在类 Unix 系统上有用; Windows 不支持依赖的基础机制。 -
QueueHandler
实例将消息发送到队列,例如在queue
或multiprocessing
模块中实现的队列。 -
NullHandler
实例对错误消息不执行任何操作。它们由想要使用日志记录的库开发人员使用,但是想要避免如果库用户没有配置日志记录,则显示 'No handlers could be found for logger XXX' 消息的情况。更多有关信息,请参阅 配置库的日志记录 。