t-bug-catcher 0.4.1__tar.gz → 0.4.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.4.1 → t_bug_catcher-0.4.3}/PKG-INFO +1 -1
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/setup.cfg +1 -1
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/setup.py +1 -1
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/__init__.py +1 -1
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/config.py +11 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/jira.py +14 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/stack_saver.py +20 -33
- t_bug_catcher-0.4.3/t_bug_catcher/utils/common.py +69 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- t_bug_catcher-0.4.1/t_bug_catcher/utils/common.py +0 -41
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/MANIFEST.in +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/README.rst +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/pyproject.toml +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/requirements.txt +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/bug_catcher.py +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/bug_snag.py +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/resources/whispers_config.yml +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/SOURCES.txt +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.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.4.
|
|
29
|
+
version="0.4.3",
|
|
30
30
|
zip_safe=False,
|
|
31
31
|
install_requires=install_requirements,
|
|
32
32
|
include_package_data=True,
|
|
@@ -15,8 +15,19 @@ class Config:
|
|
|
15
15
|
SUMMARY_LENGTH: int = 120
|
|
16
16
|
STACK_SCOPE: int = 3
|
|
17
17
|
|
|
18
|
+
class TICKET_PRIORITIES:
|
|
19
|
+
"""Priorities class for configuring the application."""
|
|
20
|
+
|
|
21
|
+
HIGHEST: str = "1"
|
|
22
|
+
HIGH: str = "2"
|
|
23
|
+
MEDIUM: str = "3"
|
|
24
|
+
LOW: str = "4"
|
|
25
|
+
LOWEST: str = "5"
|
|
26
|
+
|
|
18
27
|
SUPPORT_BOARD = "AB"
|
|
19
28
|
|
|
29
|
+
KEYS_TO_REMOVE = ["credential", "password"]
|
|
30
|
+
|
|
20
31
|
RC_RUN_LINK = (
|
|
21
32
|
f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
|
|
22
33
|
f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
|
|
@@ -209,6 +209,7 @@ class Jira:
|
|
|
209
209
|
issue_type: str,
|
|
210
210
|
assignee: Optional[str] = None,
|
|
211
211
|
labels: Optional[list] = None,
|
|
212
|
+
priority: Optional[str] = None,
|
|
212
213
|
) -> str:
|
|
213
214
|
"""Generates the issue body payload for creating a new issue.
|
|
214
215
|
|
|
@@ -218,6 +219,7 @@ class Jira:
|
|
|
218
219
|
assignee (str): The assignee of the issue.
|
|
219
220
|
issue_type (str): The type of the issue.
|
|
220
221
|
labels (list, optional): The labels of the issue. Defaults to None.
|
|
222
|
+
priority (str, optional): The priority of the issue. Defaults to None.
|
|
221
223
|
|
|
222
224
|
Returns:
|
|
223
225
|
The JSON payload for creating a new issue.
|
|
@@ -235,6 +237,8 @@ class Jira:
|
|
|
235
237
|
fields["fields"]["labels"] = labels
|
|
236
238
|
if self._project_key == CONFIG.SUPPORT_BOARD and CONFIG.ADMIN_CODE:
|
|
237
239
|
fields["fields"]["customfield_10077"] = [CONFIG.ADMIN_CODE]
|
|
240
|
+
if priority:
|
|
241
|
+
fields["fields"]["priority"] = {"id": priority}
|
|
238
242
|
payload = json.dumps(fields)
|
|
239
243
|
return payload
|
|
240
244
|
|
|
@@ -890,6 +894,7 @@ class Jira:
|
|
|
890
894
|
assignee_id: Optional[str] = None,
|
|
891
895
|
attachments: Optional[List] = None,
|
|
892
896
|
labels: Optional[list] = None,
|
|
897
|
+
priority: Optional[str] = None,
|
|
893
898
|
) -> requests.Response:
|
|
894
899
|
"""Create a new ticket.
|
|
895
900
|
|
|
@@ -899,6 +904,7 @@ class Jira:
|
|
|
899
904
|
assignee_id (str, optional): The assignee of the ticket. Defaults to None.
|
|
900
905
|
attachments (List, optional): The list of attachments. Defaults to None.
|
|
901
906
|
labels (List, optional): The list of labels. Defaults to None.
|
|
907
|
+
priority (str, optional): The priority of the ticket. Defaults to None.
|
|
902
908
|
|
|
903
909
|
Returns:
|
|
904
910
|
The response from creating the ticket.
|
|
@@ -923,6 +929,7 @@ class Jira:
|
|
|
923
929
|
assignee=assignee_id,
|
|
924
930
|
issue_type=issue_type,
|
|
925
931
|
labels=labels,
|
|
932
|
+
priority=priority,
|
|
926
933
|
)
|
|
927
934
|
response = self.post_ticket(issue=issue)
|
|
928
935
|
|
|
@@ -932,6 +939,7 @@ class Jira:
|
|
|
932
939
|
description=description,
|
|
933
940
|
assignee=assignee_id,
|
|
934
941
|
issue_type=issue_type,
|
|
942
|
+
priority=priority,
|
|
935
943
|
)
|
|
936
944
|
response = self.post_ticket(issue=issue)
|
|
937
945
|
|
|
@@ -1034,12 +1042,15 @@ class Jira:
|
|
|
1034
1042
|
metadata=metadata,
|
|
1035
1043
|
)
|
|
1036
1044
|
|
|
1045
|
+
priority = CONFIG.TICKET_PRIORITIES.HIGH if CONFIG.STAGE.lower() == "hypercare" else None
|
|
1046
|
+
|
|
1037
1047
|
response = self.__create_new_ticket(
|
|
1038
1048
|
summary=summary,
|
|
1039
1049
|
description=description,
|
|
1040
1050
|
assignee_id=assignee_id,
|
|
1041
1051
|
attachments=attachments,
|
|
1042
1052
|
labels=["bug_catcher"],
|
|
1053
|
+
priority=priority,
|
|
1043
1054
|
)
|
|
1044
1055
|
if os.path.exists(stack_trace):
|
|
1045
1056
|
os.remove(stack_trace)
|
|
@@ -1091,12 +1102,15 @@ class Jira:
|
|
|
1091
1102
|
error_id=error_id,
|
|
1092
1103
|
)
|
|
1093
1104
|
|
|
1105
|
+
priority = CONFIG.TICKET_PRIORITIES.HIGHEST if CONFIG.STAGE.lower() == "hypercare" else None
|
|
1106
|
+
|
|
1094
1107
|
response = self.__create_new_ticket(
|
|
1095
1108
|
summary=summary,
|
|
1096
1109
|
description=description,
|
|
1097
1110
|
assignee_id=assignee_id,
|
|
1098
1111
|
attachments=[stack_trace] if stack_trace else None,
|
|
1099
1112
|
labels=["bug_catcher", "fatal_error"],
|
|
1113
|
+
priority=priority,
|
|
1100
1114
|
)
|
|
1101
1115
|
if os.path.exists(stack_trace):
|
|
1102
1116
|
os.remove(stack_trace)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import json
|
|
3
|
+
import linecache
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
5
6
|
import sys
|
|
@@ -12,7 +13,7 @@ import whispers
|
|
|
12
13
|
|
|
13
14
|
from .config import CONFIG
|
|
14
15
|
from .utils import logger
|
|
15
|
-
from .utils.common import Encoder
|
|
16
|
+
from .utils.common import Encoder, convert_keys_to_primitives
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class StackSaver:
|
|
@@ -34,8 +35,7 @@ class StackSaver:
|
|
|
34
35
|
"""
|
|
35
36
|
return path.replace(os.getcwd(), "").strip(os.sep)
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
def serialize_frame_info(frame_info: dict) -> dict:
|
|
38
|
+
def serialize_frame_info(self, frame_info: dict) -> dict:
|
|
39
39
|
"""A static method to serialize the frame info.
|
|
40
40
|
|
|
41
41
|
Args:
|
|
@@ -44,26 +44,13 @@ class StackSaver:
|
|
|
44
44
|
Returns:
|
|
45
45
|
dict: The serialized frame info.
|
|
46
46
|
"""
|
|
47
|
-
run_locals = {}
|
|
48
|
-
run_args = {}
|
|
49
|
-
if frame_info["locals"]:
|
|
50
|
-
for key, value in frame_info["locals"].items():
|
|
51
|
-
if str(key).lower() == "credentials":
|
|
52
|
-
continue
|
|
53
|
-
if isinstance(value, dict):
|
|
54
|
-
run_locals[str(key)] = value
|
|
55
|
-
else:
|
|
56
|
-
run_locals[str(key)] = str(value)
|
|
57
|
-
for key, value in frame_info["args"].items():
|
|
58
|
-
if str(key).lower() == "credentials":
|
|
59
|
-
continue
|
|
60
|
-
if isinstance(value, dict):
|
|
61
|
-
run_args[str(key)] = value
|
|
62
|
-
else:
|
|
63
|
-
run_args[str(key)] = str(value)
|
|
47
|
+
run_locals = convert_keys_to_primitives(frame_info["locals"]) if frame_info["locals"] else {}
|
|
48
|
+
run_args = convert_keys_to_primitives(frame_info["args"]) if frame_info["args"] else {}
|
|
64
49
|
serializable_frame_info = {
|
|
65
50
|
"filename": frame_info["filename"],
|
|
66
51
|
"function_name": frame_info["function_name"],
|
|
52
|
+
"line_number": frame_info["line_number"],
|
|
53
|
+
"line": frame_info["line"],
|
|
67
54
|
"locals": run_locals,
|
|
68
55
|
"args": run_args,
|
|
69
56
|
}
|
|
@@ -88,7 +75,7 @@ class StackSaver:
|
|
|
88
75
|
continue
|
|
89
76
|
if isinstance(var, (ModuleType, FunctionType)):
|
|
90
77
|
continue
|
|
91
|
-
local_variables[var_name] = var
|
|
78
|
+
local_variables[str(var_name)] = var
|
|
92
79
|
return local_variables
|
|
93
80
|
|
|
94
81
|
def mask_credentials(self, file_path: str) -> None:
|
|
@@ -111,25 +98,18 @@ class StackSaver:
|
|
|
111
98
|
args = f"-c {config_path} {file_path}"
|
|
112
99
|
|
|
113
100
|
secrets = [secret for secret in whispers.secrets(args)]
|
|
114
|
-
unique_secrets = []
|
|
115
|
-
seen = set()
|
|
116
|
-
for secret in secrets:
|
|
117
|
-
item = (secret.key, secret.value, secret.line)
|
|
118
|
-
if item not in seen:
|
|
119
|
-
seen.add(item)
|
|
120
|
-
unique_secrets.append(secret)
|
|
121
101
|
|
|
122
102
|
for index, line in enumerate(filedata):
|
|
123
|
-
if not
|
|
103
|
+
if not secrets:
|
|
124
104
|
break
|
|
125
105
|
|
|
126
|
-
for secret in
|
|
106
|
+
for secret in secrets:
|
|
127
107
|
if secret.key in line and secret.value in line:
|
|
128
108
|
filedata[index] = line.replace(secret.value, secret.value[:1] + "***")
|
|
129
|
-
|
|
109
|
+
secrets.pop(secrets.index(secret))
|
|
130
110
|
break
|
|
131
111
|
|
|
132
|
-
if
|
|
112
|
+
if secrets:
|
|
133
113
|
logger.warning("Failed to mask credentials")
|
|
134
114
|
os.remove(file_path)
|
|
135
115
|
raise Exception("Failed to mask credentials")
|
|
@@ -149,20 +129,27 @@ class StackSaver:
|
|
|
149
129
|
try:
|
|
150
130
|
frames = []
|
|
151
131
|
stack_details_json = []
|
|
132
|
+
seen_frames = set()
|
|
152
133
|
tb = exception.__traceback__ if exception else sys.exc_info()[2]
|
|
153
134
|
while tb is not None:
|
|
154
135
|
frame = tb.tb_frame
|
|
136
|
+
if frame.f_code.co_name in seen_frames:
|
|
137
|
+
tb = tb.tb_next
|
|
138
|
+
continue
|
|
139
|
+
seen_frames.add(frame.f_code.co_name)
|
|
155
140
|
if "site-packages" in frame.f_code.co_filename:
|
|
156
141
|
tb = tb.tb_next
|
|
157
142
|
continue
|
|
158
143
|
frames.append(frame)
|
|
159
144
|
tb = tb.tb_next
|
|
160
|
-
frames = frames[
|
|
145
|
+
frames = frames[-CONFIG.LIMITS.STACK_SCOPE :]
|
|
161
146
|
|
|
162
147
|
for frame in frames:
|
|
163
148
|
frame_info = {
|
|
164
149
|
"filename": self.strip_path(frame.f_code.co_filename),
|
|
165
150
|
"function_name": frame.f_code.co_name,
|
|
151
|
+
"line_number": frame.f_lineno,
|
|
152
|
+
"line": linecache.getline(frame.f_code.co_filename, frame.f_lineno).strip(),
|
|
166
153
|
"locals": self.filter_variables(frame.f_locals),
|
|
167
154
|
"args": self.filter_variables(inspect.getargvalues(frame)[3]),
|
|
168
155
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
from json import JSONEncoder
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from ..config import CONFIG
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Encoder(JSONEncoder):
|
|
12
|
+
"""This class is used to encode the Episode object to json."""
|
|
13
|
+
|
|
14
|
+
def default(self, o):
|
|
15
|
+
"""This method is used to encode the Episode object to json.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
o (object): The object to be encoded.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
str: The json string.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if hasattr(o, "__dict__"):
|
|
25
|
+
keys_to_remove = [
|
|
26
|
+
key for key in o.__dict__.keys() if any(s in str(key).lower() for s in CONFIG.KEYS_TO_REMOVE)
|
|
27
|
+
]
|
|
28
|
+
for key in keys_to_remove:
|
|
29
|
+
del o.__dict__[key]
|
|
30
|
+
return {str(key): str(value) for key, value in o.__dict__.items()}
|
|
31
|
+
if isinstance(o, (datetime, date)):
|
|
32
|
+
return o.isoformat()
|
|
33
|
+
if isinstance(o, Path):
|
|
34
|
+
return str(o)
|
|
35
|
+
return super().default(self, o)
|
|
36
|
+
except TypeError:
|
|
37
|
+
return str(o)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_frames(exc_traceback: TracebackType) -> List:
|
|
41
|
+
"""Get the frames of the exception.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
exc_traceback (TracebackType): The traceback of the exception.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
List: The frames of the exception.
|
|
48
|
+
"""
|
|
49
|
+
return [
|
|
50
|
+
frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def convert_keys_to_primitives(data: dict) -> dict:
|
|
55
|
+
"""A function that recursively converts keys in a nested dictionary to primitives.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
data (dict): The input dictionary to convert keys.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: A new dictionary with keys converted to strings.
|
|
62
|
+
"""
|
|
63
|
+
new_dict = {}
|
|
64
|
+
for key, value in data.items():
|
|
65
|
+
if isinstance(value, dict):
|
|
66
|
+
new_dict[str(key)] = convert_keys_to_primitives(value)
|
|
67
|
+
else:
|
|
68
|
+
new_dict[str(key)] = value
|
|
69
|
+
return new_dict
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import traceback
|
|
2
|
-
from datetime import date, datetime
|
|
3
|
-
from json import JSONEncoder
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from types import TracebackType
|
|
6
|
-
from typing import List
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Encoder(JSONEncoder):
|
|
10
|
-
"""This class is used to encode the Episode object to json."""
|
|
11
|
-
|
|
12
|
-
def default(self, o):
|
|
13
|
-
"""This method is used to encode the Episode object to json.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
o (object): The object to be encoded.
|
|
17
|
-
|
|
18
|
-
Returns:
|
|
19
|
-
str: The json string.
|
|
20
|
-
"""
|
|
21
|
-
if hasattr(o, "__dict__"):
|
|
22
|
-
return o.__dict__
|
|
23
|
-
if isinstance(o, (datetime, date)):
|
|
24
|
-
return o.isoformat()
|
|
25
|
-
if isinstance(o, Path):
|
|
26
|
-
return str(o)
|
|
27
|
-
return JSONEncoder.default(self, o)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def get_frames(exc_traceback: TracebackType) -> List:
|
|
31
|
-
"""Get the frames of the exception.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
exc_traceback (TracebackType): The traceback of the exception.
|
|
35
|
-
|
|
36
|
-
Returns:
|
|
37
|
-
List: The frames of the exception.
|
|
38
|
-
"""
|
|
39
|
-
return [
|
|
40
|
-
frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
|
|
41
|
-
]
|
|
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
|