英文:
Pytest: logging using allure
问题
为了向我的 allure
报告添加步骤,我正在使用以下函数:
import allure
from datetime import datetime
class Report:
@staticmethod
def report_step(step_title):
with allure.step(f'[{datetime.now()}]: {step_title}'):
pass
而我想要找到一种方法来添加 logging
到我的代码中。因此,我考虑将 logging
添加到这个函数中:
import logging
def report_step(step_title):
m = f'[{datetime.now()}]: {step_title}'
with allure.step(m):
logging.debug(m)
但是我对这种方法有两个问题:
-
我无法根据此方法确定日志的级别(尽管我可以为此
report_step
方法添加另一个参数)。 -
在我的日志中,我使用以下格式:
[%(asctime)s] [ %(levelname)s ] [%(filename)s] [%(funcName)s] [%(lineno)d]: %(message)s
在这种情况下,使用
report_step
方法,所有日志行都将从相同的类名(例如我的示例中的Report
)中写入。如果需要进行调查,我需要首先找到日志行来自何处。有什么建议吗?
英文:
So In order to add steps to my allure
report I am using this function:
import allure
from datetime import datetime
class Report:
@staticmethod
def report_step(step_title):
with allure.step(f'[{datetime.now()}]: {step_title}'):
pass
And I want to find a way to add logging
to my code as well.
So I was thinking of add logging
into this function:
def report_step(step_title):
m = f'[{datetime.now()}]: {step_title}'
with allure.step(m):
logging.debug(m)
But I have 2 problems with this approach:
-
I cannot determine log
level
according this method (although I can add another parameter to thisreport_step
method) -
In my log I am using this format:
'[%(asctime)s] [ %(levelname)s ] [%(filename)s] [%(funcName)s] [%(lineno)d]: %(message)s'
And In my case here with this report_step
method all log lines will write from the same class name (Report in my example) And in case I will need to investigate I will need to find first from where the log lines come from.
Any suggestions ?
答案1
得分: 1
由于pytest插件非常灵活,您可能不需要创建另一个上下文管理器来包装`allure.step`,只需定义一些将与之一起执行的钩子。
因此,基本上,在我的解决方案中,我创建了一个包含`allure-pytest`插件的钩子实现的本地插件。本地插件(钩子和固定装置)可以存储在`conftest.py`中(或者在任何其他模块中,但在`conftest.py`中使用`pytest_plugins`变量进行声明)。
首先,我们需要添加一个命令行选项,该选项将用于设置我们的记录器的日志级别。可以使用[pytest_addoption][1]钩子完成此操作。
然后,我们需要将我们的插件定义为一个类,该类需要pytest `config`变量,并将从中定义相应的日志级别。
此类还必须包含带有`@allure_commons.hookimpl`装饰器的`start_step`函数,该装饰器指定它是钩子实现。
最后,我们需要使用[pytest_configure][2]钩子将我们的插件注册到`allure_commons.plugin_manager`中。
我将所有的代码都放在了`conftest.py`中,包括本地插件类,仅供示例参考。
**conftest.py**
```python
import inspect
import logging
import allure_commons
from _pytest.config import UsageError
class AllureStepLogger:
def __init__(self, config):
# 创建一个记录器
self.logger = logging.getLogger(self.__class__.__name__)
# 获取--allure-step-log-level的值
self.level = config.option.allure_step_log_level
if isinstance(self.level, str):
self.level = self.level.upper()
# 通过级别名称获取级别数
try:
self.level = int(getattr(logging, self.level, self.level))
except ValueError as e:
# Python日志不将其识别为日志级别
raise UsageError(
f"'{self.level}'不被识别为'{self.__class__.__name__}'的日志级别名称。请考虑传递日志级别数值。"
) from e
@allure_commons.hookimpl
def start_step(self, uuid, title, params):
"""添加一个钩子实现,以记录每个步骤"""
# 从堆栈中获取test_*函数名
test_name = next((frame[3] for frame in inspect.stack() if frame[3].startswith("test_")), "未知测试")
# 使用定义的记录器和日志级别记录消息
self.logger.log(self.level, f"{test_name}: {title}")
def pytest_configure(config):
"""如果注册了'allure_pytest'插件,则注册'allure_step_logger'插件。"""
if config.pluginmanager.getplugin('allure_pytest'):
allure_commons.plugin_manager.register(AllureStepLogger(config), "allure_step_logger")
def pytest_addoption(parser):
"""添加一个命令行选项--allure-step-log-level。"""
parser.getgroup("logging").addoption(
"--allure-step-log-level",
dest="allure_step_log_level",
default="debug",
metavar="ALLURE_STEP_LEVEL",
help="allure.step日志消息的级别,默认为'DEBUG'。"
)
test_module.py
import allure
def test_function_1():
with allure.step("步骤 1"):
pass
with allure.step("子步骤 1.1"):
pass
with allure.step("子步骤 1.2"):
pass
with allure.step("步骤 2"):
pass
def test_function_2():
with allure.step("步骤 1"):
pass
with allure.step("步骤 2"):
pass
with allure.step("步骤 3"):
pass
运行pytest --alluredir=results --log-cli-level=debug
命令的输出将包含我们的日志,因为--allure-step-log-level
默认为debug
。
============================= 测试会话开始 =============================
collecting ... collected 2 items
test_module.py::test_function_1
test_module.py::test_function_2
============================== 2 passed in 0.08s ==============================
进程结束,退出码:0
-------------------------------- 实时日志调用 --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 步骤 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 子步骤 1.1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 子步骤 1.2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: 步骤 2
通过 [ 50%]
-------------------------------- 实时日志调用 --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: 步骤 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: 步骤 2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: 步骤 3
通过 [100%]
要将记录器级别更改为INFO
,只需使用--allure-step-log-level=info
。
<details>
<summary>英文:</summary>
As pytest plugins are very flexible you may not create another context manager to wrap `allure.step` but just define some hooks that will be executed along with it.
So basically, in my solution I create a local plugin that contains `allure-pytest` plugin's hook implementation. Local plugins (hooks and fixtures) can be stored in `conftest.py` (or any other module but with declaration in `conftest.py` using `pytest_plugins` variable).
First of all, we need to add a command line option that will be used to set a log level for our logger. It can be done with [pytest_addoption][1] hook.
Then, we need to define our plugin as a class that requires the pytest `config` variable and will define a corresponding log level from it.
Also this class must contain `start_step` function with `@allure_commons.hookimpl` decorator which specifies that it is hook implementation.
And finally, we need to register our plugin with `allure_commons.plugin_manager` using [pytest_configure][2] hook.
I put all code to `conftest.py` including local plugin class just for example.
**conftest.py**
```python
import inspect
import logging
import allure_commons
from _pytest.config import UsageError
class AllureStepLogger:
def __init__(self, config):
# Create a logger
self.logger = logging.getLogger(self.__class__.__name__)
# Get --allure-step-log-level value
self.level = config.option.allure_step_log_level
if isinstance(self.level, str):
self.level = self.level.upper()
# Get a level number by a level name
try:
self.level = int(getattr(logging, self.level, self.level))
except ValueError as e:
# Python logging does not recognise this as a logging level
raise UsageError(
"'{}' is not recognized as a logging level name for "
"'{}'. Please consider passing the "
"logging level num instead.".format(self.level, self.__class__.__name__)
) from e
@allure_commons.hookimpl
def start_step(self, uuid, title, params):
"""Add a hook implementation to log every step"""
# get test_* function name from stack
test_name = next((frame[3] for frame in inspect.stack() if frame[3].startswith("test_")), "Unknown test")
# log a message using defined logger and log level
self.logger.log(self.level, f"{test_name}: {title}")
def pytest_configure(config):
"""Register `allure_step_logger` plugin if `allure_pytest` plugin is registered."""
if config.pluginmanager.getplugin('allure_pytest'):
allure_commons.plugin_manager.register(AllureStepLogger(config), "allure_step_logger")
def pytest_addoption(parser):
"""Add a cmdline option --allure-step-log-level."""
parser.getgroup("logging").addoption(
"--allure-step-log-level",
dest="allure_step_log_level",
default="debug",
metavar="ALLURE_STEP_LEVEL",
help="Level of allure.step log messages. 'DEBUG' by default."
)
test_module.py
import allure
def test_function_1():
with allure.step("Step 1"):
pass
with allure.step("Substep 1.1"):
pass
with allure.step("Substep 1.2"):
pass
with allure.step("Step 2"):
pass
def test_function_2():
with allure.step("Step 1"):
pass
with allure.step("Step 2"):
pass
with allure.step("Step 3"):
pass
Output for pytest --alluredir=results --log-cli-level=debug
will contain our logs since --allure-step-log-level
is debug
by default.
============================= test session starts =============================
collecting ... collected 2 items
test_module.py::test_function_1
test_module.py::test_function_2
============================== 2 passed in 0.08s ==============================
Process finished with exit code 0
-------------------------------- live log call --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Step 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Substep 1.1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Substep 1.2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_1: Step 2
PASSED [ 50%]
-------------------------------- live log call --------------------------------
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: Step 1
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: Step 2
[14:30:49] [ DEBUG ] [conftest.py] [start_step] [34]: test_function_2: Step 3
PASSED [100%]
To change logger level to INFO
for example, just use --allure-step-log-level=info
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论