Python日志管理(logging模块)

  • 发布时间:2017年2月9日 14:43
  • 作者:杨仕航
  • 分类标签: Python
  • 阅读(12075)
  • 评论(0)

开发是一门艺术,也是技术。程序员们经常被各种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, #是否传递给父记录器
        },
    }
}

看起来很内容很多,眼花缭乱?下面听我讲解就不会晕了。


一、日志配置说明


1、日志管理器

该管理器集合是一个字典,包含多个管理器。如下代码:

#日志管理器集合
'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高的日志消息同样会使用处理器列表中的处理器处理。

当然,网上还有些人写的配置没有写这个处理器集合内,直接将处理器写在配置字典内。

这种做法不建议,不方便维护。


2、处理器

日志处理器是设置日志消息用何种方式处理,以及使用该方式所需的相关配置信息。

最常见的有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的代码。


3、日志格式

同样,日志格式也放在一个字典里面。整体配置格式统一。

这个就是为什么我建议大家把日志管理器放到一个字典中的原因。

该日志格式集合是字典,如下代码:

#日志格式集合
'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 用户输出的消息

自行参考设置即可。日志配置讲了一大堆,看看如何使用。


4、过滤器

一个日志管理器可以包含多个处理器。有时在某些环境或场景下,希望不使用某些处理器。

以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天) 关闭调试一文。


二、使用日志logging模块


1、加载日志配置

加载日志配置之后,可以获取一个日志管理器:

#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管理器包含写入文件的处理器。

打开日志文件,可以看到如下内容:

20170210/20170210085358531.png


2、在合适的地方写入日志

那么,我们需要在哪里写入日志,什么时候写入日志?

一般日志按记录的内容可分两种:错误日志和运行日志。


错误日志比较简单,直接在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()


3、多模块使用日志

有时候涉及到多个模块都会使用到日志管理。

那么需要每个模块都加载一次配置吗?

不需要。只需要在程序入口的地方,运行一次加载配置即可。

其他地方直接获取日志管理器,即可使用。


说了这么多,难道不想动手试试吗[笑脸]

上一篇:Django用annotate实现联合统计查询

下一篇:Python异步(多线程和协程)

评论列表

智慧如你,不想发表一下意见吗?

新的评论

清空