Skip to content

Commit efd6df4

Browse files
committed
Add log attachments and async error handler to agent
Changes: - added custom Logger to support attachment in log messages; - added error handler which will re-raise exceptions from async client-Python thread; - updated examples with new logging posibilities.
1 parent 3e11651 commit efd6df4

File tree

4 files changed

+77
-12
lines changed

4 files changed

+77
-12
lines changed

README.rst

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ logging handler privided by plugin like bellow:
6060
.. code-block:: python
6161
6262
import logging
63-
# Import Report Portal handler to the test module.
64-
from pytest_reportportal import RPlogHandler
63+
# Import Report Portal logger and handler to the test module.
64+
from pytest_reportportal import RPLogger, RPLogHandler
6565
# Setting up a logging.
66+
logging.setLoggerClass(RPLogger)
6667
logger = logging.getLogger(__name__)
6768
logger.setLevel(logging.DEBUG)
6869
# Create handler for Report Portal.
@@ -79,6 +80,19 @@ logging handler privided by plugin like bellow:
7980
x = "this"
8081
logger.info("x is: %s", x)
8182
assert 'h' in x
83+
84+
# Message with an attachment.
85+
import subprocess
86+
free_memory = subprocess.check_output("free -h".split())
87+
logger.info(
88+
"Case1. Memory consumption",
89+
attachment={
90+
"name": "free_memory.txt",
91+
"data": free_memory,
92+
"mime": "application/octet-stream",
93+
},
94+
)
95+
8296
# This debug message will not be sent to the Report Portal.
8397
logger.debug("Case1. Debug message")
8498
@@ -98,4 +112,4 @@ Copyright Notice
98112

99113
Licensed under the GPLv3_ license (see the LICENSE file).
100114

101-
.. _GPLv3: https://www.gnu.org/licenses/quick-guide-gplv3.html
115+
.. _GPLv3: https://www.gnu.org/licenses/quick-guide-gplv3.html

pytest_reportportal/__init__.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,42 @@
1+
import sys
12
import logging
23

34
from .service import PyTestService
45

56

6-
class RPlogHandler(logging.Handler):
7+
class RPLogger(logging.getLoggerClass()):
8+
9+
def __init__(self, name, level=0):
10+
super(RPLogger, self).__init__(name, level=level)
11+
12+
def _log(self, level, msg, args, exc_info=None, extra=None,
13+
attachment=None):
14+
"""
15+
Low-level logging routine which creates a LogRecord and then calls
16+
all the handlers of this logger to handle the record.
17+
"""
18+
if logging._srcfile:
19+
# IronPython doesn't track Python frames, so findCaller raises an
20+
# exception on some versions of IronPython. We trap it here so that
21+
# IronPython can use logging.
22+
try:
23+
fn, lno, func = self.findCaller()
24+
except ValueError:
25+
fn, lno, func = "(unknown file)", 0, "(unknown function)"
26+
else:
27+
fn, lno, func = "(unknown file)", 0, "(unknown function)"
28+
29+
if exc_info and not isinstance(exc_info, tuple):
30+
exc_info = sys.exc_info()
31+
32+
record = self.makeRecord(
33+
self.name, level, fn, lno, msg, args, exc_info, func, extra
34+
)
35+
record.attachment = attachment
36+
self.handle(record)
37+
38+
39+
class RPLogHandler(logging.Handler):
740

841
# Map loglevel codes from `logging` module to ReportPortal text names:
942
_loglevel_map = {
@@ -17,7 +50,7 @@ class RPlogHandler(logging.Handler):
1750
_sorted_levelnos = sorted(_loglevel_map.keys(), reverse=True)
1851

1952
def __init__(self, level=logging.NOTSET):
20-
super(RPlogHandler, self).__init__(level)
53+
super(RPLogHandler, self).__init__(level)
2154

2255
def emit(self, record):
2356
try:
@@ -31,4 +64,8 @@ def emit(self, record):
3164
if level <= record.levelno:
3265
break
3366

34-
return PyTestService.post_log(msg, loglevel=self._loglevel_map[level])
67+
return PyTestService.post_log(
68+
msg,
69+
loglevel=self._loglevel_map[level],
70+
attachment=record.__dict__.get("attachment", None),
71+
)

pytest_reportportal/plugin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ def pytest_runtest_makereport(self, item, call):
2323
report = (yield).get_result()
2424

2525
if report.longrepr:
26-
PyTestService.post_log(cgi.escape(report.longreprtext), loglevel='ERROR')
26+
PyTestService.post_log(
27+
cgi.escape(report.longreprtext),
28+
loglevel='ERROR',
29+
)
2730

2831
if report.when == "setup":
2932

pytest_reportportal/service.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
from reportportal_client import ReportPortalServiceAsync
55

66

7+
def async_error_handler(exception):
8+
raise exception
9+
10+
711
def timestamp():
812
return str(int(time() * 1000))
913

@@ -34,7 +38,8 @@ def init_service(self, endpoint, project, uuid):
3438
self.RP = ReportPortalServiceAsync(
3539
endpoint=endpoint,
3640
project=project,
37-
token=uuid)
41+
token=uuid,
42+
error_handler=async_error_handler)
3843
else:
3944
logging.debug("The pytest is already initialized")
4045
return self.RP
@@ -98,13 +103,19 @@ def finish_launch(self, launch=None, status="rp_launch"):
98103
"request_body={0}".format(fl_rq))
99104
self.RP.finish_launch(**fl_rq)
100105

101-
def post_log(self, message, loglevel='INFO'):
106+
def post_log(self, message, loglevel='INFO', attachment=None):
102107
if loglevel not in self._loglevels:
103-
logging.warning('Incorrect loglevel = {}. Force set to INFO. Avaliable levels: '
104-
'{}.'.format(loglevel, self._loglevels))
108+
logging.warning('Incorrect loglevel = {}. Force set to INFO. '
109+
'Avaliable levels: {}.'
110+
.format(loglevel, self._loglevels))
105111
loglevel = 'INFO'
106112

107-
sl_rq = dict(time=timestamp(), message=message, level=loglevel)
113+
sl_rq = dict(
114+
time=timestamp(),
115+
message=message,
116+
level=loglevel,
117+
attachment=attachment,
118+
)
108119
self.RP.log(**sl_rq)
109120

110121

0 commit comments

Comments
 (0)