目录结构
一、日志概述
1. 日志的作用
2. 日志的级别
3. 日志格式
4. 日志采集位置
二、Logging模块的构成
1. Logger 记录器
2. Handler 处理器
3. Filter 过滤器
4. Formatter 格式化器
三、Logging模块综合实践
四、Logging模块配置文件管理
一、日志概述
1. 日志的作用
在项目开发、测试过程中,项目运行出现异常时,查看日志信息非常重要。日志是定位问题的重要手段,类似侦探人员要根据现场留下的线索来推断案情
2. 日志的级别
脚本运行可能出现的异常情况,如调试信息、报错异常。根据不同的情况来对日志分级管理,可较大程度减少对排查问题筛选所产生的干扰。日志主要级别有:DEBUG、INFO、WARNING、ERROR、CRITICAL
日志定位的级别:
级别 | 说明 |
---|---|
DEBUG | 调试信息。最详细的日志信息 |
INFO | 事情按预期工作 |
WARNING | 发生了一些异常,or预示可能发生的问题,软件还在正常运行(如:磁盘已满) |
ERROR | 出现更严重的问题,软件已不能执行部分功能 |
CRITICAL | 严重错误,表明软件已不能继续运行 |
PS:一般而言,输出信息可直接用 INFO 类型,调试时可使用 DEBUG 类型,若预计有错误则需要用 ERROR 类型
3. 日志格式
作用:提高日志的可阅读性,有利于快速定位问题
格式如:时间+模块+行数+日志具体信息
2018-01-10 18:02:35,633 backup.py[line:18] INFO ============test backup================
2018-01-10 18:02:39,253 backup.py[line:20] INFO click backup button
2018-01-10 18:02:54,025 backup.py[line:23] INFO click next button
2018-01-10 18:03:09,280 common_fun.py[line:83] INFO Start send Email..
2018-01-10 18:03:11,840 common_fun.py[line:91] INFO Send Email finish!
2018-01-10 18:03:13,305 common_fun.py[line:168] INFO get backup screenshot
2018-01-10 19:36:00,238 backup.py[line:17] INFO ============test backup================
4. 日志采集位置
项目中会有很多日志采集点,而日志采集点需要结合业务属性来设置,如在登录代码执行前可以插入"准备登录"日志信息。
若登录完成之后再设置登录的提示日志,则会给人造成误解,无法判断是登录之还是登录之后的问题,因此日志采集点的位置很重要
二、Logging模块的构成
Python的logging模块提供了通用的日志系统,有不同的日志级别,并可采用不同的方式记录日志,如:文件、HTTP GET/POST、SMTP、Socket等,甚至可实现自定义方式来记录日志
#导入logging模块
import logging
Logging模块官方文档:https://docs.python.org/3.7/library/logging.html
logging模块构成:
logging模块包括:logger、Handler、Filter、Formatter四个部分
① Logger:记录器。用于设置日志采集
② Handler:处理器。将日志记录发送至合适的路径
③ Filter:过滤器。提供了更好的粒度控制,可决定输出哪些日志记录
④ Formatter:格式化器。指明了最终输出中日志的格式
1. Logger 记录器
Logger是一个树形层级结构,在使用接口debug、info、warn、error、critical之前必须创建Logger实例(即创建一个记录器)。如果没有显式的进行创建,则默认创建一个root logger,并应用默认的日志级别(WARN)、Handler、Formatter
方法:
basicConfig(**kwargs)
为日志记录系统作基本配置
部分参数
filename:指定日志文件名称
filemode:指定打开文件的模式(未指定则默认为 'a')
format:为处理程序使用指定的格式字符串
datefmt:使用指定的日期/时间格式
level:将根记录器(root logger)级别设置为指定级别
文件读写模式:
r :以只读模式打开(文件必须存在,否则报错)
w :以写方式打开(会覆盖原有文件内容)
a :以追加模式打开(在原有内容之后增加新内容)
r+:以读写模式打开(文件必须存在,否则报错)
w+:以读写模式打开(文件不存在,则创建)
a+:以读写模式打开(文件不存在,则创建)
logging_test.py
import logging
# 创建logger记录器实例
logging.basicConfig(level=logging.INFO)
# logging.basicConfig(level=logging.DEBUG)
# 调用接口
logging.debug('debug info')
logging.info('run normally')
logging.warning('warning info')
logging.error('error info')
logging.critical('critical info')
2. Handler 处理器
Handler处理器,将日志记录发送至合适的路径。Handler处理器常用类型:
① StreamHandler
将日志记录输出发送到诸如sys.stdout、sys.stderr或任何类似文件流的对象。以上loggging_test.py就是输出到控制台
② FileHandler
将日志记录输出发送到磁盘文件,它继承了StreamHandler的输出功能
logging.basicConfig(level=logging.DEBUG,filename='RunLog.log')
执行结果:
③ NullHandler
不做任何格式化或输出,本质上是一个"无操作"处理程序
3. Filter 过滤器
Handlers和Loggers可以使用Filters来完成级别更复杂or深入的过滤
4. Formatter 格式化器
使用Formatter对象设置日志信息的规则、结构和内容,默认的时间格式为"%Y-%m-%d %H:%M:%S"
格式 | 描述 |
---|---|
%(levelno)s | 打印日志级别的数值 |
%(levelname)s | 打印日志级别名称 |
%(pathname)s | 打印当前执行程序的路径 |
%(filename)s | 打印当前执行程序名称 |
%(funcName)s | 打印日志的当前函数 |
%(lineno)d | 打印日志的当前行号 |
%(asctime)s | 打印日志的时间 |
%(thread)d | 打印线程ID |
%(threadName)s | 打印线程名称 |
%(process)d | 打印进程ID |
%(message)s | 打印日志信息 |
logging_format.py
logging.basicConfig(level=logging.DEBUG,filename='RunLog.log',format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s')
执行结果:
2019-07-06 18:34:21,172 logging_format.py[line:7] DEBUG: debug info
2019-07-06 18:34:21,172 logging_format.py[line:8] INFO: run normally
2019-07-06 18:34:21,173 logging_format.py[line:9] WARNING: warning info
2019-07-06 18:34:21,173 logging_format.py[line:10] ERROR: error info
2019-07-06 18:34:21,173 logging_format.py[line:11] CRITICAL: critical info
三、Logging模块综合实践
测试场景
将其前启动考研帮App的脚本,增加log采集功能,设置指定的日志格式输出,并将日志保存到指定文件
代码实现
kaoyan_logger.py
from appium import webdriver
from selenium.common.exceptions import NoSuchElementException
import yaml
import logging
# 解析yaml文档为Python对象
file=open(r'D:\CI_Env\Appium\Appium_Basic\yaml\desired_caps.yaml','r',encoding='UTF-8')
data=yaml.load(file,Loader=yaml.FullLoader)
# 创建Logger记录器实例(配置日志管理)
logging.basicConfig(level=logging.DEBUG,filename='kaoyanRunLog.log',format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s')
# 定义字典,存储capabilities配置信息
desired_caps={}
# 移动设备信息
desired_caps['platformName']=data['platformName']
desired_caps['deviceName']=data['deviceName']
desired_caps['platformVersion']=data['platformVersion']
# 基于uiautomator2识别Toast
desired_caps['automationName']=data['automationName']
# 设置键盘输入法Unicode格式
desired_caps['unicodeKeyboard']=data['unicodeKeyboard']
desired_caps['resetKeyboard']=data['resetKeyboard']
# 重置登录状态(每次都需要输入[用户名+密码]登录)
desired_caps['noReset']=data['noReset']
# 移动APP应用信息
desired_caps['app']=data['app']
desired_caps['appPackage']=data['appPackage']
desired_caps['appActivity']=data['appActivity']
logging.info('---start APP---') # 日志采集点01
# 连接远程服务器,根据配置开启会话
driver=webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub',desired_caps)
driver.implicitly_wait(10) # 等待时间(隐式)
# 定义点击取消升级按钮的方法
def check_cancelBtn():
logging.info('---check cancel Button---') # 日志采集点02
try:
cancelBtn=driver.find_element_by_id('android:id/button2')
except NoSuchElementException:
logging.info('---no cancel button---') # 日志采集点03
else:
cancelBtn.click()
# 定义点击跳过引导页的方法
def check_skipBtn():
logging.info('---check skip Button---') # 日志采集点04
try:
skipBtn=driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')
except NoSuchElementException:
logging.info('---no skip button---') # 日志采集点05
else:
skipBtn.click()
# 调用函数
check_cancelBtn()
check_skipBtn()
执行结果:
正常启动并进入到指定APP的登录界面,且生成指定格式的日志文件 kaoyanRunLog.log,内容如下:
2019-07-06 19:59:48,256 kaoyan_logger.py[line:31] INFO: ---start APP---
2019-07-06 19:59:48,389 remote_connection.py[line:388] DEBUG: POST http://127.0.0.1:4723/wd/hub/session {"capabilities": {"firstMatch": [{"platformName": "Android", "appium:deviceName": "127.0.0.1:62001", "appium:platformVersion": "5.1.1", "appium:automationName": "uiautomator2", "appium:unicodeKeyboard": true, "appium:resetKeyboard": true, "appium:noReset": false, "appium:app": "C:\\Users\\Administrator\\Desktop\\kaoyanbang_3.3.8beta.244.apk", "appium:appPackage": "com.tal.kaoyan", "appium:appActivity": "com.tal.kaoyan.ui.activity.SplashActivity"}]}, "desiredCapabilities": {"platformName": "Android", "deviceName": "127.0.0.1:62001", "platformVersion": "5.1.1", "automationName": "uiautomator2", "unicodeKeyboard": true, "resetKeyboard": true, "noReset": false, "app": "C:\\Users\\Administrator\\Desktop\\kaoyanbang_3.3.8beta.244.apk", "appPackage": "com.tal.kaoyan", "appActivity": "com.tal.kaoyan.ui.activity.SplashActivity"}}
2019-07-06 19:59:48,392 connectionpool.py[line:205] DEBUG: Starting new HTTP connection (1): 127.0.0.1:4723
2019-07-06 20:01:26,136 connectionpool.py[line:393] DEBUG: http://127.0.0.1:4723 "POST /wd/hub/session HTTP/1.1" 200 1210
2019-07-06 20:01:26,137 remote_connection.py[line:440] DEBUG: Finished Request
2019-07-06 20:01:26,138 remote_connection.py[line:388] DEBUG: POST http://127.0.0.1:4723/wd/hub/session/74779968-98df-4954-b49f-740b6b469570/timeouts {"implicit": 10000, "sessionId": "74779968-98df-4954-b49f-740b6b469570"}
2019-07-06 20:01:26,287 connectionpool.py[line:393] DEBUG: http://127.0.0.1:4723 "POST /wd/hub/session/74779968-98df-4954-b49f-740b6b469570/timeouts HTTP/1.1" 200 14
2019-07-06 20:01:26,288 remote_connection.py[line:440] DEBUG: Finished Request
2019-07-06 20:01:26,288 kaoyan_logger.py[line:39] INFO: ---check cancel Button---
2019-07-06 20:01:26,289 remote_connection.py[line:388] DEBUG: POST http://127.0.0.1:4723/wd/hub/session/74779968-98df-4954-b49f-740b6b469570/element {"using": "id", "value": "android:id/button2", "sessionId": "74779968-98df-4954-b49f-740b6b469570"}
2019-07-06 20:01:36,761 connectionpool.py[line:393] DEBUG: http://127.0.0.1:4723 "POST /wd/hub/session/74779968-98df-4954-b49f-740b6b469570/element HTTP/1.1" 404 427
2019-07-06 20:01:36,763 remote_connection.py[line:440] DEBUG: Finished Request
2019-07-06 20:01:36,764 kaoyan_logger.py[line:43] INFO: ---no cancel button---
2019-07-06 20:01:36,764 kaoyan_logger.py[line:49] INFO: ---check skip Button---
2019-07-06 20:01:36,765 remote_connection.py[line:388] DEBUG: POST http://127.0.0.1:4723/wd/hub/session/74779968-98df-4954-b49f-740b6b469570/element {"using": "id", "value": "com.tal.kaoyan:id/tv_skip", "sessionId": "74779968-98df-4954-b49f-740b6b469570"}
2019-07-06 20:01:36,859 connectionpool.py[line:393] DEBUG: http://127.0.0.1:4723 "POST /wd/hub/session/74779968-98df-4954-b49f-740b6b469570/element HTTP/1.1" 200 137
2019-07-06 20:01:36,859 remote_connection.py[line:440] DEBUG: Finished Request
2019-07-06 20:01:36,860 remote_connection.py[line:388] DEBUG: POST http://127.0.0.1:4723/wd/hub/session/74779968-98df-4954-b49f-740b6b469570/element/11e98a55-8e8f-46f8-96a1-12793ba87895/click {"id": "11e98a55-8e8f-46f8-96a1-12793ba87895", "sessionId": "74779968-98df-4954-b49f-740b6b469570"}
2019-07-06 20:01:37,519 connectionpool.py[line:393] DEBUG: http://127.0.0.1:4723 "POST /wd/hub/session/74779968-98df-4954-b49f-740b6b469570/element/11e98a55-8e8f-46f8-96a1-12793ba87895/click HTTP/1.1" 200 14
2019-07-06 20:01:37,520 remote_connection.py[line:440] DEBUG: Finished Request
四、Logging模块配置文件管理
问题描述:以上log配置的作用域只是控制当前具体某个脚本,而自动化项目中通常有很多模块脚本,若每一个脚本都单独配置则耗费较多精力且不利于维护
解决思路:将日志配置的参数抽离出来制作一个配置文件,各个模块需要使用时则直接引用即可
日志格式配置
将log输出格式、输出路径等参数抽离出来作为一个配置表,具体如下:
log.conf
[loggers]
keys=root,infoLogger
[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler
[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0
[handlers]
keys=consoleHandler,fileHandler
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form01
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form02
args=('kaoyanRunLog_02.log', 'a')
[formatters]
keys=form01,form02
[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s
[formatter_form02]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s: %(message)s
备注:
[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0 # 取值=0,输出日志但不传递消息;取值=1,输出日志同时传递消息到更高级别
[handlers]
keys=consoleHandler,fileHandler # 输出流向:控制台、文件
kaoyan_logger_02.py
from appium import webdriver
from selenium.common.exceptions import NoSuchElementException
import yaml
import logging
import logging.config
# 解析yaml文档为Python对象
file=open(r'D:\CI_Env\Appium\Appium_Basic\yaml\desired_caps.yaml','r',encoding='UTF-8')
data=yaml.load(file,Loader=yaml.FullLoader)
# 创建Logger记录器实例(配置日志管理)
CON_LOG='log.conf'
logging.config.fileConfig(CON_LOG) # 读取配置文件
logging=logging.getLogger()
# 定义字典,存储capabilities配置信息
desired_caps={}
# 移动设备信息
desired_caps['platformName']=data['platformName']
desired_caps['deviceName']=data['deviceName']
desired_caps['platformVersion']=data['platformVersion']
# 基于uiautomator2识别Toast
desired_caps['automationName']=data['automationName']
# 设置键盘输入法Unicode格式
desired_caps['unicodeKeyboard']=data['unicodeKeyboard']
desired_caps['resetKeyboard']=data['resetKeyboard']
# 重置登录状态(每次都需要输入[用户名+密码]登录)
desired_caps['noReset']=data['noReset']
# 移动APP应用信息
desired_caps['app']=data['app']
desired_caps['appPackage']=data['appPackage']
desired_caps['appActivity']=data['appActivity']
logging.info('---start APP---') # 日志采集点01
# 连接远程服务器,根据配置开启会话
driver=webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub',desired_caps)
driver.implicitly_wait(10) # 等待时间(隐式)
# 定义点击取消升级按钮的方法
def check_cancelBtn():
logging.info('---check cancel Button---') # 日志采集点02
try:
cancelBtn=driver.find_element_by_id('android:id/button2')
except NoSuchElementException:
logging.info('---no cancel button---') # 日志采集点03
else:
cancelBtn.click()
# 定义点击跳过引导页的方法
def check_skipBtn():
logging.info('---check skip Button---') # 日志采集点04
try:
skipBtn=driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')
except NoSuchElementException:
logging.info('---no skip button---') # 日志采集点05
else:
skipBtn.click()
# 调用函数
check_cancelBtn()
check_skipBtn()
fileConfig(fname, defaults=None, disable_existing_loggers=True)
方法:作用是从ConfigParser格式的文件中读取日志配置,同时覆盖当前脚本中的log配置(若当前脚本有配置log参数),以日志配置文件为准
执行结果:
正常启动并进入到指定APP的登录界面,且生成指定格式的日志文件 kaoyanRunLog_02.log,内容如下:
2019-07-06 20:20:35,168 kaoyan_logger_02.py[line:34] INFO: ---start APP---
2019-07-06 20:21:34,742 kaoyan_logger_02.py[line:42] INFO: ---check cancel Button---
2019-07-06 20:21:45,242 kaoyan_logger_02.py[line:46] INFO: ---no cancel button---
2019-07-06 20:21:45,244 kaoyan_logger_02.py[line:52] INFO: ---check skip Button---