1. logging levels
官网: https://docs.python.org/3/tutorial/stdlib2.html#logging
把print()
替换为logging
是第3种方式,和assert
比,logging
不会抛出错误,而且可以输出到文件:
Level | Numeric value | 何时使用 |
---|---|---|
CRITICAL | 50 | 严重错误,表明软件已不能继续运行了。 |
ERROR | 40 | 由于更严重的问题,软件已不能执行一些功能了。 |
WARNING | 30 | 表明发生了一些意外,或者不久的将来会发生问题(如‘磁盘满了’)。软件还是在正常工作。 |
INFO | 20 | 证明事情按预期工作。 |
DEBUG | 10 | 详细信息,典型地调试问题时会感兴趣。 |
NOTSET | 0 | 所有的消息都要记录,这只对根logger有效。 |
级别依次变高:
import logging
logging.debug('Debugging information')
logging.info('Informational message')
logging.warning('Warning:config file %s not found', 'server.conf')
logging.error('Error occurred')
logging.critical('Critical error -- shutting down')</pre>
默认输出:
WARNING:root:Warning:config file server.conf not found
ERROR:root:Error occurred
CRITICAL:root:Critical error -- shutting down
2. logging组件
logging模块与log4j的机制是一样的,只是具体的实现细节不同。模块提供logger,handler,filter,formatter。
Level | Numeric value | 何时使用 |
---|---|---|
logger | 记录器 | 暴露了应用程序代码能直接使用的接口,应用程序往往通过调用它提供的api来记录日志 |
handler | 处理器 | 将(记录器产生的)日志记录发送至合适的目的地 |
filter | 过滤器 | 提供了更好的粒度控制,它可以决定输出哪些日志记录 |
formatter | 格式化器 | 指明了最终输出中日志记录的布局 |
2.1 logger 对象
- logger不可直接实例化
logger从来不会直接被实例化,只会通过模块级方法logging.getLogger(name)
来实例化。如果在多模块调用中getLogger(name)
使用了同样的name
,那么将会返回对同一个logger对象的引用
- logger的
name
是有层级关系的
对于name
为foo
的logger,foo.bar
,foo.bar.baz
都是继承于它的
loggername
的分层结果有点像Python的package。特别是每个模块中使用 logging.getLogger(__name__)
,因为__name__
就是模块的名称
2.1.1 创建logger实例
Logger是一个树形层级结构,在使用接口debug,info,warn,error,critical之前必须创建Logger实例,即创建一个记录器
创建方法:
logger = logging.getLogger(logger_name)
创建Logger实例后,可以使用以下方法进行日志级别设置,增加处理器Handler。
logger.setLevel(logging.ERROR)
设置日志级别为ERROR,即只有日志级别大于等于ERROR的日志才会输出logger.addHandler(handler_name)
为Logger实例增加一个处理器logger.removeHandler(handler_name)
为Logger实例删除一个处理器
2.1.2 默认logger实例
如果没有显式的进行创建,则默认以下项:
- 创建一个root logger
- 日志级别(WARN)
- 处理器Handler(StreamHandler,即将日志信息打印输出在标准输出上)
- 格式化器Formatter(默认的格式即为第一个简单使用程序中输出的格式)。
2.1.3 logger的方法
logger.propagate
如果表达式是TRUE,
logger.setLevel(lvl)
Logger.isEnabledFor(lvl)
lvl级别的消息可以被这个logger处理。
这个方法首先要检查模块级的设置logging.disable(lvl)
,然后检查这个logger的getEffectiveLevel()
方法
- logger.getEffectiveLevel()
表明这个logger有效的级别
2.2 handler对象
Handler处理器类型有很多种,比较常用的有三个,StreamHandler,FileHandler,NullHandler,详情可以访问Python logging.handlers
- StreamHandler
创建方法:
sh = logging.StreamHandler(stream=None)
创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter。
ch.setLevel(logging.WARN) # 指定日志级别,低于WARN级别的日志将被忽略
ch.setFormatter(formatter_name) # 设置一个格式化器formatter
ch.addFilter(filter_name) # 增加一个过滤器,可以增加多个
ch.removeFilter(filter_name) # 删除一个过滤器
- FileHandler
创建方法:
fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False)
# filename是全路径或者相对路径
- NullHandler
NullHandler类位于核心logging包,不做任何的格式化或者输出。
本质上它是个“什么都不做”的handler,由库开发者使用。
2.3 Formatter对象
使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。
创建方法:
formatter = logging.Formatter(fmt=None, datefmt=None)
其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用’%(message)s’。如果不指明datefmt,将使用ISO8601日期格式。
2.4 Filter对象
Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。Filter基类只允许特定Logger层次以下的事件。例如用‘A.B’初始化的Filter允许Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等记录的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串来初始化,所有的事件都接受。
创建方法:
filter = logging.Filter(name='')
2.5 各组件关系
Logger是一个树形层级结构;
Logger可以包含一个或多个Handler和Filter,即Logger与Handler或Fitler是一对多的关系;
一个Logger实例可以新增多个Handler,一个Handler可以新增多个格式化器或多个过滤器,而且日志级别将会继承。
3. 配置使用
配置方式
配置方式 | 方式 | 方式 |
---|---|---|
显式创建 | 显示创建记录器Logger、处理器Handler和格式化器Formatter,并进行相关设置; | 使用的是logging组件类的方法 |
简单方式 | 使用basicConfig()函数直接进行配置 | 使用的是模块级别的方法 |
配置文件 | 使用fileConfig()函数读取配置文件; | 。 |
配置字典 | 使用dictConfig()函数读取配置信息; | 。 |
通过网络进行配置 | 使用listen()函数进行网络配置。 | 。 |
3.1 显示配置
使用的是四大组件
import logging
# create logger
logger_name = "example"
logger = logging.getLogger(logger_name)
logger.setLevel(logging.WARN)
# create file handler
log_path = "./log.log"
filehandler = logging.FileHandler(log_path)
filehandler.setLevel(logging.ERROR)
# create formatter
fmt='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s'
datefmt='%a, %d %b %Y %H:%M:%S'
formatter = logging.Formatter(fmt,datefmt)
# add handle and formatter to logger
filehandler.setFormatter(formatter)
logger.addHandler(filehandler)
# print log info
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
- 显示创建logger
- 显示创建Handler
- 显示创建Formatter
- 将Handler添加Formatter
- 将logger添加Handler
(关系参考3.5节图)
3.2 简单配置
使用的都是模块级别logging
的函数
logging.basicConfig函数各参数:
- format: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:
- %(levelno)s: 打印日志级别的数值
- %(levelname)s: 打印日志级别名称
- %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
- %(filename)s: 打印当前执行程序名
- %(funcName)s: 打印日志的当前函数
- %(lineno)d: 打印日志的当前行号
- %(asctime)s: 打印日志的时间
- %(thread)d: 打印线程ID
- %(threadName)s: 打印线程名称
- %(process)d: 打印进程ID
- %(message)s: 打印日志信息
- filename: 指定日志文件名
- filemode: 和file函数意义相同,指定日志文件的打开模式,’w’或’a’
- datefmt: 指定时间格式,同time.strftime()
- level: 设置日志级别,默认为logging.WARNING
- stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略
import logging
############################这个配置会将日志打印在文件中#################################
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='myapp.log',
filemode='w')
#######################################################################################
##############################这个配置会将日志打印在屏幕上###############################
#定义一个StreamHandler,将INFO级别或更高的日志信息打印到标准错误,并将其添加到当前的日志处理对象#
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)
######################################################################################
logging.debug('This is debug message')
logging.info('This is info message')
logging.warning('This is warning message')
./myapp.log文件中内容为:
Sun, 24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message
Sun, 24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message
Sun, 24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message
屏幕上
root : INFO This is info message
root : WARNING This is warning message
3.3 文件配置
fileConfig()
理解的配置文件格式基于configparser
的功能。
文件必需包含名为[loggers]
, [handlers]
和 [formatters]
的节,标识了文件中定义的各种类型的对象的名字。
如下:
[loggers]
keys=root,log02,log03,log04,log05,log06,log07
[handlers]
keys=hand01,hand02,hand03,hand04,hand05,hand06,hand07,hand08,hand09
[formatters]
keys=form01,form02,form03,form04,form05,form06,form07,form08,form09
对于每一个实体,有一个独立的节标识如何配置该实体。
- [loggers]
节中有名为log01
的logger
,[logger_log01]
的节中包含了相关的配置细节
- 类似的,[handlers]
节中有名为hand01
的handler
,[handler_hand01]
节中包含了相关的配置;
- [formatters]
节中有名为form01
的formatter
,[formatter_form01]
节中包含了相关的配置
[logger_root]
节中包含了根logger
的配置
3.3.1 logger节点
根logger必需指定级别和handler列表。根logger节的例子给定如下。
[logger_root]
level=NOTSET
handlers=hand01
对于非根logger的logger来说,需要一些额外的信息。如下例所示。
[logger_parser]
level=DEBUG
handlers=hand01
propagate=1
qualname=compiler.parser
- level 和 handlers 的解读和根logger的一样,如果非根logger的级别为NOTSET,系统参考高层次的logger来决定logger的有效级别。
- propagate为1表示将消息传递给高层次logger的handler,为0表示不传播。
- qualname是logger在层次中的名字,应用通过该名字得到logger。
3.3.2 handlers节点
[handler_hand02]
class=FileHandler
level=DEBUG
formatter=form02
args=('python.log', 'w')
[handler_hand03]
class=handlers.SocketHandler
level=INFO
formatter=form03
args=('localhost', handlers.DEFAULT_TCP_LOGGING_PORT)
[handler_hand04]
class=handlers.DatagramHandler
level=WARN
formatter=form04
args=('localhost', handlers.DEFAULT_UDP_LOGGING_PORT)
[handler_hand05]
class=handlers.SysLogHandler
level=ERROR
formatter=form05
args=(('localhost', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_USER)
[handler_hand06]
class=handlers.NTEventLogHandler
level=CRITICAL
formatter=form06
args=('Python Application', '', 'Application')
[handler_hand07]
class=handlers.SMTPHandler
level=WARN
formatter=form07
args=('localhost', 'from@abc', ['user1@abc', 'user2@xyz'], 'Logger Subject')
[handler_hand08]
class=handlers.MemoryHandler
level=NOTSET
formatter=form08
target=
args=(10, ERROR)
[handler_hand09]
class=handlers.HTTPHandler
level=NOTSET
formatter=form09
args=('localhost:9022', '/log', 'GET')
- class表示handler的类
- level的解读和logger的一样,NOTSET表示‘记录所有日志’。
- formatter表示了该handler的formatter的名字。如果为空,使用默认formatter (logging._defaultFormatter)。如果指定了名字,它必需出现在[formatters]节中,且在配置文件中有对应的节。
- args是handler类构造函数的参数列表 。参考相关handler的构造函数
3.3.3 formatter节点
[formatter_form01]
format=F1 %(asctime)s %(levelname)s %(message)s
datefmt=
class=logging.Formatter
- format是整体的格式化字符串,
- datefmt是strftime()兼容的日期/时间格式化字符串。如果为空,使用ISO8601格式,基本上等同于格式
%Y-%m-%d %H:%M:%S
。ISO8601格式在上述格式的末尾指明了毫秒,中间有个逗号。ISO8601格式的例子:2003-01-23 00:29:50,411。
- class可选。它表示formatter类的名字(带点的模块和类名)。实例化Formatter子类时该选项很有用。Formatter子类可以用来表示详细/概要的异常回溯。
3.3.4 调用
然后在源码中调用 logging.config.fileConfig()
方法:
import logging
from logging.config import fileConfig
fileConfig('logging_config.conf')
logger = logging.getLogger("log02")
logger.debug('often makes a very good meal of %s', 'visiting tourists')
一个完整且简洁的例子如下:
我们假设文件名为 logging_config.ini 。关于文件格式的更多细节,请参见 日志指南 中的 日志配置 部分。
[loggers]
keys=root
[handlers]
keys=stream_handler
[formatters]
keys=formatter
[logger_root]
level=DEBUG
handlers=stream_handler
[handler_stream_handler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)
[formatter_formatter]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s
然后在源码中调用 logging.config.fileConfig() 方法:
import logging
from logging.config import fileConfig
fileConfig('logging_config.ini')
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')
3.4 通过字典配置
import logging
from logging.config import dictConfig
logging_config = dict(
version = 1,
formatters = {
'f': {'format':
'%(asctime)s %(name)-12s %(levelname)-8s %(message)s'}
},
handlers = {
'h': {'class': 'logging.StreamHandler',
'formatter': 'f',
'level': logging.DEBUG}
},
root = {
'handlers': ['h'],
'level': logging.DEBUG,
},
)
dictConfig(logging_config)
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')