Appium + Python 自动化测试学习之十三:Logging日志模块

日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述(比如:每个事件发生时的数据都是不同的)。开发者还会区分事件的重要性(重要性也被称为 等级 或 严重性)。而我们在做app自动化测试时,遇到异常情况,查看日志也是必不可少的。那我们在什么情况下使用日志呢?


一.何时使用日志

对于简单的日志使用来说日志功能提供了一系列便利的函数。它们是 debug(),info(),warning(),error() 和 critical()。想要决定何时使用日志,请看下表:

你想要执行的任务此任务对应最好工具
对于命令行或程序的应用,结果显示在控制台print()
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查)logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数)
提出一个警告信息基于一个特殊的运行时事件warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警
对一个特殊的运行时事件报告错误引发异常
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理)logging.error(), logging.exception() 或 logging.critical() 分别适用于特定的错误及应用领域

日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递增):

级别何时使用数值
DEBUG细节信息,仅当诊断问题时适用。10
INFO确认程序按预期运行20
WARNING表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行30
ERROR由于严重的问题,程序的某些功能已经不能正常执行40
CRITICAL严重的错误,表明程序已不能继续执行50

默认的级别是WARNING,意味着只会追踪该级别及以上的事件,除非更改日志级别配置。
例子:

import logging
logging.warning('This is waring!')  # 会在控制台输出This is waring!
logging.info('This is info')  # 不会输出任何内容

那么如果想把日志文件记录到指定的文件件呢,该如何配置?

# -*- coding:utf-8 -*-
import logging
# 通过下面的方式进行简单配置输出方式与日志级别
logging.basicConfig(filename='logs.log', level=logging.DEBUG,)
logging.debug('debug message')
logging.info('info message')
logging.warning('warn message')
logging.error('error message')
logging.critical('critical message')

控制台未显示任何信息,而在当前工作目录下生成了logs.log,内容如下:

DEBUG:root:debug message
INFO:root:info message
WARNING:root:warn message
ERROR:root:error message
CRITICAL:root:critical message

二.重要概念

  • Logger 记录器:暴露了应用程序代码能直接使用的接口。
  • Handler 处理器:将(记录器产生的)日志记录发送至合适的目的地。
  • Filter 过滤器:提供了更好的粒度控制,它可以决定输出哪些日志记录。
  • Formatter 格式化器:指明了最终输出中日志记录的布局。
2.1 Logger处理器

Logger类永远不会直接实例化,而是始终通过模块级函数实例化 logging.getLogger(name),getLogger()具有相同名称的多次调用将始终返回对同一Logger对象的引用。 如果没有显式创建,默认创建一个名称为root的logger,并应用默认的日志级别(WARN),默认处理器Handler(StreamHandler,即屏幕打印),和默认格式化器Formatter(日志级别:logger名称:消息)。
创建方式:

#实例化一个logger对象
logger = logging.getLogger(name)

创建Logger实例后,可以使用以下方法进行日志级别设置,增加处理器Handler。

  • logger.setLevel(logging.ERROR) :设置日志级别为ERROR,即只有日志级别大于等于ERROR的日志才会输出
  • logger.addHandler(handler_name) :为Logger实例增加一个处理器
  • logger.removeHandler(handler_name) : 为Logger实例删除一个处理器
主要函数用法
setLevel(level)设置记录器级别,高于level的日志才会被记录。如果Handler设置了更高的日志级别,则以Handler日志级别为准。
getChild(suffix)返回后代记录器,logging.getLogger(‘abc’).getChild(‘def.ghi’)与logging.getLogger(‘abc.def.ghi’)一致。
addFilter(filter)添加过滤器
removeFilter(filter)删除过滤器
addHandler(handler)增加处理器
removeHandler(handler)删除处理器
2.2 Handler 处理器

Handler处理器类型有很多种,比较常用的有四个:StreamHanlder, FileHandler,RotatingFileHandler,TimedRotatingFileHandler

Handler类型主要用途
StreamHandler输出日志到标准输出设备(如屏幕)
FileHandler输出日志到文件
RotatingFileHandler输出到文件,支持文件达到一定大小后创建一个新的日志文件
TimedRotatingFileHandler输出到文件,支持经过一段时间后创建一个新的日志文件

创建StreamHandler之后,可以通过使用以下方法设置日志级别,设置格式化器Formatter,增加或删除过滤器Filter。

  • sh.setLevel(logging.WARN) : 指定日志级别,低于WARN级别的日志将被忽略
  • sh.setFormatter(formatter_name) :设置一个格式化器formatter
  • sh.addFilter(filter_name) : 增加一个过滤器,可以增加多个
  • sh.removeFilter(filter_name) : 删除一个过滤器

