简介
生活中的日志是记录你生活的点点滴滴,让它把你内心的世界表露出来,更好的诠释自己的内心世界,而电脑里的日志是有价值的信息宝库。
日志文件是专门用于记录系统操作事件的记录文件或文件集合,操作系统有操作系统日志文件,数据库系统有数据库系统日志文件,等等。
系统日志文件是包含关于系统消息的文件,包括内核、服务、在系统上运行的应用程序等。不同的日志文件记载不同的信息。例如,有的是默认的系统日志文件,有的记载特定任务。
在数据库中用事务日志文件记录数据的修改操作,其中的每条日志记录或者记录所执行的逻辑操作,或者记录已修改数据的前像和后像。前像是操作执行前的数据复本;后像是操作执行后的数据复本。
问题思考
在自动化脚本运行过程中, IDE控制台一般都会输出运行日志。但是如果测试项目是在liunx服务器上面运行,没有IDE控制台输出log,那么我们该如何采集日志? 元芳,你怎么看?
日志概述
日志作用
不管是在项目开发还是测试过程中,项目运行一旦出现问题日志信息就非常重要了。日志是定位问题的重要手段,就像侦探人员要根据现场留下的线索来推断案情。
日志级别
脚本运行会有很多的情况,比如调试信息、报错异常信息等。日志要根据这些不同的情况来继续分级管理,不然对于排查问题的筛选会有比较大的干扰。 。日志一般定位的级别如下:
|
级别 |
何时使用 |
|
DEBUG |
调试信息,也是最详细的日志信息。 |
|
INFO |
证明事情按预期工作。 |
|
WARNING |
表明发生了一些意外,或者不久的将来会发生问题(如‘磁盘满了’)。软件还是在正常工作。 |
|
ERROR |
由于更严重的问题,软件已不能执行一些功能了。 |
|
CRITICAL |
严重错误,表明软件已不能继续运行了。 |
首先我们日志需要按照 info、debug、error等级别来进行区分的。当然这个级别可以自己去设置。在一般的情况下我们普通的输出我们直接用info类型,调试的时候用debug类型,如果预计有错误时那么我们就需要用error类型的日志,一般情况去info级别最为合适。
日志格式
日志格式化是为了提高日志的可阅读性,比如: 时间 +模块+行数+日志具体信息 的内容格式。如果日志信息杂乱无章的全部输出来,这样也不利于定位问题。如下所示就是日志格式化输出,非常便于阅读查看。
2019
-
08
-
14
22
:
02
:
35
,
633
backup.py[line:
18
] INFO ============test backup================
2019
-
08
-
14
22
:
02
:
39
,
253
backup.py[line:
20
] INFO click backup button
2019
-
08
-
14
22
:
02
:
54
,
025
backup.py[line:
23
] INFO click next button
2019
-
08
-
14
22
:
03
:
09
,
280
common_fun.py[line:
83
] INFO Start send Email..
2019
-
08
-
14
22
:
03
:
11
,
840
common_fun.py[line:
91
] INFO Send Email finish!
2019
-
08
-
14
22
:
03
:
13
,
305
common_fun.py[line:
168
] INFO
get
backup screenshot
2019
-
08
-
14
23
:
36
:
00
,
238
backup.py[line:
17
] INFO ============test backup================
2019
-
08
-
14
23
:
36
:
04
,
530
backup.py[line:
19
] INFO click backup button
2019
-
08
-
14
23
:
37
:
20
,
107
backup.py[line:
17
] INFO ============test
日志位置
一个项目中会有很多的日志采集点,而日志采集点必须结合业务属性来设置。比如在登录代码执行前可以插入 “准备登录..”日志信息,如果登录完成之后,再设置登录的提示日志就会给人造成误解,无法判断到底是登录之前的问题还是登录之后的问题,因此日志采集点的位置很重要。
logging模块
简介
Python的logging模块提供了通用的日志系统,这个模块提供不同的日志级别,并可以采用不同的方式记录日志,比如文件,HTTP GET/POST,SMTP,Socket等,甚至可以自己实现方式记录日志。
# 导入 logging 模块
import logging
logging模块官方文档
logging构成
logging模块包括 logger,Handler,Filter,Formatter 四个部分。
- Logger 记录器,用于设置日志采集。
- Handler 处理器,将日志记录发送至合适的路径。
- Filter 过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。
- Formatter 格式化器,指明了最终输出中日志的格式。
Logger 记录器
Logger是一个树形层级结构,在使用接口debug,info,warn,error,critical;使用之前必须创建Logger实例,即创建一个记录器,如果没有显式的进行创建,则默认创建一个root logger,并应用默认的日志级别(WARN),处理器Handler(StreamHandler,即将日志信息打印输出在标准输出上),和格式化器Formatter(默认的格式即为第一个简单使用程序中输出的格式)。
方法:
basicConfig(**kwargs) 为日志记录系统做基本配置。
部分参数
filename 指定日志文件名称
filemode 指定打开文件的模式,如果指定了 filename(如果文件模式未指定,则默认为'a)
Tips:文件读写模式
- w 以写方式打开,
- W 文件若存在,首先要清空,然后(重新)创建
- a 以追加模式打开 (从 EOF 开始, 必要时创建新文件)
- r+ 以读写模式打开
- w+ 以读写模式打开 (参见 w )
- a+ 以读写模式打开 (参见 a )
format 为处理程序使用指定的格式字符串。
datefmt 使用指定的日期 /时间格式。样式如果指定了格式字符串,则使用它来指定 格式字符串的类型.
level 将根记录器级别设置为指定级别。
logging_test.py
# coding=utf-
8
#
1
.先设置编码,utf-
8可支持中英文,如上,一般放在第一行
#
2
.注释:包括记录创建时间,创建人,项目名称。
'''
Created on
2019
-
8
-
14
@author: 北京
-宏哥 QQ交流群:
707699217
Project:学习和使用appium自动化测试
-代码和数据分离-
日志收集
'''
#
3
.导入模块
import logging
# logging.basicConfig(level
=
logging.DEBUG)
logging.basicConfig(level
=
logging.INFO)
logging.debug(
'
debug info
'
)
logging.info(
'
hello 宏哥
'
)
logging.warning(
'
warning info
'
)
logging.error(
'
error info
'
)
logging.critical(
'
critical info
'
)
Handler 处理器
Handler 处理器,将日志记录发送至合适的路径,Handler处理器类型有很多种,比较常用的有三个:
1.StreamHandler
将日志记录输出发送到诸如 sys.stdout,sys.stderr或任何类似文件流的对象。上面例子就是输出到控制台
2.FileHandler
将日志记录输出发送到磁盘文件。 它继承了 StreamHandler的输出功能。
logging.basicConfig(filename=
'
runlog.log
'
,level=logging.DEBUG)
3.NullHandler
不做任何格式化或输出。 它本质上是一个开发人员使用的 “无操作”处理程序。
Filter 过滤器
Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。
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.basicConfig(filename=
'
runlog.log
'
,level=
logging.DEBUG,
format
=
'
%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
'
)
输出结果:
2019
-
08
-
14
14
:
35
:
19
,
430
logging_test.py[line:
6
]DEBUGdebug info
2019
-
08
-
14
14
:
35
:
19
,
430
logging_test.py[line:
7
]INFOhello hongge
2019
-
08
-
14
14
:
35
:
19
,
430
logging_test.py[line:
8
]WARNINGwarning info
2019
-
08
-
14
14
:
35
:
19
,
430
logging_test.py[line:
9
]ERRORerror info
2019
-
08
-
14
14
:
35
:
19
,
430
logging_test.py[line:
10
]CRITICALcritical info
Logging实战操作
测试场景
将前面所学的启动考研帮 App的脚本增加log采集功能,设置指定的日志格式输出,并将日志保存到指定文件。
代码实现
kyb_logger.py
# coding=utf-
8
#
1
.先设置编码,utf-
8可支持中英文,如上,一般放在第一行
#
2
.注释:包括记录创建时间,创建人,项目名称。
'''
Created on
2019
-
8
-
14
@author: 北京
-宏哥 QQ交流群:
707699217
Project:学习和使用appium自动化测试
-代码和数据分离-
日志收集
'''
#
3
.导入模块
from
appium import webdriver
import yaml
import logging
from
selenium.common.exceptions import NoSuchElementException
file
=open(
'
./desired_caps.yaml
'
,
'
r
'
)
data
=
yaml.load(file)
logging.basicConfig(level
=logging.INFO,filename=
'
runlog.log
'
,
format
=
'
%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s%(message)s
'
)
desired_caps
=
{}
desired_caps[
'
platformName
'
]=data[
'
platformName
'
]
desired_caps[
'
platformVersion
'
]=data[
'
platformVersion
'
]
desired_caps[
'
deviceName
'
]=data[
'
deviceName
'
]
desired_caps[
'
app
'
]=data[
'
app
'
]
desired_caps[
'
appPackage
'
]=data[
'
appPackage
'
]
desired_caps[
'
appActivity
'
]=data[
'
appActivity
'
]
desired_caps[
'
noReset
'
]=data[
'
noReset
'
]
logging.info(
'
start app...
'
)
driver
=webdriver.Remote(
'
http://
'
+str(data[
'
ip
'
])+
'
:
'
+str(data[
'
port
'
])+
'
/wd/hub
'
,desired_caps)
def check_cancelBtn():
logging.info(
'
check cancelBtn
'
)
try
:
cancelBtn
= driver.find_element_by_id(
'
android:id/button2
'
)
except NoSuchElementException:
logging.info(
'
no cancelBtn
'
)
else
:
cancelBtn.click()
def check_skipBtn():
logging.info(
'
check skipBtn
'
)
try
:
skipBtn
= driver.find_element_by_id(
'
com.tal.kaoyan:id/tv_skip
'
)
except NoSuchElementException:
logging.info(
'
no skipBtn
'
)
else
:
skipBtn.click()
check_cancelBtn()
check_skipBtn()
runlog.log
2019
-
08
-
14
15
:
27
:
38
,
964
kyb_logger.py[line:
32
]INFOstart app...
2019
-
08
-
14
15
:
27
:
47
,
641
poolmanager.py[line:
358
]INFORedirecting http:
//
127.0.0.1:4723/wd/hub/session ->
http://127.0.0.1
:4723/wd/hub/session/dfc8e7e7-71cc-4f0b-9aa6-5db0fdc98a84
2019
-
08
-
14
15
:
27
:
47
,
644
kyb_logger.py[line:
36
]INFOcheck cancelBtn
2019
-
08
-
14
15
:
27
:
49
,
442
kyb_logger.py[line:
46
]INFOcheck skipBtn
问题思考
前面我们已经实现了在代码中增添 log,log也按照预期的采集到了,看似一切完美无瑕。但是该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
=
form02
args
=
(sys.stdout,)
[handler_fileHandler]
class
=
FileHandler
level
=
INFO
formatter
=
form01
args
=(
'
runlog.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
在需要调用的模块增加如下代码:
import logging
import logging.config
CON_LOG
=
'
log.conf
'
logging.config.fileConfig(CON_LOG)
logging
=logging.getLogger()
方法:
fileConfig(fname, defaults=None, disable_existing_loggers=True)
该放在作用是从 ConfigParser格式的文件中读取日志配置,同时如果当前脚本有配置log参数,则覆盖当前log配置选项。
代码实现
kyb_logconf.py
# coding=utf-
8
#
1
.先设置编码,utf-
8可支持中英文,如上,一般放在第一行
#
2
.注释:包括记录创建时间,创建人,项目名称。
'''
Created on
2019
-
8
-
14
@author: 北京
-宏哥 QQ交流群:
707699217
Project:学习和使用appium自动化测试
-代码和数据分离-
日志收集
'''
#
3
.导入模块
from
appium import webdriver
import yaml
import logging
import logging.config
from
selenium.common.exceptions import NoSuchElementException
file
=open(
'
./desired_caps.yaml
'
,
'
r
'
)
data
=
yaml.load(file)
CON_LOG
=
'
log.conf
'
logging.config.fileConfig(CON_LOG)
logging
=
logging.getLogger()
desired_caps
=
{}
desired_caps[
'
platformName
'
]=data[
'
platformName
'
]
desired_caps[
'
platformVersion
'
]=data[
'
platformVersion
'
]
desired_caps[
'
deviceName
'
]=data[
'
deviceName
'
]
desired_caps[
'
app
'
]=data[
'
app
'
]
desired_caps[
'
appPackage
'
]=data[
'
appPackage
'
]
desired_caps[
'
appActivity
'
]=data[
'
appActivity
'
]
desired_caps[
'
noReset
'
]=data[
'
noReset
'
]
logging.info(
'
start app...
'
)
driver
=webdriver.Remote(
'
http://
'
+str(data[
'
ip
'
])+
'
:
'
+str(data[
'
port
'
])+
'
/wd/hub
'
,desired_caps)
def check_cancelBtn():
logging.info(
'
check cancelBtn
'
)
try
:
cancelBtn
= driver.find_element_by_id(
'
android:id/button2
'
)
except NoSuchElementException:
logging.info(
'
no cancelBtn
'
)
else
:
cancelBtn.click()
def check_skipBtn():
logging.info(
'
check skipBtn
'
)
try
:
skipBtn
= driver.find_element_by_id(
'
com.tal.kaoyan:id/tv_skip
'
)
except NoSuchElementException:
logging.info(
'
no skipBtn
'
)
else
:
skipBtn.click()
check_cancelBtn()
check_skipBtn()
小结
元芳,今天分享的知识快要结束,你给总结一下,把结案文书写一下,交给我。元芳,你怎么看?日志的收集。
好了好了,大人,元芳今天太累了,所以说出如此大逆不道的话,求大人原谅他,结案文书我稍后交给大人。
结案文书:
1.Logger是一个树形层级结构
Logger可以包含一个或多个Handler和Filter,即Logger与Handler或Fitler是一对多的关系;
一个Logger实例可以新增多个Handler,一个Handler可以新增多个格式化器或多个过滤器,而且日志级别将会继承。
2.Logging工作流程
logging模块使用过程
- 第一次导入logging模块或使用reload函数重新导入logging模块,logging模块中的代码将被执行,这个过程中将产生logging日志系统的默认配置。
- 自定义配置(可选)。logging标准模块支持三种配置方式: dictConfig,fileConfig,listen。其中,dictConfig是通过一个字典进行配置Logger,Handler,Filter,Formatter;fileConfig则是通过一个文件进行配置;而listen则监听一个网络端口,通过接收网络数据来进行配置。当然,除了以上集体化配置外,也可以直接调用Logger,Handler等对象中的方法在代码中来显式配置。
- 使用logging模块的全局作用域中的getLogger函数来得到一个Logger对象实例(其参数即是一个字符串,表示Logger对象实例的名字,即通过该名字来得到相应的Logger对象实例)。
- 使用Logger对象中的debug,info,error,warn,critical等方法记录日志信息。
您的肯定就是我进步的动力。
如果你感觉还不错,就请鼓励一下吧!记得点波
推荐
哦!!!(点击右边的小球即可!(
^__^
) 嘻嘻……)
个人公众号 微信群 (微信群已满100,可以加宏哥的微信拉你进群,请备注:进群)

