很多人可能 会有这样的疑问,要在程序运行的过程中输出一些信息来确定我的程序在正常运行的话,那直接用print函数不是更直接吗,我何必要那么麻烦的用logging呢?其实我一开始也是有这种想法的,所以一直没有怎么用过logging模块。但当写的项目的代码数开始增加并且项目更加复杂后,才发现logging模块“真香”!!!接下来来说为什么要选择logging模块。
对logging
模块的基本配置可以通过logging.basicConfig()
函数来实现的,函数定义如下:
logging.basicConfig(level=logging.WARNING, format, filename, filemode='a', datefmt, stream)
各个参数的含义:
level
:日志级别(默认值为logging.WARNING
),系统内置的级别有六种:DEBUG、INFO、WARNING、ERROR以及CRITICAL,系统会输出到控制台或者保存到日志文件的日志信息一定是等于或者高于当前等级的信息,例如当level=logging.WARNING
时,只会输出或者保存WARNING、ERROR和CRITICAL级别的信息,其他级别的日志信息不会显示或保存,这也是level
这个参数控制输出的意义所在。日志级别值如下表,
日志级别 | 数值 |
---|---|
logging.CRITICAL | 50 |
logging.ERROR | 40 |
logging.WARNING | 30 |
logging.INFO | 20 |
logging.DEBUG | 10 |
logging.NOTSET | 0 |
format
:日志信息输出的模板,其中可以包含的内容包括以下:
filename
:指定日志文件的名字
filemode
:指定日志文件的打开模式(默认值为’a’),可以选择’w’(清空已有内容再写入)或者’a’(在已有内容的末尾追加新内容)
datefmt
:指定时间格式,同time.strftime()
stream
:指定日志的输出流(默认为sys.stderr),可以指定输出到sys.stderr,sys.stdout或者日志文件。当filename
和stream
两个参数同时指定时,系统会忽略掉stream
的值
通过basicConfig()
函数进行配置的一个简单的例子如下:
import logging
logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
logging.info("Start print log")
logging.debug("Do something")
logging.warning("Something maybe fail.")
logging.error("Somthing goes wrong!")
logging.critical("Critical error like out of memory and etc.")
logging.fatal("Fatal error that interrupt execution of program.")
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.error("Somthing goes wrong!")
logger.critical("Critical error like out of memory and etc.")
logger.fatal("Fatal error that interrupt execution of program.")
上面的代码输出为:
从这个例子可以看出,输出信息的级别是通过不同的函数来实现的,当level=logging.INFO
时,DEBUG级别的日志信息是不会输出的。同时,我们可以通过logging
或者Logger
对象两种方式来输出不同级别的日志信息。
首先我们设置logging,同时创建一个FileHandler,并对输出的日志信息的格式进行设置,再将设置好的FileHandler加入到logger,这样就能将日志写入到指定的文件中了。
import logging
# 设置logger
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
# 创建并对Filehandler进行设置
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 将Filehandler加到logger中
logger.addHandler(handler)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
日志文件中的输出为:
将日志文件同时输出到屏幕和写入文件,其实只比上一部分多一步,除了要将一个FileHandler加入到logger中,还要将一个Streamhandler添加到logger中,StreamHandler就是用于将日志信息输出到屏幕上。为了控制输出到屏幕和写入文件的日志格式,我们还可以调用相应函数对这两个对象进行设置。
import logging
logger = logging.getLogger(__name__)
logger.setLevel(level = logging.INFO)
# 创建并设置FileHandler
handler = logging.FileHandler("log.txt")
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
# 将FileHandler添加到logger
logger.addHandler(handler)
# 创建并设置StreamHandler
console = logging.StreamHandler()
console.setLevel(logging.INFO)
# 将StreamHandler添加到logger
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
logger.info("Finish")
控制台的输出为:
日志文件的输出为:
这里其实存在一个问题:当我们通过上面的代码将日志信息同时输出到屏幕和写入文件时,我们可以同时对logger、FileHandler和StreamHandler调用
setLevel()
函数,设置日志级别,那么最终输出到屏幕和写入文件的日志信息是怎么筛选的呢?这个问题其实很好解释:首先,根据logger设置的日志级别对日志信息进行筛选,得到的日志信息用于输出到屏幕和写入文件。接下来,当输出到屏幕和写入文件时,再分别根据各自设置的日志级别从上一步筛选得到的日志信息中得到可以输出或写入的日志信息。
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)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
rHandler.setFormatter(formatter)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(formatter)
logger.addHandler(rHandler)
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
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服务器 |
logging中捕获traceback错误是通过try...except
语句实现的,通过在这个语句中加入logging语句,就可以将信息输出到屏幕或者写入到日志文件。一个简单的例子如下:
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)
logger.addHandler(handler)
logger.addHandler(console)
logger.info("Start print log")
logger.debug("Do something")
logger.warning("Something maybe fail.")
try:
open("sklearn.txt","rb")
except (SystemExit,KeyboardInterrupt):
raise
except Exception:
'''
logger.error()也可以换成logger.exception(), 两者是等价的
'''
logger.error("Faild to open sklearn.txt from logger.error",exc_info = True)
logger.info("Finish")
控制台输出为:
写入文件的内容为:
主模块mainModule.py
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
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")
控制台输出为:
首先我们在主模块定义名为’mainModule’的logger,并对它进行配置,就可以在解释器进程里面的其他地方通过logging.getLogger('mainModule')
函数得到这个logger,不需要重新配置,可以按原有的配置直接使用。
父子logger是通过命名来识别的,任意以"mainModule"开头的logger都是它的子logger,比如"mainModule.sub"。子logger可以共享父logger的定义和配置,这样免去了在不同模块间重复定义日志输出格式的问题,也保证了日志信息的一致性。
Python Logging 模块完全解读
python 日志 logging模块(详细解析)
Python常用模块大全
logging-Logging facility for Python