在一个项目中,日志模块是必不可少的,健壮的日志输出有助于及时发现问题和调试。python的日志模块logging为我们提供了强大的日志功能。
一.logging模块简介
我们先看一个标准的程序:
import logging
logger=logging.getLogger()
handler=logging.FileHandler("Log_test.txt")
logger.addHandler(handler)
logger.setLevel(logging.NOTSET)
logger.error("This is an error message")
logger.info("This is an info message")
logger.critical("This is a critical message")
这个程序应用了logging中常用的filehandler,在日志文件中会出现三行内容:
This is an error message
This is an info message
This is a critical message
上面程序的第2行是生成一个日志对象,里面的参数时日志的名字,可以带,也可以不带。第3行是生成了一个handler,logging支持很多种 Handler,像FileHandler,SocketHandler等待,这里由于我们要写文件,所以用了FileHandler,它的参数就是 filename,默认当前路径,当然我们可以自己指定路径。
第5行设置日志信息输出的级别。Logging提供了多种日志级别,如 NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL等,每个级别都对应一个数值,如果我们不自己设置输出级别,那么系统 会执行缺省级别,值为30,就warning。当写入日志时,小于指定级别的信息将被忽略。因此为了输出想要的日志级别一定要设置好此参数。这里我设为NOTSET(值为0),也就是想输出所有信息。
关于logging模块的详细介绍,以及程序实例,可以参考这个链接
http://www.red-dove.com/python_logging.html
二.实际项目中的应用
目前我做的一个爬虫项目,程序由计划任务定时执行,程序一旦跑起来就不会天天去关注,所以对日志模块就以下四个要求。
1.详细的debug信息写入文件,一旦程序出错,可以在文件里面找到详细的出错信息
2.控制台实时打印重要信息,如一些error和warning
3.由于程序属于无人监管状态,当一般错误信息达到一定数量后,自动发邮件提醒。
4.出现重大的错误后,单独发邮件提醒,并挂起程序。如数据库死掉,磁盘阵列无法访问等。
综合以上的要求,我需要用到logging中得FileHandler(由于日志量会很大,这里需要用到RotatingFileHandler,日志达到设定大小后自动写到另外的文件中),Streamhandler(往控制台输出日志),SMTPHanler(用于致命错误的邮件提醒), MemoryHandler(用于缓存一般错误日志,达到阀值之后自动邮件提醒).
logging的配置如下:
#encoding=utf-8
import logging
import time
import smtplib
from email.mime.text import MIMEText
import logging.handlers
import os
#日志文件的路径,FileHandler不能创建目录,这里先检查目录是否存在,不存在创建他
#当然也可以继承之后重写FileHandler的构造函数
LOG_FILE_PATH="log/Execution.log"
dir= os.path.dirname(LOG_FILE_PATH)
if not os.path.isdir(dir):
os.mkdir(dir)
#写入文件的日志等级,由于是详细信息,推荐设为debug
FILE_LOG_LEVEL="DEBUG"
#控制台的日照等级,info和warning都可以,可以按实际要求定制
CONSOLE_LOG_LEVEL="INFO"
#缓存日志等级,最好设为error或者critical
MEMOEY_LOG_LEVEL="ERROR"
#致命错误等级
URGENT_LOG_LEVEL="CRITICAL"
#缓存溢出后的邮件标题
ERROR_THRESHOLD_ACHEIVED_MAIL_SUBJECT="Too many errors occurred during the execution"
#缓存溢出的阀值
ERROR_MESSAGE_THRESHOLD=50
#致命错误发生后的邮件标题
CRITICAL_ERROR_ACHEIVED_MAIL_SUBJECT="Fatal error occurred"
#邮件服务器配置
MAIL_HOST="your exchange server"
FROM="from"
MAIL_TO=["address1","address2"]
class OptmizedMemoryHandler(logging.handlers.MemoryHandler):
"""
由于自带的MemoryHandler达到阀值后,每一条缓存信息会单独处理一次,这样如果阀值设的100,
会发出100封邮件,这不是我们希望看到的,所以这里重写了memoryHandler的2个方法,
当达到阀值后,把缓存的错误信息通过一封邮件发出去.
"""
def __init__(self, capacity,mail_subject):
logging.handlers.MemoryHandler.__init__(self, capacity,flushLevel=logging.ERROR, target=None)
self.mail_subject=mail_subject
self.flushed_buffers=[]
def shouldFlush(self, record):
"""
检查是否溢出
"""
if len(self.buffer) >= self.capacity:
return True
else:
return False
def flush(self):
"""
缓存溢出时的操作,
1.发送邮件 2.清空缓存 3.把溢出的缓存存到另一个列表中,方便程序结束的时候读取所有错误并生成报告
"""
if self.buffer!=[] and len(self.buffer) >= self.capacity:
content=""
for record in self.buffer:
message= record.getMessage()
level= record.levelname
ctime= record.created
t=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(ctime))
content+=t+" "+"*"+level+"* : "+message+"\n"
self.mailNotification(self.mail_subject, content)
self.flushed_buffers.extend(self.buffer)
self.buffer = []
def mailNotification(self,subject,content):
"""
发邮件的方法
"""
msg=MIMEText(content)
msg['Subject']=subject
msg['From']=FROM
msg['To']=";".join(MAIL_TO)
try:
s=smtplib.SMTP()
s.connect(MAIL_HOST)
s.sendmail(FROM,MAIL_TO,msg.as_string())
s.close()
except Exception,e:
self.logger.error(str(e))
MAPPING={"CRITICAL" :50,
"ERROR" : 40,
"WARNING" : 30,
"INFO" : 20,
"DEBUG" : 10,
"NOTSET" :0,
}
class logger:
"""
logger的配置
"""
def __init__(self,logFile,file_level,console_level,memory_level,urgent_level):
self.config(logFile, file_level, console_level, memory_level,urgent_level)
def config(self,logFile,file_level,console_level,memory_level,urgent_level):
#生成root logger
self.logger = logging.getLogger("crawler")
self.logger.setLevel(MAPPING[file_level])
#生成RotatingFileHandler,设置文件大小为10M,编码为utf-8,最大文件个数为100个,如果日志文件超过100,则会覆盖最早的日志
self.fh = logging.handlers.RotatingFileHandler(logFile,mode='a', maxBytes=1024*1024*10, backupCount=100, encoding="utf-8")
self.fh.setLevel(MAPPING[file_level])
#生成StreamHandler
self.ch = logging.StreamHandler()
self.ch.setLevel(MAPPING[console_level])
#生成优化过的MemoryHandler,ERROR_MESSAGE_THRESHOLD是错误日志条数的阀值
self.mh = OptmizedMemoryHandler(ERROR_MESSAGE_THRESHOLD,ERROR_THRESHOLD_ACHEIVED_MAIL_SUBJECT)
self.mh.setLevel(MAPPING[memory_level])
#生成SMTPHandler
self.sh=logging.handlers.SMTPHandler(MAIL_HOST,FROM,";".join(MAIL_TO),CRITICAL_ERROR_ACHEIVED_MAIL_SUBJECT)
self.sh.setLevel(MAPPING[urgent_level])
#设置格式
formatter = logging.Formatter("%(asctime)s *%(levelname)s* : %(message)s",'%Y-%m-%d %H:%M:%S')
self.ch.setFormatter(formatter)
self.fh.setFormatter(formatter)
self.mh.setFormatter(formatter)
self.sh.setFormatter(formatter)
#把所有的handler添加到root logger中
self.logger.addHandler(self.ch)
self.logger.addHandler(self.fh)
self.logger.addHandler(self.mh)
self.logger.addHandler(self.sh)
def debug(self,msg):
if msg is not None:
self.logger.debug(msg)
def info(self,msg):
if msg is not None:
self.logger.info(msg)
def warning(self,msg):
if msg is not None:
self.logger.warning(msg)
def error(self,msg):
if msg is not None:
self.logger.error(msg)
def critical(self,msg):
if msg is not None:
self.logger.critical(msg)
LOG=logger(LOG_FILE_PATH,FILE_LOG_LEVEL,CONSOLE_LOG_LEVEL,MEMOEY_LOG_LEVEL,URGENT_LOG_LEVEL)
if __name__=="__main__":
#测试代码
for i in range(50):
LOG.error(i)
LOG.debug(i)
LOG.critical("Database has gone away")
在主程序中,只需要把LOG对象import进去,就可以通过LOG提供的info,debug。。。的方法输入日志。
当输入一条debug日志,由于等级是最低的,只有RotatingFileHandler的日志等级与之匹配,所以文件中会记录这条debug信息。
当输入一条error日志时,匹配日志等级,文件中会记录这条日志,控制台中会打印出来,MemoryHandler模块会缓存这条日志,并判断是否达到溢出条件。
当输入一条critical日志时,匹配日志等级,文件中会记录这条日志,控制台中会打印出来,MemoryHandler模块会缓存这条日志,并判断是否达到溢出条件,同时SMTPHandler会直接发送致命错误邮件提醒。
使用了这个日志配置之后,我们就可以高枕无忧的干其他事情了,只要没有邮件提醒,程序都在正常运行。。
一旦收到邮件后,及时查看文件日志,也不难找出错误。