Python之日志模块


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基本使用

基本步骤

  1. 设置logger名称,若不设置,将使用默认的root
  2. 设置log级别,这里指的是日志器收集日志的级别
  3. 创建handler,可以设置输出到控制台或写入日志文件
  4. 设置日志级别,这里指的是处理器输出的日志级别(比如,日志器设置为INFO,即收集INFO、WARNING、ERROR、CRITICAL4个级别的日志,则处理器可以设置为除ERROR级别以外4个级别的任意一个。若处理器设置为WARNING,则处理器只能输出WARNING、ERROR、CRITICAL3个级别的日志)
  5. 定义handler的输出格式
  6. 添加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

文章作者: 老百
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 老百 !
 上一篇
JSONPath使用介绍 JSONPath使用介绍
JSONPath是一种简单的方法来提取给定JSON文档的部分内容。JSONPath是跨语言的,很多语言都可以使用jsonpath,如Javascript,Python和PHP,Java等。
2022-11-13
下一篇 
Python中对CSV的操作 Python中对CSV的操作
Python附带一个CSV模块,该模块允许在不安装第三方库的情况下读取和写入CSV文件。默认情况下,它旨在处理Microsoft Excel生成的CSV文件格式,但它可以配置为读取和写入任何类型的 CSV 文件。
2022-11-08
  目录