t-bug-catcher 0.6.1__tar.gz → 0.6.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/PKG-INFO +1 -1
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/setup.cfg +1 -1
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/setup.py +1 -1
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/__init__.py +1 -1
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/bug_catcher.py +5 -2
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/config.py +5 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/jira.py +99 -26
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/MANIFEST.in +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/README.rst +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/pyproject.toml +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/requirements.txt +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/bug_snag.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/resources/whispers_config.yml +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/stack_saver.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/utils/common.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/SOURCES.txt +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.6.1 → t_bug_catcher-0.6.3}/tests/test_t_bug_catcher.py +0 -0
|
@@ -26,7 +26,7 @@ setup(
|
|
|
26
26
|
packages=find_packages(include=["t_bug_catcher", "t_bug_catcher.*"]),
|
|
27
27
|
test_suite="tests",
|
|
28
28
|
url="https://www.thoughtful.ai/",
|
|
29
|
-
version="0.6.
|
|
29
|
+
version="0.6.3",
|
|
30
30
|
zip_safe=False,
|
|
31
31
|
install_requires=install_requirements,
|
|
32
32
|
include_package_data=True,
|
|
@@ -111,6 +111,7 @@ class BugCatcher:
|
|
|
111
111
|
attachments: Optional[List] = None,
|
|
112
112
|
assignee: Optional[str] = None,
|
|
113
113
|
team: Optional[str] = None,
|
|
114
|
+
group_by: Optional[str] = None,
|
|
114
115
|
):
|
|
115
116
|
"""Reports an error to the Jira project.
|
|
116
117
|
|
|
@@ -121,6 +122,7 @@ class BugCatcher:
|
|
|
121
122
|
attachments (List, optional): The attachments to be added to the Jira issue. Defaults to None.
|
|
122
123
|
assignee (str, optional): The assignee to be added to the Jira issue. Defaults to None.
|
|
123
124
|
team (str, optional): The team to be assigned to the Jira issue. Defaults to None.
|
|
125
|
+
group_by (str, optional): The group to be assigned to the Jira issue. Defaults to None.
|
|
124
126
|
|
|
125
127
|
Returns:
|
|
126
128
|
None
|
|
@@ -182,6 +184,7 @@ class BugCatcher:
|
|
|
182
184
|
stack_trace=stack_trace,
|
|
183
185
|
additional_info=description,
|
|
184
186
|
metadata=metadata,
|
|
187
|
+
group_by=group_by,
|
|
185
188
|
)
|
|
186
189
|
|
|
187
190
|
if self.__configurator.is_bugsnag_configured:
|
|
@@ -271,7 +274,7 @@ class BugCatcher:
|
|
|
271
274
|
Args:
|
|
272
275
|
exc_type (type): The type of the exception.
|
|
273
276
|
exc_value (Exception): The value of the exception.
|
|
274
|
-
|
|
277
|
+
exc_traceback (traceback): The traceback of the exception.
|
|
275
278
|
|
|
276
279
|
Returns:
|
|
277
280
|
None
|
|
@@ -292,7 +295,7 @@ class BugCatcher:
|
|
|
292
295
|
stack_trace = self.__stack_saver.save_stack_trace(exc_value)
|
|
293
296
|
|
|
294
297
|
if self.__configurator.is_jira_configured:
|
|
295
|
-
self.__jira.report_unhandled_error(
|
|
298
|
+
self.__jira.report_unhandled_error(exc_value, stack_trace)
|
|
296
299
|
if self.__configurator.is_bugsnag_configured:
|
|
297
300
|
self.__bug_snag.report_unhandled_error(exc_type, exc_value, exc_traceback)
|
|
298
301
|
frames = get_frames(exc_value.__traceback__)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import socket
|
|
2
3
|
|
|
3
4
|
from .workitems import metadata, variables
|
|
4
5
|
|
|
@@ -16,6 +17,7 @@ class Config:
|
|
|
16
17
|
STACK_SCOPE: int = 3
|
|
17
18
|
STACK_ITEM_LENGTH: int = 100
|
|
18
19
|
STACK_TEXT_LENGTH: int = 10000
|
|
20
|
+
COMMENT_LIMIT: int = 4950
|
|
19
21
|
|
|
20
22
|
class TICKET_PRIORITIES:
|
|
21
23
|
"""Priorities class for configuring the application."""
|
|
@@ -32,6 +34,9 @@ class Config:
|
|
|
32
34
|
KEYS_TO_REMOVE = ["credential", "password"]
|
|
33
35
|
BUILD_INFO_FILE = "commit_info.json"
|
|
34
36
|
|
|
37
|
+
HOST = socket.gethostname() if os.name.lower() == "nt" else None
|
|
38
|
+
UNAME = os.getlogin() if os.name.lower() == "nt" else None
|
|
39
|
+
|
|
35
40
|
RC_RUN_LINK = (
|
|
36
41
|
f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
|
|
37
42
|
f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import re
|
|
8
8
|
import sys
|
|
9
9
|
import traceback
|
|
10
|
+
import zlib
|
|
10
11
|
from datetime import datetime
|
|
11
12
|
from importlib.metadata import version
|
|
12
13
|
from pathlib import Path
|
|
@@ -643,6 +644,34 @@ class Jira:
|
|
|
643
644
|
else []
|
|
644
645
|
)
|
|
645
646
|
|
|
647
|
+
@staticmethod
|
|
648
|
+
def __host_markup() -> List[dict]:
|
|
649
|
+
"""Create the host worker markup.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
dict: The host worker markup.
|
|
653
|
+
"""
|
|
654
|
+
return (
|
|
655
|
+
[
|
|
656
|
+
{
|
|
657
|
+
"type": "paragraph",
|
|
658
|
+
"content": [
|
|
659
|
+
{
|
|
660
|
+
"type": "text",
|
|
661
|
+
"text": "Host: ",
|
|
662
|
+
"marks": [{"type": "strong"}],
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
"type": "text",
|
|
666
|
+
"text": f"{CONFIG.HOST} > {CONFIG.UNAME}",
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
}
|
|
670
|
+
]
|
|
671
|
+
if CONFIG.HOST and CONFIG.UNAME
|
|
672
|
+
else []
|
|
673
|
+
)
|
|
674
|
+
|
|
646
675
|
@staticmethod
|
|
647
676
|
def __traceback_markup(exc_traceback_info: str) -> List[dict]:
|
|
648
677
|
"""Create the traceback markup.
|
|
@@ -837,6 +866,7 @@ class Jira:
|
|
|
837
866
|
+ self.__date_markup()
|
|
838
867
|
+ self.__runlink_markup()
|
|
839
868
|
+ self.__environment_markup()
|
|
869
|
+
+ self.__host_markup()
|
|
840
870
|
+ self.__branch_info_markup(exc_traceback)
|
|
841
871
|
+ (self.__description_markup(additional_info) if additional_info else [])
|
|
842
872
|
+ self.__traceback_markup(exc_traceback_info)
|
|
@@ -1125,9 +1155,10 @@ class Jira:
|
|
|
1125
1155
|
else []
|
|
1126
1156
|
)
|
|
1127
1157
|
|
|
1128
|
-
if len(issue.get("fields", {}).get("comment", [])) >=
|
|
1158
|
+
if len(issue.get("fields", {}).get("comment", [])) >= CONFIG.LIMITS.COMMENT_LIMIT:
|
|
1129
1159
|
logger.warning(
|
|
1130
|
-
f"Comments for '{issue.get('key')}' were not posted due to exceeding JIRA comments limit
|
|
1160
|
+
f"Comments for '{issue.get('key')}' were not posted due to exceeding JIRA comments limit "
|
|
1161
|
+
f"({CONFIG.LIMITS.COMMENT_LIMIT})."
|
|
1131
1162
|
)
|
|
1132
1163
|
return
|
|
1133
1164
|
|
|
@@ -1282,6 +1313,7 @@ class Jira:
|
|
|
1282
1313
|
stack_trace: Optional[str] = None,
|
|
1283
1314
|
metadata: Optional[dict] = None,
|
|
1284
1315
|
additional_info: Optional[str] = None,
|
|
1316
|
+
group_by: Optional[str] = None,
|
|
1285
1317
|
) -> dict:
|
|
1286
1318
|
"""Create a Jira issue with the given attachments.
|
|
1287
1319
|
|
|
@@ -1293,6 +1325,7 @@ class Jira:
|
|
|
1293
1325
|
stack_trace (str, optional): Stack trace to be added to the Jira issue.
|
|
1294
1326
|
metadata (dict, optional): Metadata to be added to the Jira issue.
|
|
1295
1327
|
additional_info (str, optional): Additional information to be added to the Jira issue.
|
|
1328
|
+
group_by (str, optional): group_by flag to be added to the Jira issue.
|
|
1296
1329
|
|
|
1297
1330
|
Returns:
|
|
1298
1331
|
The response from creating the Jira issue.
|
|
@@ -1318,13 +1351,32 @@ class Jira:
|
|
|
1318
1351
|
logger.warning(f"Only the first {CONFIG.LIMITS.MAX_ATTACHMENTS} attachments were uploaded.")
|
|
1319
1352
|
attachments = attachments[: CONFIG.LIMITS.MAX_ATTACHMENTS]
|
|
1320
1353
|
|
|
1321
|
-
|
|
1322
|
-
|
|
1354
|
+
error_id = self.__generate_error_id(exception)
|
|
1355
|
+
group_id = self.generate_group_id(group_by)
|
|
1356
|
+
|
|
1357
|
+
all_issues = self.get_issues()["issues"]
|
|
1358
|
+
|
|
1359
|
+
if group_id:
|
|
1360
|
+
existing_tickets = [
|
|
1361
|
+
ticket
|
|
1362
|
+
for ticket in all_issues
|
|
1363
|
+
if ticket["fields"]["description"] and f"{error_id}-" in ticket["fields"]["description"]
|
|
1364
|
+
]
|
|
1365
|
+
summary = self.__create_summary(
|
|
1366
|
+
type(exception),
|
|
1367
|
+
exception,
|
|
1368
|
+
exception.__traceback__,
|
|
1369
|
+
len(existing_tickets) + 1 if existing_tickets else 1,
|
|
1370
|
+
)
|
|
1371
|
+
error_id = f"{error_id}-{group_id}"
|
|
1372
|
+
else:
|
|
1373
|
+
summary = self.__create_summary(type(exception), exception, exception.__traceback__)
|
|
1323
1374
|
|
|
1324
1375
|
existing_ticket = self.filter_tickets(
|
|
1325
|
-
all_tickets=
|
|
1376
|
+
all_tickets=all_issues,
|
|
1326
1377
|
error_id=error_id,
|
|
1327
1378
|
)
|
|
1379
|
+
|
|
1328
1380
|
if existing_ticket:
|
|
1329
1381
|
self.__update_existing_ticket(
|
|
1330
1382
|
existing_ticket=existing_ticket,
|
|
@@ -1376,23 +1428,20 @@ class Jira:
|
|
|
1376
1428
|
self.report_internal_error(exception=ex, additional_info="Failed to report error.")
|
|
1377
1429
|
return False
|
|
1378
1430
|
|
|
1379
|
-
def report_unhandled_error(
|
|
1380
|
-
self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, stack_trace: str = None
|
|
1381
|
-
):
|
|
1431
|
+
def report_unhandled_error(self, exception: Exception, stack_trace: str = None):
|
|
1382
1432
|
"""Report an unhandled error to Jira.
|
|
1383
1433
|
|
|
1384
1434
|
Args:
|
|
1385
1435
|
exc_type (type): The type of the exception.
|
|
1386
|
-
|
|
1387
|
-
exc_traceback (TracebackType): The traceback of the exception.
|
|
1436
|
+
exception (Exception, str): The value of the exception.
|
|
1388
1437
|
stack_trace (str, optional): Stack trace to be added to the Jira issue.
|
|
1389
1438
|
|
|
1390
1439
|
Returns:
|
|
1391
1440
|
The response from creating the Jira issue.
|
|
1392
1441
|
"""
|
|
1393
1442
|
try:
|
|
1394
|
-
summary = self.__create_summary(
|
|
1395
|
-
error_id = self.__generate_error_id(
|
|
1443
|
+
summary = self.__create_summary(type(exception), exception, exception.__traceback__)
|
|
1444
|
+
error_id = self.__generate_error_id(exception)
|
|
1396
1445
|
|
|
1397
1446
|
existing_ticket = self.filter_tickets(
|
|
1398
1447
|
all_tickets=self.get_issues()["issues"],
|
|
@@ -1415,9 +1464,9 @@ class Jira:
|
|
|
1415
1464
|
logger.info(f"Failed to get assignee {self._default_assignee} due to: {ex}")
|
|
1416
1465
|
|
|
1417
1466
|
description = self.__create_description_markup(
|
|
1418
|
-
exc_type=
|
|
1419
|
-
exc_value=
|
|
1420
|
-
exc_traceback=
|
|
1467
|
+
exc_type=type(exception),
|
|
1468
|
+
exc_value=exception,
|
|
1469
|
+
exc_traceback=exception.__traceback__,
|
|
1421
1470
|
error_id=error_id,
|
|
1422
1471
|
)
|
|
1423
1472
|
|
|
@@ -1444,6 +1493,8 @@ class Jira:
|
|
|
1444
1493
|
|
|
1445
1494
|
Args:
|
|
1446
1495
|
exception (Exception): The exception to be added to the Jira issue.
|
|
1496
|
+
metadata (dict, optional): The metadata to be added to the Jira issue. Defaults to None.
|
|
1497
|
+
additional_info (str, optional): Additional information to be added to the Jira issue. Defaults to None.
|
|
1447
1498
|
|
|
1448
1499
|
Returns:
|
|
1449
1500
|
The response from creating the Jira issue.
|
|
@@ -1455,7 +1506,7 @@ class Jira:
|
|
|
1455
1506
|
attachments = [str(file) for file in Path().cwd().glob("stack_details_*.json")]
|
|
1456
1507
|
|
|
1457
1508
|
summary = self.__create_summary(type(exception), exception, exception.__traceback__)
|
|
1458
|
-
error_id = self.__generate_error_id(
|
|
1509
|
+
error_id = self.__generate_error_id(exception)
|
|
1459
1510
|
|
|
1460
1511
|
existing_ticket = self.filter_tickets(
|
|
1461
1512
|
all_tickets=self.get_issues(project_key=CONFIG.BC_BOARD)["issues"],
|
|
@@ -1623,31 +1674,47 @@ class Jira:
|
|
|
1623
1674
|
for ticket in all_tickets:
|
|
1624
1675
|
if not ticket["fields"]["description"]:
|
|
1625
1676
|
continue
|
|
1626
|
-
if error_id not in ticket["fields"]["description"]:
|
|
1677
|
+
if f"{error_id}_~" not in ticket["fields"]["description"]:
|
|
1627
1678
|
continue
|
|
1628
1679
|
return ticket
|
|
1629
1680
|
|
|
1630
1681
|
else:
|
|
1631
1682
|
return None
|
|
1632
1683
|
|
|
1633
|
-
def
|
|
1684
|
+
def generate_group_id(self, group_by: Optional[str] = None) -> Optional[str]:
|
|
1685
|
+
"""Generates a group ID based on the provided group_by string.
|
|
1686
|
+
|
|
1687
|
+
Args:
|
|
1688
|
+
group_by (str): The string to use for generating the group ID.
|
|
1689
|
+
|
|
1690
|
+
Returns:
|
|
1691
|
+
str or None: The generated group ID if group_by is not None, otherwise None.
|
|
1692
|
+
"""
|
|
1693
|
+
if not group_by:
|
|
1694
|
+
return None
|
|
1695
|
+
crc_hash = zlib.crc32(group_by.encode())
|
|
1696
|
+
return format(crc_hash, "x")
|
|
1697
|
+
|
|
1698
|
+
def __generate_error_id(self, exception: Exception) -> str:
|
|
1634
1699
|
"""Generates an error string ID using the exception, function name, and error string.
|
|
1635
1700
|
|
|
1636
1701
|
Args:
|
|
1637
|
-
|
|
1638
|
-
exc_traceback (TracebackType): The traceback of the exception.
|
|
1702
|
+
exception (Exception):
|
|
1639
1703
|
|
|
1640
1704
|
Returns:
|
|
1641
|
-
str: The generated error string ID.
|
|
1705
|
+
str: The generated error string ID and the group ID.
|
|
1642
1706
|
|
|
1643
1707
|
"""
|
|
1644
|
-
frames = get_frames(
|
|
1708
|
+
frames = get_frames(exception.__traceback__)
|
|
1645
1709
|
exception_chain = "-".join([f"{frame.name}" for frame in frames])
|
|
1646
1710
|
rel_path = os.path.relpath(frames[-1].filename, os.getcwd())
|
|
1647
1711
|
path = Path(os.path.splitext(rel_path)[0]).as_posix()
|
|
1648
1712
|
path = remove_holotree_id(path)
|
|
1649
|
-
error_id =
|
|
1650
|
-
|
|
1713
|
+
error_id = (
|
|
1714
|
+
f"{path}-{exception_chain}-{frames[-1].line}-" f"{type(exception).__module__}-{type(exception).__name__}"
|
|
1715
|
+
)
|
|
1716
|
+
hashed_id = hashlib.md5(error_id.encode()).hexdigest()
|
|
1717
|
+
return hashed_id
|
|
1651
1718
|
|
|
1652
1719
|
@staticmethod
|
|
1653
1720
|
def __generate_warning_id(inspected_frame) -> tuple:
|
|
@@ -1700,7 +1767,9 @@ class Jira:
|
|
|
1700
1767
|
return str(message)
|
|
1701
1768
|
return re.sub(r"\'(.+)\'", "'...'", message)
|
|
1702
1769
|
|
|
1703
|
-
def __create_summary(
|
|
1770
|
+
def __create_summary(
|
|
1771
|
+
self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, idx: Optional[int] = None
|
|
1772
|
+
) -> str:
|
|
1704
1773
|
"""Create the summary of the ticket.
|
|
1705
1774
|
|
|
1706
1775
|
Args:
|
|
@@ -1713,7 +1782,11 @@ class Jira:
|
|
|
1713
1782
|
"""
|
|
1714
1783
|
frames = get_frames(exc_traceback)
|
|
1715
1784
|
file_name, line_no, _, _ = frames[-1]
|
|
1716
|
-
|
|
1785
|
+
|
|
1786
|
+
if idx:
|
|
1787
|
+
summary = f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}({idx})]"
|
|
1788
|
+
else:
|
|
1789
|
+
summary = f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}]"
|
|
1717
1790
|
if self._project_key == CONFIG.SUPPORT_BOARD and CONFIG.ADMIN_CODE:
|
|
1718
1791
|
summary = CONFIG.ADMIN_CODE + " - " + summary
|
|
1719
1792
|
if CONFIG.LIMITS.SUMMARY_LENGTH <= len(summary):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|