StreamHandler——流处理器

创建方式:

#实例化控制台输出对象
sh = logging.StreamHandler()

FileHandler——文件处理器

创建方式:

#实例化文件输出对象
fh = logging.FileHandler(filename, mode='a', encoding='UTF-8', delay=False)

RotatingFileHandler——循环文件处理器
长期运行的程序(如web服务),日志文件会越来越大,大到可能难以读写——循环处理器就是解决这一问题——自动切分日志。
创建方式:

#maxBytes是当前日志最大字节数,backupCount是老日志保留个数
rh = logging.RotatingFileHandler(filename,mode=‘a’,maxBytes=0, backupCount=0, encoding=None, delay=False)
  • 如果maxBytes或者backupCount任意一个等于0,则永不会触发循环,日志无限增长。
  • 如果当前日志达到设定的非零maxBytes,则新建一个日志文件,老的日志文件被重命名为后缀‘.1’,’.2’…等,如果新的日志又达到设定大小,则再新建一个日志,当前所有backup日志的后缀+1,最多保持backupCount个后缀,注意当前日志是没有后缀的。

TimeRotatingFileHandler——时间循环文件处理器

创建方式:

#when和interval共同决定循环时间间隔,when是单位,interval是数量。循环后老日志会自动加上一个后缀,当前日志是没有后缀的。
rh = logging.TimedRotatingFileHandler(filename, when=‘h’, interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
  • 当when设置为W0到W6时,interval失效。backupCount=0则永远不会删除老日志。
  • 系统将老日志自动保存,添加一个以’%Y-%m-%d_%H-%M-%S’格式的拓展名。
  • 如果when设置成了1分钟,但一分钟内程序没有打印日志,则也不会自动循环(这个好理解,不然会造成很多空日志文件)。

2.3 Formatter格式化器

使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。

创建方法:

#fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用'%(message)s'。如果不指明datefmt,将使用ISO8601日期格式。
formatter = logging.Formatter(fmt=None, datefmt=None)

其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,将使用’%(message)s’。如果不指明datefmt,将使用ISO8601日期格式。

日志格式含义
%(levelno)s打印日志级别的数值
%(levelname)s打印日志级别名称
%(pathname)s打印当前执行程序的路径
%(filename)s打印当前执行程序名称
%(funcName)s打印日志的当前函数
%(lineno)d打印日志的当前行号
%(asctime)s打印日志的时间
%(thread)d打印线程id
%(threadName)s打印线程名称
%(process)d打印进程ID
%(message)s打印日志信息
%(module)s当前模块名
%(name)s打印这条消息的Logger名

2.4 Filter 过滤器

Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。Filter基类只允许特定Logger层次以下的事件。例如用‘A.B’初始化的Filter允许Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等记录的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串来初始化,所有的事件都接受。

创建方式:

#包含了name的记录将会被过滤掉,不管是什么日志级别;如name=’’,则不过滤。
filter= logging.Filter(name=’’)

它们之间的关系:Logger可以包含一个或多个Handler和Filter,一个Handler可以新增多个Formatter和Filter,而且日志级别将会继承。


三. Logging 封装

实例:

import logging
import os
PATA=lambda p:os.path.abspath(os.path.join(os.path.dirname(__file__),p))

class Log:
    def __init__(self,name=None):
        self.logger=logging.getLogger(name)#实例化一个logger对象
        self.logformat=logging.Formatter('%(asctime)s - %(filename)s- %(name)s - %(levelname)s - %(message)s')
        self.logger.setLevel(logging.DEBUG)

    def __console(self,level,message):

        if not self.logger.handlers:
            fh=logging.FileHandler(PATA('../logfile/mylog.txt'),'a',encoding='utf-8')#实例化文件输出对象
            fh.setFormatter(self.logformat)#增加输出格式
            fh.setLevel(logging.DEBUG)#设置日志级别
            self.logger.addHandler(fh)  # 增加文件输出对象
            if level.lower()=='info':
                self.logger.info(message)
            elif level.lower()=='debug':
                self.logger.debug(message)
            elif level.lower()=='warning':
                self.logger.warning(message)
            elif level.lower()=='error':
                self.logger.error(message)
            else:
                self.logger.exception(message)

            self.logger.removeHandler(fh)#避免日志输出重复

            fh.close()#关闭打开的日志文件

    def info(self,message):
        self.__console('info',message)
    def debug(self,message):
        self.__console('debug',message)
    def warning(self,message):
        self.__console('warning',message)
    def error(self,message):
        self.__console('error',message)
    def exception(self,message):
        self.__console('exception',message)


if __name__ == '__main__':
    log=Log('test')
    log.info('info测试')
    log.debug('debug')
    log.warning('warning')
    log.error('error')

四.多模块下使用

多次调用 logging.getLogger(‘someLogger’) 时会返回对同一个 logger 对象的引用。 这不仅是在同一个模块中,在其他模块调用也是如此,只要是在同一个 Python 解释器进程中。 是应该引用同一个对象,此外,应用程序也可以在一个模块中定义和配置父 logger,而在单独的模块中创建(但不配置)子 logger,对子 logger 的所有调用都将传给父 logger。 这里是主模块:

import logging
import auxiliary_module
import os

PATA=lambda p:os.path.abspath(os.path.join(os.path.dirname(__file__),p))
#实例化名为“auto_test”的logger对象
logger = logging.getLogger('auto_test')
logger.setLevel(logging.DEBUG)
# 实例化文件输出对象
fh = logging.FileHandler('spam.log')
fh.setLevel(logging.DEBUG)
# 实例化控制台输出对象
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
# 设置输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
#设置文件和控制台输出格式
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 增加文件和控制台输出对象
logger.addHandler(fh)
logger.addHandler(ch)

logger.info('creating an instance of auxiliary_module.Auxiliary')
a = auxiliary_module.Auxiliary()
logger.info('created an instance of auxiliary_module.Auxiliary')
logger.info('calling auxiliary_module.Auxiliary.do_something')
a.do_something()
logger.info('finished auxiliary_module.Auxiliary.do_something')
logger.info('calling auxiliary_module.some_function()')
auxiliary_module.some_function()
logger.info('done with auxiliary_module.some_function()')

这里是辅助模块:

import logging

# create logger
module_logger = logging.getLogger('spam_application.auxiliary')

class Auxiliary:
    def __init__(self):
        self.logger = logging.getLogger('spam_application.auxiliary.Auxiliary')
        self.logger.info('creating an instance of Auxiliary')

    def do_something(self):
        self.logger.info('doing something')
        a = 1 + 1
        self.logger.info('done doing something')

def some_function():
    module_logger.info('received a call to "some_function"')

五.配置文件配置日志

这种配置方式可以将日志配置和代码分离,方便代码的维护和日志管理。

  • 实现logger对应的配置信息必须是 logger_name: name为loggers中key的值
  • level : 日志级别,级别有 DEBUG,INFO,WARNING,ERROR,CRITICAL
  • handlers : 日志处理器,可以有多个 以逗号隔开
  • qualname : logger的名称,通过logging.getLogger(name)获取,这里的name便是qualname,如果获取的logger 名称不存在,则调用默认(root)logger
  • propagate 是否继承父类的配置信息,0:否 1:是

新建配置文件logging.conf,用于存放logging配置文件的信息:

# 配置logger信息。必须包含一个名字叫做root的logger,当使用无参函数logging.getLogger()时,
#默认返回root这个logger,其他自定义logger可以通过 logging.getLogger("fileAndConsole") 方式进行调用
[loggers]
keys=root,file,fileAndConsole

# 定义声明handlers信息。
[handlers]
keys=fileHandler,consoleHandler

# 设置日志格式
[formatters]
keys=simpleFormatter

# 对loggers中声明的logger进行逐个配置,且要一一对应,在所有的logger中,必须制定level和handlers这两个选项,对于非roothandler,
#还需要添加一些额外的option,其中qualname表示它在logger层级中的名字,在应用代码中通过这个名字制定所使用的handler,
#即 logging.getLogger("fileAndConsole"),handlers可以指定多个,中间用逗号隔开,比如handlers=fileHandler,consoleHandler,
#同时制定使用控制台和文件输出日志

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_file]
level=DEBUG
handlers=fileHandler
qualname=file
propagate=1

[logger_fileAndConsole]
level=DEBUG
handlers=fileHandler,consoleHandler
qualname=fileAndConsole
propagate=0

# 在handler中,必须指定class和args这两个option,常用的class包括 StreamHandler(仅将日志输出到控制台)、FileHandler(将日志信息输出保存到文件)、
#RotaRotatingFileHandler(将日志输出保存到文件中,并设置单个日志wenj文件的大小和日志文件个数),
#args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;
#里面指定输出路径,比如输出的文件名称等。level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,
#这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;
#如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;

[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter

[handler_fileHandler]
class=FileHandler
args=('loggers.log', 'a')
level=DEBUG
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(module)s - %(thread)d - %(levelname)s : %(message)s
datefmt=%Y-%m-%d %H:%M:%S

logging日志文件的使用:

import logging
import logging.config

logging.config.fileConfig('logger.conf')
logger=logging.getLogger()

logger.debug('debug message')
logger.info('info message')
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值