python之模块分类(七)
很多程序都有记录日志的需求,并且日志中包含的信息即有正常的程序访问日志,还可能有错误、警告等信息输出,python的logging模块提供了标准的日志接口,你可以通过它存储各种格式的日志,logging的日志可以分为 debug()
, info()
, warning()
, error()
and critical() 5个级别,
一、最简单用法
#Author:Anliu import logging #logging.warning("usr [anliu] attempted wrong password more than 3 times") #logging.critical("server is down") logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s %(name)s',datefmt='%m-%d-%Y %I:%M:%S %p',filename='example.log',level=logging.INFO) logging.debug("This message should go to the log file") logging.info("so should this") logging.warning("And this,too")
二、日志级别
#Author:Anliu import logging #将信息打印到控制台上 logging.debug(u"调试") logging.info(u"启动服务") logging.warning(u"版本即将过期") logging.error(u"404") logging.critical(u"奔溃")
回显:
上面可以看到只有后面三个能打印出来
默认生成的root logger的level是logging.WARNING,低于该级别的就不输出了
级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG
debug : 打印全部的日志,详细的信息,通常只出现在诊断问题上
info : 打印info,warning,error,critical级别的日志,确认一切按预期运行
warning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作
error : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能
critical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行
这时候,如果需要显示低于WARNING级别的内容,可以引入NOTSET级别来显示:
#Author:Anliu import logging #将信息打印到控制台上 logging.basicConfig(level=logging.NOTSET) logging.debug(u"调试") logging.info(u"启动服务") logging.warning(u"版本即将过期") logging.error(u"404") logging.critical(u"奔溃")
回显:
三、将日志写入到文件
3.1 将日志写入到文件
设置logging,创建一个FileHandler,并对输出消息的格式进行设置,将其添加到logger,然后将日志写入到指定的文件中。
#Author:Anliu import logging logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) handler = logging.FileHandler("log.txt") handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.info("this is a test from info") logger.debug("This is a test from debug") logger.warning("This is a test from warning") logger.info("Finish")
在log.txt中写入:
2020-04-19 21:58:06,060 - __main__ - INFO - this is a test from info 2020-04-19 21:58:06,060 - __main__ - WARNING - This is a test from warning 2020-04-19 21:58:06,060 - __main__ - INFO - Finish
3.2 将日志同时输出到屏幕和日志文件
logger中添加StreamHandler,可以将日志输出到屏幕上。
#Author:Anliu import logging logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) handler = logging.FileHandler("log.txt") handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(formatter) logger.addHandler(handler) logger.addHandler(console) logger.info("this is a test from info") logger.debug("This is a test from debug") logger.warning("This is a test from warning") logger.info("Finish")
可以发现,logging有一个日志处理的主对象,其他处理方式都是通过addHandler添加进去,logging中包含的handler主要有如下几种:
handler名称:位置;作用 StreamHandler:logging.StreamHandler;日志输出到流,可以是sys.stderr,sys.stdout或者文件 FileHandler:logging.FileHandler;日志输出到文件 BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式 RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚 TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件 SocketHandler:logging.handlers.SocketHandler;远程输出日志到TCP/IP sockets DatagramHandler:logging.handlers.DatagramHandler;远程输出日志到UDP sockets SMTPHandler:logging.handlers.SMTPHandler;远程输出日志到邮件地址 SysLogHandler:logging.handlers.SysLogHandler;日志输出到syslog NTEventLogHandler:logging.handlers.NTEventLogHandler;远程输出日志到Windows NT/2000/XP的事件日志 MemoryHandler:logging.handlers.MemoryHandler;日志输出到内存中的指定buffer HTTPHandler:logging.handlers.HTTPHandler;通过"GET"或者"POST"远程输出到HTTP服务器
3.3 日志回滚
使用RotatingFileHandler,可以实现日志回滚:
#Author:Anliu import logging from logging.handlers import RotatingFileHandler logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) #定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K rHandler = RotatingFileHandler("log.txt",maxBytes = 1*1024,backupCount = 3) rHandler.setLevel(logging.INFO) #handler = logging.FileHandler("log.txt") #handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') rHandler.setFormatter(formatter) #handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(formatter) logger.addHandler(rHandler) logger.addHandler(console) logger.info("this is a test from info") logger.debug("This is a test from debug") logger.warning("This is a test from warning") logger.info("Finish")
3.4 捕获traceback
Python中的traceback模块被用于跟踪异常返回信息,可以在logging中记录下traceback:
#Author:Anliu import logging #from logging.handlers import RotatingFileHandler logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) #定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K #rHandler = RotatingFileHandler("log.txt",maxBytes = 1*1024,backupCount = 3) #rHandler.setLevel(logging.INFO) handler = logging.FileHandler("log.txt") handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') #rHandler.setFormatter(formatter) handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) #console.setFormatter(formatter) #logger.addHandler(rHandler) logger.addHandler(handler) logger.addHandler(console) logger.info("this is a test from info") logger.debug("This is a test from debug") logger.warning("This is a test from warning") logger.info("Finish") try: open("sklearn.txt","rb") except (SystemExit,KeyboardInterrupt): raise except Exception: logger.error("Faild to open sklearn.txt from logger.error",exc_info = True) logger.info("Finish")
3.5 多模块使用logging
主模块mainModule.py:
#Author:Anliu import logging import subModule logger = logging.getLogger("mainModule") logger.setLevel(level=logging.INFO) handler = logging.FileHandler("log.txt") handler.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s - %(name)s -[ %(levelname)s ]- %(message)s') handler.setFormatter(formatter) console = logging.StreamHandler() console.setLevel(logging.INFO) console.setFormatter(formatter) logger.addHandler(handler) logger.addHandler(console) logger.info("creating an instance of subModule.subModuleClass") a = subModule.SubModuleClass() logger.info("calling subModule.subModuleClass.doSomething") a.doSomething() logger.info("done with subModule.subModuleClass.doSomething") logger.info("calling subModule.some_function") subModule.som_function() logger.info("done with subModule.some_function")
子模块subModule.py:
#Author:Anliu import logging module_logger = logging.getLogger("mainModule.sub") class SubModuleClass(object): def __init__(self): self.logger = logging.getLogger("mainModule.sub.module") self.logger.info("creating an instance in SubModuleClass") def doSomething(self): self.logger.info("do something in SubModule") a = [] a.append(1) self.logger.debug("list a = " + str(a)) self.logger.info("finish something in SubModuleClass") def som_function(): module_logger.info("call function some_function")
首先在主模块定义了logger'mainModule',并对它进行了配置,就可以在解释器进程里面的其他地方通过getLogger('mainModule')得到的对象都是一样的,不需要重新配置,可以直接使用。定义的该logger的子logger,都可以共享父logger的定义和配置,所谓的父子logger是通过命名来识别,任意以'mainModule'开头的logger都是它的子logger,例如'mainModule.sub'。
实际开发一个application,首先可以通过logging配置文件编写好这个application所对应的配置,可以生成一个根logger,如'PythonAPP',然后在主函数中通过fileConfig加载logging配置,接着在application的其他地方、不同的模块中,可以使用根logger的子logger,如'PythonAPP.Core','PythonAPP.Web'来进行log,而不需要反复的定义和配置各个模块的logger。
4.通过配置文件创建模块
通过配置文件可以将配置信息和代码分离了,这一方面降低了日志的维护成本,同时还使得非开放人员也能够很容易的修改日志配置。
例如:
创建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=1 [handler_consoleHandler] class=StreamHandler args=(sys.stdout,) level=DEBUG formatter=simpleFormatter [handler_fileHandler] class=FileHandler args=("logging.log", "a") #level=ERROR level=DEBUG formatter=simpleFormatter [formatter_simpleFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt=
创建python代码:
#Author:Anliu import logging.config #d读取日志配置文件内容 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.")
fname:表示配置文件的文件名或文件对象;
defaults:指定传给ConfigParser的默认值;
disable_existing_loggers:这是一个布尔值,默认值为True(为了向后兼容)表示禁用已经存在的logger,除非它们或它们的祖先明确的出现在日志配置中;如果该值为False,则对已存在的loggers保持启动状态;
1.关于fileConfig()函数的说明:
该函数实际上是对configparser模块的封装,函数定义:该函数定义在logging.config模块下
logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)
参数说明:
(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;
2.配置文件格式说明:
上面提到过,fileConfig()函数是对ConfigParser/configparser模块的封装,也就是说fileConfig()函数是基于ConfigParser/configparser模块来理解日志配置文件的。换句话说,fileConfig()函数所能理解的配置文件基础格式是与ConfigParser/configparser模块一致的,因此在此基础上对文件中包含的section和option做了以下规定和限制,比如:
5 通过JSON或者YAML文件配置logging模块
Python 3.2中引入的一种新的配置日志记录的方法:用字典来保存logging配置信息。这相对于上面的基于配置文件来保存logging配置信息的方式来说,功能更加强大,也更加灵活,因为我们可以把很多数据转换成字典。比如:我们可以使用JSON格式的配置文件、YMAL格式的配置文件,然后将它们填充到一个配置字典中;或者,我们也可以用Python代码构建这个配置字典,或者通过socket接收pickled序列化后的配置信息。总之,我们可以使用应用程序可以操作的任何方法来构建这个配置字典。在这个例子中,我们将使用YAML格式来完成上面同样的日志配置。
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]
创建python文件如下:
#Author:Anliu import logging.config import yaml with open("logging.yaml", "r") as f_conf: dict_conf = yaml.load(f_conf,Loader=yaml.FullLoader) 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.")
1.关于dictConfig()函数说明:
该函数实际上是对configparser模块的封装,该函数定义在logging.config模块下:
logging.config.dictConfig(config)
该函数可以从一个字典对象中获取日志配置信息,config参数就是这个字典对象。关于这个字典对象的内容规则会在下面进行描述。
2.配置字典说明:
无论是上面提到的配置文件,还是这里的配置字典,它们都要描述出日志配置说需要创建的各种对象以及这些对象之间的关联关系。比如:可以先创建一个名为"simple"的格式器formatter;然后创建一个名为"console"的处理器handler,并指定该handler输出日志所使用的格式器为"simple";然后再创建一个日志器logger,并指定所使用的处理器为"console"。传递给dictConfig()函数的字典对象只能包含下面这些keys,其中version是必须指定的key,其它key都是可选项:
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: 33.关于外部对象的访问:
需要说明的是,上面所使用的对象并不限于logging模块所提供的对象,我们可以实现自己的formatter或handler类。另外,这些类的参数也许需要包含sys.stderr这样的外部对象。如果配置字典对象是使用Python代码构造的,可以直接使用sys.stdout、sys.stderr;但是当通过文本文件(如:JSON、YAML格式的配置文件)提供配置时就会出现问题,因为在文本文件中,没有标准的方法来区分sys.stderr和字符串"sys.stderr",为了区分它们,配置系统会在字符串值中查找特定的前缀,例如:"ext://sys.stderr"中"ext://"会被移除,然后import sys.stderr。