【Python】 日志管理logging

logging

  *****本文参考了http://www.cnblogs.com/dkblog/archive/2011/08/26/2155018.html

■  最最基本的用法

  logging模块用于管理,生成日志信息文件

    ●  首先logging模块可以简单地向屏幕打印出信息:

    logging.warning('Hello')  #会在屏幕上输出WARNING:root:Hello的信息

  在默认情况下,logging只会打印出级别高于warning的信息,比如

    logging.debug("Hello")  #屏幕上不会出现任何信息

    ●  logging内置的警戒级别:

    CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET

  

    ●  当然所谓日志,那肯定是要以文件的形式存在的,通常可以用logging.basicConfig来进行日志文件的指定和一些基础的配置设置

  basicConfig方法可选的参数有:

    level  指定日志的最低记录级别,低于次级别的信息将不被记录,默认值为WARNING

    filename  指定日志文件的文件名

    filemode  指定日志文件的写入模式是w还是a,默认是a

    format   指定日志单条信息的格式

    datefmt  指定日志中日期时间的格式

  比如以下这样配置一个logging参数:

    

logging.basicConfig(level=logging.DEBUG,
                           format="%(asctime)s %(filename)s [line %(lineno)d] %(levelname)s %(message)s ",
                            datefmt = "%a, %Y %b %d %H:%M:%S",
                            filename = "/tmp/test.log"
                            )

  配置完成之后,就可以在程序中用logging.debug/warning/error等方法来记录日志。比如

logging.debug("some debug message.")

#执行完这句话后去/tmp/test.log查看可以看到记录
$cat /tmp/test.log
Mon,2014 May 05 16:29:53 teset_logging.py [line 9] DEBUG some debug message.

   上述的logging配置中的format可用的格式化字符串有:

  %(name)s  Logger的名字

  %(levelname)s  该条记录的级别

  %(lineno)d  该条记录行号

  %(process)d  添加该条记录的PID

  %(module)s  添加此条记录的模块名(就是调用了那句logging语句的脚本的名字)

  %(funcName)s  添加了此条记录的函数名

  %(asctime)s  字符串形式的时间,默认格式是 %y-%m-%d %H:%M:%S:%MS  可以用datefmt来调整  

■  不那么基本的基本用法

  上面虽然可以简单地进行日志输出了,但是总体而言还是比较simple。一个完整的日志输出体系,不仅要有文件形式,还要有HTTP,socket等多种输出形式;不仅要在发出记录日志的信号后对一个文件写日志,还要能同时进行并行的日志记录;不仅要能写日志,还要能自动备份日志,自动对日志进行分片等等。其实这些功能logging模块也都提供了。下面来看一下这些更加高级点的日志记录方法

  使用logging模块记录日志,我们必须要有一个logger对象。上面通过basicConfig来记录日志的就是logging给我们创建了一个默认的logger对象。logger对象可以通过logging.getLogger([name])的方法来获取,name在这里是你给logger取得一个名字,虽然不是必须的,但是规范地取一个名字会很有用,可以体现出不同logger间的层级。logger的名字一般遵从域名的命名习惯,比如可以叫app,然后再创建两个叫app.net和app.net.client的logger来代表app这个logger的下面层级的logger。至于层级到底是怎么回事后面再说吧。

  ●  handler类

  光有一个logger的空壳是没有用的,要进行日志的输出,我们至少还需要一个handler来实际操作。handler的类都在logging.handler里面,有很多种不同的handler来满足不同的日志记录需求。主要有的内置的handler有这些:

  FileHandler(filename [,mode [,encoding [,delay]]])  (注意,FileHandler在logging中而不是logging.handlers)经典的文件handler,就是把日志内容写入filename指定的文件,mode是打开文件时的模式默认是'a',delay是一个boolean参数,如果设置为True,那么会延迟打开文件,即直到发出首条日志消息时才真的调用open方法打开文件

  HTTPHandler(host ,url [, method])  HTTPHandler,用于将日志通过HTTP方法传递给指定的HTTP服务器

  MemoryHandler(capacity [,flushLevel [,target]])  这个处理器用于收集内存中的日志消息,然后将其转移到另一个处理器target。capacity参数指内存缓冲区的大小,单位是字节。如果没有设置target参数那么需要另外调用setTarget()方法来确定target指向的handler,否则将无法运行

  RotatingFileHandler(filename[ ,mode[ ,maxBytes[, backupCount [, enoding [, delay]]]]])  RotatingFileHandler很有意思,和FileHandler类似的,它也是把日志写入文件中的,不过它提供了一个功能,就是当日志文件的大小达到指定大小的时候,就另起一个新文件写入,也就是说实现了日志的分片。最终得到的日志名字是filename,filename.1,filename.2等等。maxBytes指出了单个日志所能达到的最大大小,以字节记,默认值是0,即可以无限增长的单个文件。backupCount指出最多做几个日志文件,默认值是0。需要注意的是,通过这个handler得到的日志文件群,filename是最新的文件,随着filename.1,filename.2……到filename.N为止,依次变老,filename.N才是最早的日志。

  StreamHandler([fileobj])  将日志写入已经打开的类文件对象fileobj,如果不提供参数,那么消息会被写入sys.stderr中。

  TimeRotatingFileHandler(filename[, when [, interval [, backupCount [, encoding [, delay[, utc]]]]]])  和RotatingFileHandler类似,不过控制日志分片的不是文件大小而是时间。when可以指定为'S','M','H','D','W','midnight'等,配合interval指定的一个数字,表名每隔多长时间(多少)(单位)进行文件的分片。对于每一个分日志,其命名格式是filename.%Y-%m-%d_%H_%M_%S。

  下面是一个简单的应用实例:

