我的收藏

[AP_13] Logging日志模块-配置管理&案例实

2019-07-06  本文已影响10人  Fighting_001

目录结构

一、日志概述
    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')
INFO级别 DEBUG级别

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---
上一篇下一篇

猜你喜欢

热点阅读