关于本站
1、基于Django+Bootstrap开发
2、主要发表本人的技术原创博客
3、本站于 2015-12-01 开始建站
开发是一门艺术,也是技术。程序员们经常被各种Bug折腾到死去活来。
Bug也是加班和拖延开发进度的罪魁祸首,然而大部分Bug是我们自己写的(哭笑不得)。
所以添加日志是十分重要。一些经验老道的程序员在项目开始的时候,第一步就加入日志管理模块。
在Python中,有个logging模块可以轻松实现日志功能。
但该模块需要填写配置信息。该配置有些复杂,阻碍了不少使用它的新手。
logging模块设置配置有3种方式:
1)logging等自身的方法设置
2)通过文件加载配置(fileConfig)
3)通过字典加载配置(dictConfig)
建议使用字典加载配置的方式。因为该方式定义的参数比较明显直观,便于维护。同样也可以把字段的数据转成json字符串保存到文件中。
logging的字典配置信息主要分5个部分:基本设置、日志内容格式、过滤器、处理器和管理器。
先看看一个标准的日志配置:
#coding:utfi-8 import os BASE_DIR = BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DEBUG = True #标记是否在开发环境 #给过滤器使用的判断 class RequireDebugTrue(logging.Filter): #实现filter方法 def filter(self, record): return DEBUG LOGGING = { #基本设置 'version':1, #日志级别 'disable_existing_loggers':False, #是否禁用现有的记录器 #日志格式集合 'formatters':{ #标准输出格式 'standard':{ #[具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容 'format':'[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:%(levelname)s(%(lineno)d)]\n[%(module)s:%(funcName)s]:%(message)s' } }, #过滤器 'filters':{ 'require_debug_true': { '()': RequireDebugTrue, } }, #处理器集合 'handlers':{ #输出到控制台 'console':{ 'level':'DEBUG', #输出信息的最低级别 'class':'logging.StreamHandler', 'formatter':'standard', #使用standard格式 'filters': ['require_debug_true',], #仅当 DEBUG = True 该处理器才生效 }, #输出到文件 'log':{ 'level':'DEBUG', 'class':'logging.handlers.RotatingFileHandler', 'formatter':'standard', 'filename':os.path.join(BASE_DIR, 'debug.log'), #输出位置 'maxBytes':1024*1024*5, #文件大小 5M 'backupCount': 5, #备份份数 'encoding': 'utf8', #文件编码 }, }, #日志管理器集合 'loggers':{ #管理器 'default':{ 'handlers':['console', 'log'], 'level':'DEBUG', 'propagate':True, #是否传递给父记录器 }, } }
看起来很内容很多,眼花缭乱?下面听我讲解就不会晕了。
该管理器集合是一个字典,包含多个管理器。如下代码:
#日志管理器集合 'loggers':{ #管理器 'default':{ 'handlers':['console', 'log'], 'level':'DEBUG', 'propagate':True, #是否传递给父记录器 }, }
其中键名是管理器的名称,键值是管理器的配置。
管理器需要配置3个内容:
1)handlers:必填,处理器列表,处理传递进来的日志消息用哪些处理器。
2)level:必填,日志消息处理的最低级别,处理该哪些日志消息。
3)propagate:选填,是否传递给父记录器(这个一般写True即可)。
handlers同样在日志配置里面配置,稍后详细讲解。
level是针对日志消息。日志消息存在级别,共6个。
级别由大到小分别是 CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET
若设置DEBUG级别,比DEBUG高的日志消息同样会使用处理器列表中的处理器处理。
当然,网上还有些人写的配置没有写这个处理器集合内,直接将处理器写在配置字典内。
这种做法不建议,不方便维护。
日志处理器是设置日志消息用何种方式处理,以及使用该方式所需的相关配置信息。
最常见的有3种处理方式:
1)将日志信息输出到控制台
2)将日志信息输出到文件
3)将日志信息发送邮件到指定邮箱
其中,第3种在Django使用比较多。有兴趣可以参考我的网站搭建(第12天) 关闭调试一文。
处理器写在处理器集合(handlers)中,如下代码有两个处理器:
#处理器集合 'handlers':{ #输出到控制台 'console':{ 'level':'DEBUG', #输出信息的最低级别 'class':'logging.StreamHandler', 'formatter':'standard', #使用standard格式 'filters': ['require_debug_true',], #仅当 DEBUG = True 该处理器才生效 }, #输出到文件 'log':{ 'level':'DEBUG', 'class':'logging.handlers.RotatingFileHandler', 'formatter':'standard', 'filename':os.path.join(BASE_DIR, 'debug.log'), #输出位置 'maxBytes':1024*1024*5, #文件大小 5M 'backupCount': 5, #备份份数 'encoding': 'utf8', #文件编码 }, }
其中键名是处理器的名称,键值是处理器的配置。
处理器配置的内容包含4个基本设置:
1)level:该级别和日志管理器的级别一样,该配置有些重复
2)class:用何种方式处理,该类是继承了logging.handle类
3)formatter:日志内容的格式,该内容稍后讲解。
4)filter:选填,过滤器,该内容稍后讲解。
其他参数是class类初始化所需的参数。
该处理器配置内容比较清晰,我已经在上面写的标准配置写了注释,参考即可。
其中输出到文件中用到os模块和一个BASE_DIR变量,该变量是获取当前文件所在的位置。
若你先自定义自己的class处理类,继承logging.Handler类,实现emit方法即可。
例如下代码:
#coding:utf-8 import logging class TestHandler(logging.Handler): def __init__(self, text): self.text = text def emit(self, record): print("test handler %s" % self.text) #输出初始化传递的变量 print(record.getMessage()) #输出日志消息
这里,我在初始化方法加了text变量。那么设置处理器的时候就需要加入该参数配置。
emit方法中的record是日志消息,还可以获取更多内容(消息追踪的内容也可以获取)。
可参考Python安装目录里面\Lib\logging的代码。
同样,日志格式也放在一个字典里面。整体配置格式统一。
这个就是为什么我建议大家把日志管理器放到一个字典中的原因。
该日志格式集合是字典,如下代码:
#日志格式集合 'formatters':{ #标准输出格式 'standard':{ #[具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容 'format':'[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:%(levelname)s(%(lineno)d)]\n[%(module)s:%(funcName)s]:%(message)s' } },
其中键名是日志格式的名称,键值是日志格式的内容。
日志格式只需要设置format格式化文本即可。格式化文本如下:
%(name)s Logger的名字
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(filename)s 调用日志输出函数的模块的文件名
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
%(message)s 用户输出的消息
自行参考设置即可。日志配置讲了一大堆,看看如何使用。
一个日志管理器可以包含多个处理器。有时在某些环境或场景下,希望不使用某些处理器。
以console那个处理器为例。若用py2exe等打包Python脚本,不要添加console处理器。
打包之后的程序会把控制台的消息也输出一个log日志(而且还弹窗提示)。此时我要通过过滤器判断是否需要输出到控制台该处理器。
过滤器同样放在一个过滤器集合中,如下代码:
#过滤器 'filters':{ 'require_debug_true': { '()': RequireDebugTrue, } },
其中键名是过滤器的名称,键值是过滤器的内容。
过滤器内容只需要设置一个属性,该属性值是继承了logging.Filter的类。如下代码:
DEBUG = True #标记是否在开发环境 #给过滤器使用的判断 class RequireDebugTrue(logging.Filter): #实现filter方法 def filter(self, record): return DEBUG
继承该类需要实现filter方法,返回一个布尔值。
在开发环境,我设置DEBUG为True;在客户端,我设置DEBUG为False。从而控制是否需要使用某些处理器。
当然,该值你也可以想办法通过一下判断动态设置。
过滤器在Django会用到,可参考我的网站搭建(第12天) 关闭调试一文。
加载日志配置之后,可以获取一个日志管理器:
#coding:utf-8 import logging import logging.config #加载前面的标准配置 logging.config.dictConfig(LOGGING) #获取loggers其中的一个日志管理器 logger = logging.getLogger("default") #尝试写入不同消息级别的日志信息 logger.debug("debug message") logger.info("info message") logger.warn("warn message") logger.error("error message") logger.critical("critical message")
前面写的标准日志配置,default管理器包含写入文件的处理器。
打开日志文件,可以看到如下内容:
那么,我们需要在哪里写入日志,什么时候写入日志?
一般日志按记录的内容可分两种:错误日志和运行日志。
错误日志比较简单,直接在try的错误处理代码块中写入。
#coding:utf-8 import logging import logging.config #加载前面的标准配置 logging.config.dictConfig(LOGGING) #获取loggers其中的一个日志管理器 logger = logging.getLogger("default") def test(): try: a = 1/0 except Exception as e: logger.error(e.message) #写入错误日志 #其他错误处理代码 pass
运行日志可以专门写个装饰器,记录执行该方法的开始时间和结束时间。
需要记录运行日志的方法都加上该装饰器即可。如下代码:
#coding:utf-8 import logging import logging.config #加载前面的标准配置 logging.config.dictConfig(LOGGING) #日志记录装饰器 def recode_log(func): #获取loggers其中的一个日志管理器 logger = logging.getLogger("default") def warpper(*args, **kw): #记录开始运行时间 logger.debug("start %s" % func.func_name) #运行方法 func() #记录结束运行时间 logger.debug("end %s" func.func_name) return warpper #测试方法 @recode_log def test(): print("run test") if __name__ == "__main__": test()
有时候涉及到多个模块都会使用到日志管理。
那么需要每个模块都加载一次配置吗?
不需要。只需要在程序入口的地方,运行一次加载配置即可。
其他地方直接获取日志管理器,即可使用。
说了这么多,难道不想动手试试吗[笑脸]