zoukankan      html  css  js  c++  java
  • python logging模块

    本章内容


    1. 日志相关概念
    2. logging模块简介
    3. 使用logging提供的模块级别的函数记录日志
    4. logging模块日志流处理流程
    5. 使用logging四大组件记录日志
    6. 配置logging的几种方式
    7. 向日志输出中添加上下文信息
    8. 参考文档

    一、日志相关概念


    日志是一种可以追踪某些软件运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述。此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。

    1.日志的作用

    通过log的分析,可以方便用户了解系统或软件、应用的运行情况;如果你的应用log足够丰富,也可以分析以往用户的操作行为、类型喜好、地域分布或其他更多信息;如果一个应用的log同时也分了多个级别,那么可以很轻易地分析得到该应用的健康状况,及时发现问题并快速定位、解决问题,补救损失。
    简单来讲就是,我们通过记录和分析日志可以了解一个系统或软件程序运行情况是否正常,也可以在应用程序出现故障时快速定位问题。比如,做运维的同学,在接收到报警或各种问题反馈后,进行问题排查时通常都会先去看各种日志,大部分问题都可以在日志中找到答案。再比如,做开发的同学,可以通过IDE控制台上输出的各种日志进行程序调试。对于运维老司机或者有经验的开发人员,可以快速的通过日志定位到问题的根源。可见,日志的重要性不可小觑。日志的作用可以简单总结为以下3点:

    • 程序调试
    • 了解软件程序运行情况,是否正常
    • 软件程序运行故障分析与问题定位

    如果应用的日志信息足够详细和丰富,还可以用来做用户行为分析,如:分析用户的操作行为、类型洗好、地域分布以及其它更多的信息,由此可以实现改进业务、提高商业利益。

    2.日志的等级

    我们先来思考下下面的两个问题:

    • 作为开发人员,在开发一个应用程序时需要什么日志信息?在应用程序正式上线后需要什么日志信息?
    • 作为应用运维人员,在部署开发环境时需要什么日志信息?在部署生产环境时需要什么日志信息?

    在软件开发阶段或部署开发环境时,为了尽可能详细的查看应用程序的运行状态来保证上线后的稳定性,我们可能需要把该应用程序所有的运行日志全部记录下来进行分析,这是非常耗费机器性能的。当应用程序正式发布或在生产环境部署应用程序时,我们通常只需要记录应用程序的异常信息、错误信息等,这样既可以减小服务器的I/O压力,也可以避免我们在排查故障时被淹没在日志的海洋里。那么,怎样才能在不改动应用程序代码的情况下实现在不同的环境记录不同详细程度的日志呢?这就是日志等级的作用了,我们通过配置文件指定我们需要的日志等级就可以了。

    不同的应用程序所定义的日志等级可能会有所差别,分的详细点的会包含以下几个等级:

    • DEBUG
    • INFO
    • NOTICE
    • WARNING
    • ERROR
    • CRITICAL
    • ALERT
    • EMERGENCY

    3.日志字段信息与日志格式

    本节开始问题提到过,一条日志信息对应的是一个事件的发生,而一个事件通常需要包括以下几个内容:

    • 事件发生时间
    • 事件发生位置
    • 事件的严重程度--日志级别
    • 事件内容

    上面这些都是一条日志记录中可能包含的字段信息,当然还可以包括一些其他信息,如进程ID、进程名称、线程ID、线程名称等。日志格式就是用来定义一条日志记录中包含那些字段的,且日志格式通常都是可以自定义的。

    说明:

    输出一条日志时,日志内容和日志级别是需要开发人员明确指定的。对于而其它字段信息,只需要是否显示在日志中就可以了。

    4.日志功能的实现

    几乎所有开发语言都会内置日志相关功能,或者会有比较优秀的第三方库来提供日志操作功能,比如:log4j,log4php等。它们功能强大、使用简单。Python自身也提供了一个用于记录日志的标准库模块--logging。

    二、logging模块简介


    logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等;相比print,具备如下优点:

    1. 可以通过设置不同的日志等级,在release版本中只输出重要信息,而不必显示大量的调试信息;
    2. print将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging则可以由开发者决定将信息输出到什么地方,以及怎么输出;

    logging模块的日志级别

    日志等级(level)描述
    DEBUG 最详细的日志信息,典型应用场景是 问题诊断
    INFO 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作
    WARNING                                             当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的
    ERROR 由于一个更严重的问题导致某些功能不能正常运行时记录的信息
    CRITICAL 当发生严重错误,导致应用程序不能继续运行时记录的信息

    三、logging模块的简单使用


    1、基本使用

    配置logging基本的设置,然后在控制台输出日志

    1 import logging
    2 
    3 
    4 logging.debug('this is debug')
    5 logging.info('this is info')
    6 logging.warning('this is warning')
    7 logging.error('this is error')
    8 logging.critical('this is critical')

    控制台输出结果

    WARNING:root:this is warning
    ERROR:root:this is error
    CRITICAL:root:this is critical

    问:为什么控制台输出结果只有warning、error、critical?

    答:因为logging模块默认的日志级别是warning,只有warning级别及以上才会打印,debug和info级别低于warning,所以不会被打印

    2、设置日志格式和级别,打印在控制台

     1 import logging
     2 # 设置日志格式
     3 log_format = "%(asctime)s - %(levelname)s - %(levelno)s - %(name)s - " 
     4              "%(message)s - %(pathname)s - %(process)s - %(processName)s"
     5 # 设置时间格式
     6 data_format = "%Y-%m-%d %H:%M:%S %p"
     7 # 打印日志级别为debug
     8 logging.basicConfig(format=log_format, level="DEBUG", datefmt=data_format)
     9 
    10 logging.debug('this is debug')
    11 logging.info('this is info')
    12 logging.warning('this is warning')
    13 logging.error('this is error')
    14 logging.critical('this is critical')

    控制台输出结果

    2019-01-03 09:59:01 AM - DEBUG - 10 - root - this is debug - D:/python_file/py_study/日志/logger_1.py - 5820 - MainProcess
    2019-01-03 09:59:01 AM - INFO - 20 - root - this is info - D:/python_file/py_study/日志/logger_1.py - 5820 - MainProcess
    2019-01-03 09:59:01 AM - WARNING - 30 - root - this is warning - D:/python_file/py_study/日志/logger_1.py - 5820 - MainProcess
    2019-01-03 09:59:01 AM - ERROR - 40 - root - this is error - D:/python_file/py_study/日志/logger_1.py - 5820 - MainProcess
    2019-01-03 09:59:01 AM - CRITICAL - 50 - root - this is critical - D:/python_file/py_study/日志/logger_1.py - 5820 - MainProcess

    3、logging.basicConfig()函数说明

    该函数可接收的关键字参数如下:

    参数名称描述
    filename 指定日志输出目标文件的文件名,指定该设置项后日志信心就不会被输出到控制台了
    filemode      指定日志文件的打开模式,默认为'a'。需要注意的是,该选项要在filename指定时才有效
    format        指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。logging模块定义的格式字段下面会列出。
    datefmt       指定日期/时间格式。需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效
    level 指定日志器的日志级别
    stream 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream。需要说明的是,stream和filename不能同时提供,否则会引发 ValueError异常
    style Python 3.2中新添加的配置项。指定format格式字符串的风格,可取值为'%'、'{'和'$',默认为'%'
    handlers Python 3.3中新添加的配置项。该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象,这些handler将会被添加到root logger。需要说明的是:filename、stream和handlers这三个配置项只能有一个存在,不能同时出现2个或3个,否则会引发ValueError异常。

    4、 logging模块定义的格式字符串字段

    我们来列举一下logging模块中定义好的可以用于format格式字符串中字段有哪些:

    字段/属性名称使用格式描述
    asctime %(asctime)s 日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
    created %(created)f 日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值
    relativeCreated %(relativeCreated)d 日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的)
    msecs %(msecs)d 日志事件发生事件的毫秒部分
    levelname %(levelname)s 该日志记录的文字形式的日志级别('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
    levelno %(levelno)s 该日志记录的数字形式的日志级别(10, 20, 30, 40, 50)
    name %(name)s 所使用的日志器名称,默认是'root',因为默认使用的是 rootLogger
    message %(message)s 日志记录的文本内容,通过 msg % args计算得到的
    pathname %(pathname)s 调用日志记录函数的源码文件的全路径
    filename %(filename)s pathname的文件名部分,包含文件后缀
    module %(module)s filename的名称部分,不包含后缀
    lineno %(lineno)d 调用日志记录函数的源代码所在的行号
    funcName %(funcName)s 调用日志记录函数的函数名
    process %(process)d 进程ID
    processName %(processName)s 进程名称,Python 3.1新增
    thread %(thread)d 线程ID
    threadName %(thread)s 线程名称

    5、 其他说明

    几个要说明的内容:
    • logging.basicConfig()函数是一个一次性的简单配置工具使,也就是说只有在第一次调用该函数时会起作用,后续再次调用该函数时完全不会产生任何操作的,多次调用的设置并不是累加操作。
    • 日志器(Logger)是有层级关系的,上面调用的logging模块级别的函数所使用的日志器是RootLogger类的实例,其名称为'root',它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。
    • 如果要记录的日志中包含变量数据,可使用一个格式字符串作为这个事件的描述消息(logging.debug、logging.info等函数的第一个参数),然后将变量数据作为第二个参数*args的值进行传递,如:logging.warning('%s is %d years old.', 'Tom', 10),输出内容为WARNING:root:Tom is 10 years old.
    • logging.debug(), logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数。它们支持3个关键字参数: exc_info, stack_info, extra,下面对这几个关键字参数作个说明。
    关于exc_info, stack_info, extra关键词参数的说明:
    • exc_info: 其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。
    • stack_info: 其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。
    • extra: 这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。

    四、logging模块日志流处理流程


    在介绍logging模块的高级用法之前,很有必要对logging模块所包含的重要组件以及其工作流程做个全面、简要的介绍,直接从网上摘抄一段。

    1、 logging日志模块四大组件

    在介绍logging模块的日志流处理流程之前,我们先来介绍下logging模块的四大组件:

    组件名称对应类名功能描述
    日志器 Logger 提供了应用程序可一直使用的接口
    处理器 Handler 将logger创建的日志记录发送到合适的目的输出
    过滤器 Filter 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录
    格式器 Formatter 决定日志记录的最终输出格式

    logging模块就是通过这些组件来完成日志处理的,上面所使用的logging模块级别的函数也是通过这些组件对应的类来实现的。

    这些组件之间的关系描述:
    • 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
    • 不同的处理器(handler)可以将日志输出到不同的位置;
    • 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
    • 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
    • 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。

    简单点说就是:日志器(logger)是入口,真正干活儿的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。

    2. logging日志模块相关类及其常用方法介绍

    下面介绍下与logging四大组件相关的类:Logger, Handler, Filter, Formatter。

    Logger类

    Logger对象有3个任务要做:

    • 1)向应用程序代码暴露几个方法,使应用程序可以在运行时记录日志消息;
    • 2)基于日志严重等级(默认的过滤设施)或filter对象来决定要对哪些日志进行后续处理;
    • 3)将日志消息传送给所有感兴趣的日志handlers。

    Logger对象最常用的方法分为两类:配置方法 和 消息发送方法

    最常用的配置方法如下:

    方法描述
    Logger.setLevel() 设置日志器将会处理的日志消息的最低严重级别
    Logger.addHandler() 和 Logger.removeHandler() 为该logger对象添加 和 移除一个handler对象
    Logger.addFilter() 和 Logger.removeFilter() 为该logger对象添加 和 移除一个filter对象

    关于Logger.setLevel()方法的说明:

    内建等级中,级别最低的是DEBUG,级别最高的是CRITICAL。例如setLevel(logging.INFO),此时函数参数为INFO,那么该logger将只会处理INFO、WARNING、ERROR和CRITICAL级别的日志,而DEBUG级别的消息将会被忽略/丢弃。

    logger对象配置完成后,可以使用下面的方法来创建日志记录:

    方法描述
    Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() 创建一个与它们的方法名对应等级的日志记录
    Logger.exception() 创建一个类似于Logger.error()的日志消息
    Logger.log() 需要获取一个明确的日志level参数来创建一个日志记录

    说明:

    • Logger.exception()与Logger.error()的区别在于:Logger.exception()将会输出堆栈追踪信息,另外通常只是在一个exception handler中调用该方法。
    • Logger.log()与Logger.debug()、Logger.info()等方法相比,虽然需要多传一个level参数,显得不是那么方便,但是当需要记录自定义level的日志时还是需要该方法来完成。

    那么,怎样得到一个Logger对象呢?一种方式是通过Logger类的实例化方法创建一个Logger类的实例,但是我们通常都是用第二种方式--logging.getLogger()方法。

    logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为'root'。若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。

    关于logger的层级结构与有效等级的说明:

    • logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是'.'前面的logger的children,例如,有一个名称为 foo 的logger,其它名称分别为 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的后代。
    • logger有一个"有效等级(effective level)"的概念。如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。需要说明的是,root logger总是会有一个明确的level设置(默认为 WARNING)。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。
    • child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
    Handler类

    Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加0个或者更多个handler对象。比如,一个应用程序可能想要实现以下几个日志需求:

    • 1)把所有日志都发送到一个日志文件中;
    • 2)把所有严重级别大于等于error的日志发送到stdout(标准输出);
    • 3)把所有严重级别为critical的日志发送到一个email邮件地址。
      这种场景就需要3个不同的handlers,每个handler复杂发送一个特定严重级别的日志到一个特定的位置。

    一个handler中只有非常少数的方法是需要应用开发人员去关心的。对于使用内建handler对象的应用开发人员来说,似乎唯一相关的handler方法就是下面这几个配置方法:

    方法描述
    Handler.setLevel() 设置handler将会处理的日志消息的最低严重级别
    Handler.setFormatter() 为handler设置一个格式器对象
    Handler.addFilter() 和 Handler.removeFilter() 为handler添加 和 删除一个过滤器对象

    需要说明的是,应用程序代码不应该直接实例化和使用Handler实例。因为Handler是一个基类,它只定义了素有handlers都应该有的接口,同时提供了一些子类可以直接使用或覆盖的默认行为。下面是一些常用的Handler:

    Handler描述
    logging.StreamHandler 将日志消息发送到输出到Stream,如std.out, std.err或任何file-like对象。
    logging.FileHandler 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长
    logging.handlers.RotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按大小切割
    logging.hanlders.TimedRotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按时间切割
    logging.handlers.HTTPHandler 将日志消息以GET或POST的方式发送给一个HTTP服务器
    logging.handlers.SMTPHandler 将日志消息发送给一个指定的email地址
    logging.NullHandler 该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免'No handlers could be found for logger XXX'信息的出现。
    Formater类

    Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个Formatter的子类来完成。

    Formatter类的构造方法定义如下:

    logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

    可见,该构造方法接收3个可选参数:

    • fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
    • datefmt:指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
    • style:Python 3.2新增的参数,可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
    Filter类

    Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。该类定义如下:

    class logging.Filter(name='')
        filter(record)

    比如,一个filter实例化时传递的name参数值为'A.B',那么该filter实例将只允许名称为类似如下规则的loggers产生的日志记录通过过滤:'A.B','A.B,C','A.B.C.D','A.B.D',而名称为'A.BB', 'B.A.B'的loggers产生的日志则会被过滤掉。如果name的值为空字符串,则允许所有的日志事件通过过滤。

    filter方法用于具体控制传递的record记录是否能通过过滤,如果该方法返回值为0表示不能通过过滤,返回值为非0表示可以通过过滤。

    说明:

    • 如果有需要,也可以在filter(record)方法内部改变该record,比如添加、删除或修改一些属性。
    • 我们还可以通过filter做一些统计工作,比如可以计算下被一个特殊的logger或handler所处理的record数量等。
     1 import logging
     2 from logging import handlers
     3 # 设置日志格式
     4 log_format = '%(asctime)s - %(levelname)s - %(pathname)s[line:%(lineno)d] - %(message)s'
     5 # 设置时间格式
     6 data_format = "%Y-%m-%d %H:%M:%S %p"
     7 # 声明一个logger,设置日志等级为debug
     8 logger = logging.getLogger('logger_1.log')
     9 logger.setLevel(logging.DEBUG)
    10 # 生成一个输出到屏幕的句柄,日志等级为INFO
    11 sh = logging.StreamHandler()
    12 # sh.setFormatter(logging.Formatter(log_format))  # 设置输出到屏幕的格式
    13 sh.setLevel(logging.INFO)
    14 # 生成一个输出到文件的句柄,日志等级为ERROR
    15 th = handlers.TimedRotatingFileHandler(filename='mylog.log', when='D', backupCount=2, encoding='utf-8')
    16 th.setFormatter(logging.Formatter(log_format))  # 设置输出到文件的格式
    17 th.setLevel(logging.ERROR)
    18 # 将sh和th句柄添加到logger
    19 logger.addHandler(sh)
    20 logger.addHandler(th)
    21 
    22 logger.error('error message')

    控制台输出结果

    2019-01-03 10:22:08,715 - ERROR - D:/python_file/py_study/日志/logger_1.py[line:23] - info message

    同时会生成一个mylog.log的文件,内容与控制台输出一致

    再贴一段代码

     1 import logging
     2 from logging import handlers
     3 
     4 
     5 class Logger():
     6     level_relations = {'debug':logging.DEBUG,
     7                        'info':logging.INFO,
     8                        'warning':logging.WARNING,
     9                        'error':logging.ERROR,
    10                        'critical':logging.CRITICAL}
    11 
    12     def __init__(self, filename, level='info', when='D', backCount=2, fmt='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s:%(message)s'):
    13         self.logger = logging.getLogger(filename)
    14         format_str = logging.Formatter(fmt)  # 设置日志格式
    15         self.logger.setLevel(self.level_relations.get(level))  # 设置日志级别
    16         sh = logging.StreamHandler()  # 往屏幕上输出
    17         th = handlers.TimedRotatingFileHandler(filename,when=when,backupCount=backCount,encoding='utf-8')  # 往文件里写入,指定间隔时间自动生成文件的处理器
    18         # 实例化TimedRotatingFileHandler
    19         th.setFormatter(format_str)  # 设置文件里写入的格式
    20         self.logger.addHandler(sh)  # 把对象加到logger里
    21         self.logger.addHandler(th)
    22 
    23 
    24 if __name__ == '__main__':
    25     log = Logger('logger123.log', level='debug')
    26     log.logger.debug('this is debug')
    27     log.logger.info('this is info')
    28     log.logger.warning('this is warning')
    29     log.logger.error('this is error')
    30     log.logger.critical("this is critical")

    控制台输出结果

    this is debug
    this is info
    this is warning
    this is error
    this is critical

    文件输出结果

    2019-01-03 10:38:29,281 - D:/python_file/py_study/日志/logger_1.py[line:26] - DEBUG:this is debug
    2019-01-03 10:38:29,281 - D:/python_file/py_study/日志/logger_1.py[line:27] - INFO:this is info
    2019-01-03 10:38:29,281 - D:/python_file/py_study/日志/logger_1.py[line:28] - WARNING:this is warning
    2019-01-03 10:38:29,281 - D:/python_file/py_study/日志/logger_1.py[line:29] - ERROR:this is error
    2019-01-03 10:38:29,281 - D:/python_file/py_study/日志/logger_1.py[line:30] - CRITICAL:this is critical

    五、参考文档


  • 相关阅读:
    3.3测试复盘
    计算机网络-运输层面试题整理
    3.2专项测试复盘
    2.27专项测试复盘
    2.26排序专项测试复盘
    2.25专项测试复盘
    前端基础笔记1
    2.24专项测试复盘
    2.23专项测试复盘
    更新pip报错AttributeError: 'NoneType' object has no attribute 'bytes'
  • 原文地址:https://www.cnblogs.com/gxfaxe/p/10212604.html
Copyright © 2011-2022 走看看