Logging模块简介
logging模块介绍
Python提供了一个日志模块logging,可以通过它存储各种格式的日志,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径等;日志对于软件开发的开发、调试和运行整个过程中都起着很重要的作用。通过日志不仅可以调试程序,也可以了解到软件运行的状况,及时发现问题、定位问题及解决问题。
logging与print()的区别
logging可以设置不同的日志等级;
可以指定如何输出及输出的位置;
logging日志的级别
级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG
级别(level) | 描述 | 对应值 |
---|---|---|
CRITICAL | 当发生严重错误,导致应用程序不能继续运行时记录的信息 | 50 |
ERROR | 由于一个严重的问题导致某些功能不能正常运行时记录的信息 | 40 |
WARNING | 当某些不期望的事情发生时记录的信息,但此时应用程序还是正常运行的 | 30 |
INFO | 信息较为详细,仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照预期的那样进行工作 | 20 |
DEBUG | 最详细的日志信息,典型应用场景是问题诊断 | 10 |
NOTSET | 不设置,按照父logger级别来过滤日志 | 0 |
logging常用函数
不同级别日志的输出都有对应的内置函数
函数 | 说明 |
---|---|
logging.critical(msg, *args, **kwargs) | 创建一条严重级别为CRITICAL的日志记录 |
logging.error(msg, *args, **kwargs) | 创建一条严重级别为ERROR的日志记录 |
logging.warning(msg, *args, **kwargs) | 创建一条严重级别为WARNING的日志记录 |
logging.info(msg, *args, **kwargs) | 创建一条严重级别为INFO的日志记录 |
logging.debug(msg, *args, **kwargs) | 创建一条严重级别为DEBUG的日志记录 |
logging.log(msg, *args, **kwargs) | 创建一条严重级别为level的日志记录 |
logging.basicConfig(msg, *args, **kwargs) | 对root logger进行一次性配置 |
其中logging.basicConfig(msg, *args, **kwargs)
函数用于指定“要记录的日志级别”、“日志格式”、“日志输出位置”、“日志文件的打开模式”等信息,其他几个都是用于记录各个级别日志的函数。
import logging
logging.basicConfig(
# 1、日志输出位置:1、终端 2、文件
# filename = 'access.log', # 不指定,默认打印到终端
# 2、日志格式
# %(levelno)s: 打印日志级别的数值
# %(levelname)s: 打印日志级别名称
# %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
# %(filename)s: 打印当前执行程序名
# %(funcName)s: 打印日志的当前函数
# %(lineno)d: 打印日志的当前行号
# %(asctime)s: 打印日志的时间
# %(thread)d: 打印线程ID
# %(threadName)s: 打印线程名称
# %(process)d: 打印进程ID
# %(message)s: 打印日志信息
# %(name)s: Logger的名字
format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
# 3、时间格式
# datefmt='%Y-%m-%d %H:%M:%S %p',
# 4、日志级别
# 级别排序:critical > error > warning > info > debug
# critical => 50 # 只打印critical级别日志
# error => 40 # 打印error,critical级别日志
# warning => 30 # 打印warning,error,critical级别日志
# info => 20 # 打印info,warning,error,critical级别日志
# debug => 10 # 打印全部级别日志
level=10,
)
上面的代码也可以写成:
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s')
调用代码
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')
执行结果:
2022-11-11 10:24:16,018 - root - DEBUG -demo_log: 调试debug
2022-11-11 10:24:16,018 - root - INFO -demo_log: 消息info
2022-11-11 10:24:16,018 - root - WARNING -demo_log: 警告warn
2022-11-11 10:24:16,018 - root - ERROR -demo_log: 错误error
2022-11-11 10:24:16,018 - root - CRITICAL -demo_log: 严重critical
logging四大组件
组件 | 说明 |
---|---|
logger(日志器) | 提供应用程序代码直接使用的接口,logging模块提供了一个默认的日志收集器root(可参考上面代码执行结果) |
handler(处理器) | 用于将日志记录发送到指定的目的位置,比较常用的有三个,StreamHandler,FileHandler,NullHandler |
filter(过滤器) | 提供更细粒度的日志过滤功能,用于决定哪些日志记录将会被输出(其它的日志记录将会被忽略) |
formatter(格式器) | 用于控制日志信息的最终输出格式 |
Logging基本使用
基本步骤
- 设置logger名称,若不设置,将使用默认的root
- 设置log级别,这里指的是日志器收集日志的级别
- 创建handler,可以设置输出到控制台或写入日志文件
- 设置日志级别,这里指的是处理器输出的日志级别(比如,日志器设置为INFO,即收集INFO、WARNING、ERROR、CRITICAL4个级别的日志,则处理器可以设置为除ERROR级别以外4个级别的任意一个。若处理器设置为WARNING,则处理器只能输出WARNING、ERROR、CRITICAL3个级别的日志)
- 定义handler的输出格式
- 添加handler
日志输出到控制台
import logging
# 创建日志器logger,并设置名称,未设置名称使用默认的root
logger = logging.getLogger("pls")
# 设置日志收集器收集log的级别
logger.setLevel(logging.INFO)
# 创建输出到控制台的处理器handler
fh_stream = logging.StreamHandler()
# 设置处理器handler输出日志的级别
fh_stream.setLevel(logging.WARNING)
# 定义日志的输出格式
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
# 为handler设置日志输出格式
fh_stream.setFormatter(formatter)
# 将处理器添加到日志器进行关联
logger.addHandler(fh_stream)
logger.critical('this is a critical')
logger.error('this is a error')
logger.warning('this is a warning')
logger.info('this is a info')
logger.debug('this is a debug')
执行结果:
2022-11-11 15:45:16,899 pls CRITICAL this is a critical
2022-11-11 15:45:16,899 pls ERROR this is a error
2022-11-11 15:45:16,899 pls WARNING this is a warning
日志写入文件
import logging
# 创建日志器logger,并设置名称,未设置名称使用默认的root
logger = logging.getLogger("pls")
# 设置日志收集器收集log的级别
logger.setLevel(logging.INFO)
# 创建输出到控制台的处理器handler
fh_stream = logging.StreamHandler()
# 创建输出到文件的处理器handler
fh_file = logging.FileHandler('../logs/test.log')
# 设置处理器handler输出日志的级别
fh_file.setLevel(logging.WARNING)
# 定义日志的输出格式
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s')
# 为handler设置日志输出格式
fh_file.setFormatter(formatter)
# 将处理器添加到日志器进行关联
logger.addHandler(fh_file)
logger.critical('this is a critical')
logger.error('this is a error')
logger.warning('this is a warning')
logger.info('this is a info')
logger.debug('this is a debug')
执行结果:
在../logs/test.log中写入了下面的日志信息
2022-11-11 15:58:30,505 pls CRITICAL this is a critical
2022-11-11 15:58:30,505 pls ERROR this is a error
2022-11-11 15:58:30,505 pls WARNING this is a warning
Logging封装
封装成工具类
自定义封装PLLogger
类,日志的生成路径和文件名,以及日志的等级都提取到工具类中
import logging
from logging import Logger
from common.tools import get_log_file, get_log_level
class PLLogger(Logger):
def __init__(self, name, level=logging.INFO, file=None):
# 调用父类的初始化方法
super().__init__(name, level)
# 定义日志输出格式
fmt_str = "%(asctime)s %(name)s %(levelname)s %(filename)s [%(lineno)d] %(message)s "
# 实例化一个日志格式类
formatter = logging.Formatter(fmt_str)
# log输出到终端控制台
stream_handle = logging.StreamHandler()
stream_handle.setFormatter(formatter)
# 将输出渠道与日志收集器绑定
self.addHandler(stream_handle)
if file:
# log写入到文件
file_handle = logging.FileHandler(file, encoding="utf-8")
file_handle.setFormatter(formatter)
# 将输出渠道与日志收集器绑定
self.addHandler(file_handle)
pllog = PLLogger(name='PLS自动化测试', level=get_log_level(), file=get_log_file())
在需要记录日志的文件中导入PLLogger
进行日志的收集与输出
import time
import pytest
import allure
import xlrd
from common.PLLogger import pllog
def readExecl():
datas = list()
book = xlrd.open_workbook('./testdata/login_data.xlsx')
sheet = book.sheet_by_index(0)
for item in range(1, sheet.nrows):
datas.append(sheet.row_values(item))
return datas
@pytest.mark.parametrize('username, password, result, title', readExecl())
class Test_login:
@allure.description("测试登录功能的测试用例")
@allure.epic("登录注册epic")
@allure.feature("登录feature")
@allure.story("登录story")
@allure.title("登录用例-{title}")
@allure.tag("登录注册tag")
@allure.link('http://101.43.8.10/', name='ShopXO商城')
@allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
@allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
@allure.severity(allure.severity_level.CRITICAL)
def test_login_success(self, driver, username, password, result, title):
""" 登录成功断言账号信息 """
pllog.info("创建一条测试用例,用例标题为:{}".format(title))
with allure.step("点击登录按钮,跳转到登录页面"):
driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
with allure.step("在登录页面输入用户名"):
driver.find_element('xpath',
'/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
username)
with allure.step("在登录页面输入密码"):
driver.find_element('xpath',
'/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
password)
pllog.info("测试数据为:用户名:{},密码:{}".format(username, password))
with allure.step("点击登录按钮"):
driver.find_element('xpath',
'/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
time.sleep(3)
with allure.step("获取登录成功后左上角的登录名"):
login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
time.sleep(1)
with allure.step("断言获取的登录名与预期结果是否一致"):
try:
assert result in login_text
except AssertionError as e:
pllog.error(f"断言失败:{e}")
raise e
time.sleep(1)
执行结果:
$ python run.py
================================================================ test session starts ================================================================
platform darwin -- Python 3.9.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo02/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '6.2.5', 'pluggy': '1.0.0'}, 'Plugins': {'variables': '2.0.0', 'html': '3.2.0', 'base-url': '2.0.0', 'allure-pytest': '2.12.0', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home', 'Base URL': ''}
rootdir: /Users/laobai/workspaces/testdemo02, configfile: pytest.ini, testpaths: testcases
plugins: variables-2.0.0, html-3.2.0, base-url-2.0.0, allure-pytest-2.12.0, metadata-2.0.4
collected 2 items
testcases/test_login.py::Test_login::test_login_success[PLChrome-laobai-a123456-laobai-登录成功-管理员账号登录] 2022-12-28 14:14:44,811 PLS自动化测试 test_login.py [62] 创建一条测试用例,用例标题为:登录成功-管理员账号登录
2022-12-28 14:14:45,776 PLS自动化测试 INFO test_login.py [75] 测试数据为:用户名:laobai,密码:a123456
PASSED
testcases/test_login.py::Test_login::test_login_success[PLChrome-plscript-y123456-plscript1-登录成功-商户账号登录] 2022-12-28 14:14:56,362 PLS自动化测NFO test_login.py [62] 创建一条测试用例,用例标题为:登录成功-商户账号登录
2022-12-28 14:14:57,571 PLS自动化测试 INFO test_login.py [75] 测试数据为:用户名:plscript,密码:y123456
2022-12-28 14:15:01,692 PLS自动化测试 ERROR test_login.py [92] 断言失败:assert 'plscript1' in 'plscript,欢迎来到'
FAILED
封装成装饰器
将上面封装好的PLLogger.py
再次包装成装饰器,不过装饰器只能修饰类或方法/函数,也就是只能对测试用例进行日志记录,而无法对测试用例中的某些步骤进行日志记录输出。
from functools import wraps
from common.PLLogger import pllog
def pllog_decorators(func):
@wraps(func)
def wrap_func(*args, **kwargs):
tuple_agrs = args
dict_kwargs = kwargs
func(*args, **kwargs)
pllog.debug(
f'{func.__name__}(*args: tuple = *{tuple_agrs}, **kwargs: dict = **{dict_kwargs})')
return wrap_func
装饰测试用例
import time
import pytest
import allure
import xlrd
from common.PLLogger import pllog
from common.decorators import pllog_decorators
def readExecl():
datas = list()
book = xlrd.open_workbook('./testdata/login_data.xlsx')
sheet = book.sheet_by_index(0)
for item in range(1, sheet.nrows):
datas.append(sheet.row_values(item))
return datas
@pytest.mark.parametrize('username, password, result, title', readExecl())
class Test_login:
@pllog_decorators
@allure.description("测试登录功能的测试用例")
@allure.epic("登录注册epic")
@allure.feature("登录feature")
@allure.story("登录story")
@allure.title("登录用例-{title}")
@allure.tag("登录注册tag")
@allure.link('http://101.43.8.10/', name='ShopXO商城')
@allure.issue('https://demo16.zentao.net/bug-view-162.html', name='bug162')
@allure.testcase('https://demo16.zentao.net/testcase-view-690-1.html', name='登录注册测试用例')
@allure.severity(allure.severity_level.CRITICAL)
def test_login_success(self, driver, username, password, result, title):
""" 登录成功断言账号信息 """
# pllog.info("创建一条测试用例,用例标题为:{}".format(title))
with allure.step("点击登录按钮,跳转到登录页面"):
driver.find_element('xpath', '/html/body/div[6]/div/div[1]/div[2]/a[1]').click()
with allure.step("在登录页面输入用户名"):
driver.find_element('xpath',
'/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[1]/input').send_keys(
username)
with allure.step("在登录页面输入密码"):
driver.find_element('xpath',
'/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[2]/div/input').send_keys(
password)
with allure.step("点击登录按钮"):
driver.find_element('xpath',
'/html/body/div[4]/div/div[2]/div[2]/div/div/div[1]/form/div[3]/button').click()
time.sleep(3)
with allure.step("获取登录成功后左上角的登录名"):
login_text = driver.find_element('xpath', '/html/body/div[2]/div/ul[1]/div/div/em[2]').text
time.sleep(1)
with allure.step("断言获取的登录名与预期结果是否一致"):
try:
assert result in login_text
except AssertionError as e:
raise e
time.sleep(1)
执行测试:
$ python run.py
================================================================ test session starts ================================================================
platform darwin -- Python 3.9.12, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo02/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '6.2.5', 'pluggy': '1.0.0'}, 'Plugins': {'variables': '2.0.0', 'html': '3.2.0', 'base-url': '2.0.0', 'allure-pytest': '2.12.0', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home', 'Base URL': ''}
rootdir: /Users/laobai/workspaces/testdemo02, configfile: pytest.ini, testpaths: testcases
plugins: variables-2.0.0, html-3.2.0, base-url-2.0.0, allure-pytest-2.12.0, metadata-2.0.4
collected 2 items
testcases/test_login.py::Test_login::test_login_success[PLChrome-laobai-a123456-laobai-登录成功-管理员账号登录] 2022-12-28 15:28:25,797 PLS自动化测试G decorators.py [19] test_login_success(*args: tuple = *(<test_login.Test_login object at 0x1081ef760>,), **kwargs: dict = **{'driver': <selenium.webdriver.chrome.webdriver.WebDriver (session="341d4c43c043a4012e4eb76a35c9d121")>, 'username': 'laobai', 'password': 'a123456', 'result': 'laobai', 'title': '登录成功-管理员账号登录'})
PASSED
testcases/test_login.py::Test_login::test_login_success[PLChrome-plscript-y123456-plscript-登录成功-商户账号登录] 2022-12-28 15:28:35,168 PLS自动化测BUG decorators.py [19] test_login_success(*args: tuple = *(<test_login.Test_login object at 0x108147820>,), **kwargs: dict = **{'driver': <selenium.webdriver.chrome.webdriver.WebDriver (session="7f1a98b931893a0b5bb4d955868d469a")>, 'username': 'plscript', 'password': 'y123456', 'result': 'plscript', 'title': '登录成功-商户账号登录'})
PASSED
================================================================ 2 passed in 19.54s =================================================================
Report successfully generated to /Users/laobai/workspaces/testdemo02/report/html