import os
import logging
from logging.handlers import RotatingFileHandler,HTTPHandler

if __name__ == '__main__':
    LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),'test.log')
    log = logging.getLogger('testlogger1')
    r_handler = RotatingFileHandler(LOG_FILE,maxBytes=1024*1024,backupCount=5)
    http_handler = HTTPHandler('127.0.0.1','http://127.0.0.1:5050',method='GET')
    log.addHandler(r_handler)
    log.addHandler(http_handler)
    log.setLevel(logging.DEBUG)  #setLevel指定了这个logger所记录的最低等级的信息

    log.log(level=logging.INFO,msg='This is a info message')

  这就是一个log一次就既写入文件又发出http请求的logger,log.log这个方法指定了level和msg,是相对低层的方法,当碰到不是默认分级的日志记录可以这么写,对logging自带的那些分级也可直接log.info,log.warn,log.error等等。

  可以看到logger对象有addHandler方法,这个方法关联了一个logger和一个handler。如果不想要某个handler发挥作用了就可以removeHandler来消除关联。

  ●  Formatter类

  上面的例子中,日志文件写入的就是This is a info message。如果需要更加详细的信息,就需要给logger指定一个Formatter类的对象来规定日志格式。Formatter的一般使用就是:

  formatter = Formatter([fmt [, datefmt]])  fmt,datefmt的具体写法在最上面的basicConfig中有提到,就不说了。datefmt修饰的是fmt中日期时间的部分。

  handler.setFormatter(formatter)  将formatter和handler关联起来。注意是关联handler不是logger,因为不同的处理器当然可以有不同样式的格式。切记切记

  除此之外,Formatter还可以帮助logger在记录每条日志时加上动态的上下文。用法是在Formatter的fmt参数中加上相关字段名,然后调用日志记录方法时加上参数extra。比如:

  

formatter = Formatter(fmt="%(asctime)s %(filename)s [line %(lineno)d] %(levelname)s %(message)s || %(num)s ",datefmt = "%Y-%m-%d %H:%M:%S")
handler = FileHandler('test.log')
handler.setFormatter(formatter)
log = getLogger('app')
log.addHandler(handler)
random_info = {
    'num':random.random()    #记得保证key的名字和formatter中的要一样,可以写多个key让解释器自由选取解析  
}

log.critical('Some Message',extra=random_info)

##########
#最后日志中
#2017-08-15 13:33:43 test.py [line 19] INFO Some Solid Message Here || 0.217201896973
##########

  不过如果要这么做,那就要保证每次调用写入日志的方法时都必须带有extra参数,且里面一定要有相关字段存在,否则会报错。

  ●  Filter类

  从广义上来说,上面提到的log.setLevel其实也是筛选器的一种,它筛选掉了所以级别低于指定级别的日志信息。而狭义上的Filter,是一个类,可以用来对日志信息进行筛选。过滤器直接与logger对象相关联,比如log.setFilter(filter)或者log.removeFilter(filter)这样来用。另一方面,可以自定义一个Filter类来充当过滤器。任何一个类,只要继承自logging.Filter类并且实现了filter(self,record)方法就可以作为过滤器使用。其中,record是指一条日志记录对象,这个方法最终一定要返回一个True或者False,其中做的逻辑判断就是自定义过滤的本体。record这个参数具有的属性大致有以下这些:

  record.name 记录器名称    record.levelname 级别    record.levelno 级别编号    record.pathname 完整脚本路径    record.filename 脚本名称    record.module 模块名字

  record.lineno 调用日志发出方法的行数    record.funcName  调用日志发出方法的函数    record.created  发出日志消息的时间    record.thread  线程标识符    record.process  当前进程PID。

  这个filter感觉和beautifulsoup里面定位html元素时用的filterfunc有异曲同工的意思,比如:

class MyFilter(Filter):
    def __init__(self):
        pass
    def filter(self,record):
        if record.module == 'test':
            return False
        else:
            return True

log = getLogger(...)
log.setFilter(MyFilter())

  ●  关于logger分级和日志上报

  之前就说过了,给logger取的名字可以体现为不同logger间的层级关系。而层级关系的用处,在默认情况下就是体现为下层logger发出的日志信息会自动上报到上层logger中。比如parent_log = getLogger('app')而child_log = getLogger('app.child'),这样的话在child_log.log写入的东西都会反馈给parent_log对应的文件中(如果设置的是其他handler那么就是其他日志载体)。

  需要注意的是,在日志上报的机制中,parent_log本身设置的level也好,filter也好都是不起作用的。换句话说,当child_log写出一条日志,这条日志没有被child_log本身的filter和level拒掉的话,那么就一定会反馈到parent_log里面去。这种设定乍一看挺不合理的,但是其反映的是一种分级处理日志信息的一种思想。

  若想要改变下层logger的默认行为,可以设置logger.propagate这个标志False,就不会上报了。

原文地址:https://www.cnblogs.com/franknihao/p/6537281.html