Python 配置日志的几种方式
Python配置日志的几种方式
作为开发者,我们可以通过以下3种方式来配置logging:
- (1)使用Python代码显式的创建loggers,handlers和formatters并分别调用它们的配置函数;
- (2)创建一个日志配置文件,然后使用fileConfig()函数来读取该文件的内容;
- (3)创建一个包含配置信息的dict,然后把它传递给dictConfig()函数;
需要说明的是:logging.basicConfig()也属于第一种方式,它只是对loggers,handlers和formatters的配置函数进行了封装。另外,第二种配置方式相对于第一种配置方式的优点在于,它将配置信息和代码分离了,这一方面降低了日志的维护成本,同时还使得非开放人员也能够很容易的修改日志配置。
一、使用Python代码实现日志配置
import logging
import sys
# 创建一个日志器logger并设置其日志级别为DEBUG
logger = logging.getLogger("simple_logger")
logger.setLevel(logging.DEBUG)
# 创建一个流处理器handler并设置其日志级别为DEBUG
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
# 创建一个格式器formatter并将其添加到处理器handler
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
# 为日志器logger添加上面创建的处理器handler
logger.addHandler(handler)
# 日志输出
logger.debug("debug message.")
logger.info("info message.")
logger.warning("warning message.")
logger.error("error message.")
logger.critical("critical message.")
运行输出结果为:
2018-05-06 11:05:34,486 - simple_logger - DEBUG - debug message.
2018-05-06 11:05:34,487 - simple_logger - INFO - info message.
2018-05-06 11:05:34,487 - simple_logger - WARNING - warning message.
2018-05-06 11:05:34,487 - simple_logger - ERROR - error message.
2018-05-06 11:05:34,487 - simple_logger - CRITICAL - critical message.
二、使用配置文件和fileConfig()函数实现日志配置
现在我们通过配置文件的方式来实现与上面相同的功能:
import logging.config
# 读取日志配置文件内容
logging.config.fileConfig("logging.conf")
# 创建一个日志器logger
logger = logging.getLogger("simpleExample")
# 日志输出
logger.debug("debug message.")
logger.info("info message.")
logger.warning("warning message.")
logger.error("error message.")
logger.critical("critical message.")
配置文件logging.conf内容如下:
[loggers]
keys=root,simpleExample
[handlers]
keys=fileHandler,consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=fileHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter
[handler_fileHandler]
class=FileHandler
args=("logging.log", "a")
level=ERROR
formatter=simpleFormatter
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
运行输出结果为:
2018-05-06 12:29:24,849 - simpleExample - DEBUG - debug message.
2018-05-06 12:29:24,849 - simpleExample - INFO - info message.
2018-05-06 12:29:24,849 - simpleExample - WARNING - warning message.
2018-05-06 12:29:24,849 - simpleExample - ERROR - error message.
2018-05-06 12:29:24,849 - simpleExample - CRITICAL - critical message.
1.关于fileConfig()函数的说明:
该函数实际上是对configparser模块的封装,函数定义:该函数定义在logging.config模块下
logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)
参数说明:
- fname:表示配置文件的文件名或文件对象;
- defaults:指定传给ConfigParser的默认值;
- disable_existing_loggers:这是一个布尔值,默认值为True(为了向后兼容)表示禁用已经存在的logger,除非它们或它们的祖先明确的出现在日志配置中;如果该值为False,则对已存在的loggers保持启动状态;
2.配置文件格式说明:
上面提到过,fileConfig()函数是对ConfigParser/configparser模块的封装,也就是说fileConfig()函数是基于ConfigParser/configparser模块来理解日志配置文件的。换句话说,fileConfig()函数所能理解的配置文件基础格式是与ConfigParser/configparser模块一致的,因此在此基础上对文件中包含的section和option做了以下规定和限制,比如:
- (1)配置文件中一定要包含loggers、handlers、formatters这些section,它们通过keys这个option来指定该配置文件中已经定义好的loggers、handlers和formatters,多个值之间用逗号分隔;另外loggers这个section中的keys一定要包含root这个值;
- (2)loggers、handlers、formatters中所所指定的日志器、处理器和格式器都需要在下面单独的section中进行定义。section的命名规则为[logger_loggerName]、[handler_handlerName]、[formatter_formatterName];
- (3)定义logger的section必须指定level和handlers这两个option,level的可取值为DEBUG、INFO、WARNING、ERROR、CRITICAL、NOTSET,其中NOTSET表示所有级别的日志消息都要记录,包括用户定义级别;handlers的值是以逗号分隔的handler名字列表,这里出现的handler必须出现在[handlers]这个section中,并且相应的handler必须在配置文件中有对应的section定义;
- (4)对于非root logger来说,除了level和handlers这两个option之外,还需要一些额外的option,其中qualname是必须提供的option,它表示在logger层级中的名字,在应用代码中通过这个名字得到logger;propagate是可选的,其默认值为1,表示消息将会传递给高层次logger的handler,通常我们需要指定其值为0,这个可以看下面的例子;另外,对于非root logger的level如果设置为NOTSET,系统将会查找高层次的logger来决定此logger的有效level;
- (5)定义handler的section中必须指定class和args这两个option,level和formatter为可选option;class表示用于创建handler的类名,args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section总,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;
- (6)定义formatter的section中的option都是可选的,其中包括format用于指定格式字符串,默认为消息字符串本身;datefmt用于指定asctime的时间格式,默认为"%Y-%m-%d %H:%M:%S";class用于指定格式器类名,默认为logging.Formatter;
说明:配置文件中的class指定类名时,该类名可以是相对于logging模块的相对值,如:FileHandler、handlers.TimeRotatingFileHnadler;也可以是一个绝对路径值,通过普通的import机制来解析,如:自定义的handler类,mypackage.mymodule.MyHandler,但是mypackage需要在Python可用的导入路径中---sys.path
3.对于propagate属性的说明:
实例1:我们把logging.conf中simpleExample这个handler定义中的propagate属性值改为1,或者删除这个option(默认值就是1)
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=1
现在来执行同样的代码,我们会发现,除了在控制台有输出信息,在logging.log文件中也有内容输出,且输出内容如下:
2018-05-06 13:14:52,215 - simpleExample - ERROR - error message.
2018-05-06 13:14:52,215 - simpleExample - CRITICAL - critical message.
这说明simpleExample这个logger在处理完日志记录后,把日志记录传递给了上级的root logger再次做处理,所以才会有两个地方都有日志记录的输出。通常,我们都需要显式的指定propagate的值为0,防止日志记录向上层logger传递;
实例2:现在,我们试着用一个没有在配置文件中定义的logger名称来获取logger:
import logging.config
# 读取日志配置文件内容
logging.config.fileConfig("logging.conf")
# 创建一个日志器logger
# logger = logging.getLogger("simpleExample")
# 用一个没有在配置文件中定义的logger名称来创建一个日志器
logger = logging.getLogger("simpleExample1")
# 日志输出
logger.debug("debug message.")
logger.info("info message.")
logger.warning("warning message.")
logger.error("error message.")
logger.critical("critical message.")
运行程序后,我们会发现控制台没有任何输出,但是在logging.log文件中又多了两行输出:
2018-05-06 13:19:32,702 - simpleExample1 - ERROR - error message.
2018-05-06 13:19:32,702 - simpleExample1 - CRITICAL - critical message.
这是因为,当一个日志器没有被设置任何处理器时,系统会去查找该日志器的上层日志器所设置的日志处理器来处理日志记录。simpleExample1在配置文件中没有被定义,因此logging.getLogger(simpleExample1)这行代码是获取了一个logger实例,并没有给它设置任何处理器,但是它的上级日志器----root logger在配置文件中有定义且设置了一个FileHandler处理器,simpleExample1处理器最终通过这个FileHandler处理器将日志记录输出到logging.log文件中了。
三、使用字典配置信息和dictConfig()函数实现日志配置
Python 3.2中引入的一种新的配置日志记录的方法:用字典来保存logging配置信息。这相对于上面的基于配置文件来保存logging配置信息的方式来说,功能更加强大,也更加灵活,因为我们可以把很多数据转换成字典。比如:我们可以使用JSON格式的配置文件、YMAL格式的配置文件,然后将它们填充到一个配置字典中;或者,我们也可以用Python代码构建这个配置字典,或者通过socket接收pickled序列化后的配置信息。总之,我们可以使用应用程序可以操作的任何方法来构建这个配置字典。在这个例子中,我们将使用YAML格式来完成上面同样的日志配置。
首先,需要安装PyYAML模块:
pip install PyYAML
Python代码如下:
import logging.config
import yaml
with open("logging.yml", "r") as f_conf:
dict_conf = yaml.load(f_conf)
logging.config.dictConfig(dict_conf)
logger = logging.getLogger("simpleExample")
# 日志输出
logger.debug("debug message.")
logger.info("info message.")
logger.warning("warning message.")
logger.error("error message.")
logger.critical("critical message.")
logging.yml配置文件的内容如下:
version: 1
formatters:
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
stream: ext://sys.stdout
console_err:
class: logging.StreamHandler
level: ERROR
formatter: simple
stream: ext://sys.stderr
loggers:
simpleExample:
level: DEBUG
handlers: [console]
propagate: no
root:
level: DEBUG
handlers: [console_err]
运行输出结果为:
2018-05-06 14:39:54,673 - simpleExample - DEBUG - debug message.
2018-05-06 14:39:54,673 - simpleExample - INFO - info message.
2018-05-06 14:39:54,673 - simpleExample - WARNING - warning message.
2018-05-06 14:39:54,673 - simpleExample - ERROR - error message.
2018-05-06 14:39:54,673 - simpleExample - CRITICAL - critical message.
1.关于dictConfig()函数说明:
该函数实际上是对configparser模块的封装,该函数定义在logging.config模块下:
logging.config.dictConfig(config)
该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象。关于这个字典对象的内容规则会在下面进行描述。
2.配置字典说明:
无论是上面提到的配置文件,还是这里的配置字典,它们都要描述出日志配置说需要创建的各种对象以及这些对象之间的关联关系。比如:可以先创建一个名为"simple"的格式器formatter;然后创建一个名为"console"的处理器handler,并指定该handler输出日志所使用的格式器为"simple";然后再创建一个日志器logger,并指定所使用的处理器为"console"。传递给dictConfig()函数的字典对象只能包含下面这些keys,其中version是必须指定的key,其它key都是可选项:
key名称 | 描述 |
---|---|
version | 必选项,其值是一个整数值,表示配置格式的版本,当前唯一可用的值就是1 |
formatters | 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的格式器名称,value为格式器的配置信息组成的dict,如:name |
filters | 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的过滤器名称,value为过滤器的配置信息组成的dict,如:name |
handlers | 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的处理器名称,value为处理器的配置信息组成的dict,如:class、formatter和filters,其中class为必选项,其它为可选项;其他配置信息将会传递给class所指定的处理器类的构造函数,如下面的handlers定义示例中的stream、filename、maxBytes和backupCount等 |
loggers | 可选项,其值是一个字典对象,该字典对象每个元素的key为要定义的日志器名称,value为日志器的配置信息组成的dict,如:level、handlers、filters和propagate等 |
root | 可选项,这是root logger的配置信息,其值也是一个字典对象。除非在定义其它logger时明确指定propagate值为no,否则root logger定义的handlers都会被作用到其它logger上 |
incremental | 可选项,默认值为False。该选项的意义在于,如果这里定义的对象已经存在,那么这里对这些对象的定义是否应用到已存在的对象上。值为False表示,已存在的对象将会被重新定义 |
disable_existing_loggers | 可选项,默认值为True。该选项用于指定是否禁用已存在的日志器loggers,如果increamental的值为True则该选项将会被忽略 |
handlers定义示例:
handlers:
console:
class: logging.StreamHandler
formatter: brief
level: INFO
filters: [allow_foo]
stream: ext://sys.stdout
file:
class: logging.handlers.RotatingFileHandler
formatter: precise
filename: logconfig.log
maxBytes: 1024
backupCount: 3
3.关于外部对象的访问:
需要说明的是,上面所使用的对象并不限于logging模块所提供的对象,我们可以实现自己的formatter或handler类。另外,这些类的参数也许需要包含sys.stderr这样的外部对象。如果配置字典对象是使用Python代码构造的,可以直接使用sys.stdout、sys.stderr;但是当通过文本文件(如:JSON、YAML格式的配置文件)提供配置时就会出现问题,因为在文本文件中,没有标准的方法来区分sys.stderr和字符串"sys.stderr",为了区分它们,配置系统会在字符串值中查找特定的前缀,例如:"ext://sys.stderr"中"ext://"会被移除,然后import sys.stderr。
参考文档