t-bug-catcher 0.3.0__tar.gz → 0.4.0__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.3.0 → t_bug_catcher-0.4.0}/PKG-INFO +2 -1
- t_bug_catcher-0.3.0/t_bug_catcher.egg-info/requires.txt → t_bug_catcher-0.4.0/requirements.txt +1 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/setup.cfg +1 -1
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/setup.py +1 -1
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/__init__.py +1 -5
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/bug_catcher.py +8 -1
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/config.py +3 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/jira.py +88 -11
- t_bug_catcher-0.4.0/t_bug_catcher/stack_saver.py +186 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/PKG-INFO +2 -1
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/SOURCES.txt +1 -0
- t_bug_catcher-0.3.0/requirements.txt → t_bug_catcher-0.4.0/t_bug_catcher.egg-info/requires.txt +2 -1
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/MANIFEST.in +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/README.rst +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/pyproject.toml +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/bug_snag.py +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/utils/common.py +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.3.0 → t_bug_catcher-0.4.0}/tests/test_t_bug_catcher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: t_bug_catcher
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Bug catcher
|
|
5
5
|
Home-page: https://www.thoughtful.ai/
|
|
6
6
|
Author: Thoughtful
|
|
@@ -14,6 +14,7 @@ Requires-Python: >=3.9
|
|
|
14
14
|
Requires-Dist: requests<3.0.0,>=2.31.0
|
|
15
15
|
Requires-Dist: bugsnag>=4.6.1
|
|
16
16
|
Requires-Dist: retry~=0.9.2
|
|
17
|
+
Requires-Dist: whispers>=2.2.1
|
|
17
18
|
|
|
18
19
|
t-bug-catcher
|
|
19
20
|
==============
|
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
__author__ = """Thoughtful"""
|
|
4
4
|
__email__ = "support@thoughtful.ai"
|
|
5
5
|
# fmt: off
|
|
6
|
-
__version__ = '0.
|
|
6
|
+
__version__ = '0.4.0'
|
|
7
7
|
# fmt: on
|
|
8
8
|
|
|
9
9
|
from .bug_catcher import (
|
|
10
10
|
configure,
|
|
11
11
|
report_error,
|
|
12
|
-
report_error_to_jira,
|
|
13
|
-
report_error_to_bugsnag,
|
|
14
12
|
attach_file_to_exception,
|
|
15
13
|
install_sys_hook,
|
|
16
14
|
uninstall_sys_hook,
|
|
@@ -19,8 +17,6 @@ from .bug_catcher import (
|
|
|
19
17
|
__all__ = [
|
|
20
18
|
"configure",
|
|
21
19
|
"report_error",
|
|
22
|
-
"report_error_to_jira",
|
|
23
|
-
"report_error_to_bugsnag",
|
|
24
20
|
"attach_file_to_exception",
|
|
25
21
|
"install_sys_hook",
|
|
26
22
|
"uninstall_sys_hook",
|
|
@@ -8,6 +8,7 @@ from typing import List, Optional
|
|
|
8
8
|
from .bug_snag import BugSnag
|
|
9
9
|
from .config import CONFIG
|
|
10
10
|
from .jira import Jira
|
|
11
|
+
from .stack_saver import StackSaver
|
|
11
12
|
from .utils import logger
|
|
12
13
|
from .utils.common import get_frames
|
|
13
14
|
|
|
@@ -89,6 +90,7 @@ class BugCatcher:
|
|
|
89
90
|
self.__bug_snag: BugSnag = BugSnag()
|
|
90
91
|
self.__configurator: Configurator = Configurator(self.__jira, self.__bug_snag)
|
|
91
92
|
self.__sys_excepthook = None
|
|
93
|
+
self.__stack_saver = StackSaver()
|
|
92
94
|
|
|
93
95
|
@property
|
|
94
96
|
def configure(self):
|
|
@@ -131,11 +133,14 @@ class BugCatcher:
|
|
|
131
133
|
logger.warning(f"Exception {handled_error} already reported.")
|
|
132
134
|
return
|
|
133
135
|
|
|
136
|
+
stack_trace = self.__stack_saver.save_stack_trace(exception)
|
|
137
|
+
|
|
134
138
|
if self.__configurator.is_jira_configured:
|
|
135
139
|
self.__jira.report_error(
|
|
136
140
|
exception=exception,
|
|
137
141
|
assignee=assignee,
|
|
138
142
|
attachments=attachments,
|
|
143
|
+
stack_trace=stack_trace,
|
|
139
144
|
additional_info=description,
|
|
140
145
|
metadata=metadata,
|
|
141
146
|
)
|
|
@@ -243,8 +248,10 @@ class BugCatcher:
|
|
|
243
248
|
logger.warning(f"Exception {handled_error} already reported.")
|
|
244
249
|
return
|
|
245
250
|
|
|
251
|
+
stack_trace = self.__stack_saver.save_stack_trace(exc_value)
|
|
252
|
+
|
|
246
253
|
if self.__configurator.is_jira_configured:
|
|
247
|
-
self.__jira.report_unhandled_error(exc_type, exc_value, exc_traceback)
|
|
254
|
+
self.__jira.report_unhandled_error(exc_type, exc_value, exc_traceback, stack_trace)
|
|
248
255
|
if self.__configurator.is_bugsnag_configured:
|
|
249
256
|
self.__bug_snag.report_unhandled_error(exc_type, exc_value, exc_traceback)
|
|
250
257
|
|
|
@@ -12,6 +12,7 @@ class Config:
|
|
|
12
12
|
MAX_ATTACHMENTS: int = 5
|
|
13
13
|
MAX_ISSUE_ATTACHMENTS: int = 100
|
|
14
14
|
MAX_DESCRIPTION_LENGTH: int = 250
|
|
15
|
+
SUMMARY_LENGTH: int = 120
|
|
15
16
|
|
|
16
17
|
SUPPORT_BOARD = "AB"
|
|
17
18
|
|
|
@@ -19,6 +20,7 @@ class Config:
|
|
|
19
20
|
f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
|
|
20
21
|
f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
|
|
21
22
|
f"/{os.environ.get('RC_PROCESS_ID')}/runs/{os.environ.get('RC_PROCESS_RUN_ID')}/"
|
|
23
|
+
f"stepRuns/{os.environ.get('RC_ACTIVITY_RUN_ID')}/"
|
|
22
24
|
)
|
|
23
25
|
|
|
24
26
|
ENVIRONMENT = (
|
|
@@ -29,6 +31,7 @@ class Config:
|
|
|
29
31
|
|
|
30
32
|
STAGE = metadata.get("process", dict()).get("implementationStage", "")
|
|
31
33
|
ADMIN_CODE = metadata.get("process", dict()).get("adminCode", "")
|
|
34
|
+
WORKER_NAME = metadata.get("process", dict()).get("name", "")
|
|
32
35
|
EMPOWER_URL = metadata.get("process", dict()).get("processRunUrl") or variables.get("processRunUrl")
|
|
33
36
|
|
|
34
37
|
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import re
|
|
8
8
|
import sys
|
|
9
9
|
import traceback
|
|
10
|
+
from importlib.metadata import version
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from types import TracebackType
|
|
12
13
|
from typing import List, Optional, Union
|
|
@@ -442,6 +443,34 @@ class Jira:
|
|
|
442
443
|
}
|
|
443
444
|
]
|
|
444
445
|
|
|
446
|
+
@staticmethod
|
|
447
|
+
def __bot_name_markup() -> List[dict]:
|
|
448
|
+
"""Create the ai worker markup.
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
dict: The ai worker markup.
|
|
452
|
+
"""
|
|
453
|
+
return (
|
|
454
|
+
[
|
|
455
|
+
{
|
|
456
|
+
"type": "paragraph",
|
|
457
|
+
"content": [
|
|
458
|
+
{
|
|
459
|
+
"type": "text",
|
|
460
|
+
"text": "Process name: ",
|
|
461
|
+
"marks": [{"type": "strong"}],
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"type": "text",
|
|
465
|
+
"text": f"{CONFIG.ADMIN_CODE} - {CONFIG.WORKER_NAME}",
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
if CONFIG.ADMIN_CODE and CONFIG.WORKER_NAME
|
|
471
|
+
else []
|
|
472
|
+
)
|
|
473
|
+
|
|
445
474
|
@staticmethod
|
|
446
475
|
def __traceback_markup(exc_traceback_info: str) -> List[dict]:
|
|
447
476
|
"""Create the traceback markup.
|
|
@@ -522,6 +551,14 @@ class Jira:
|
|
|
522
551
|
{"type": "subsup", "attrs": {"type": "sub"}},
|
|
523
552
|
],
|
|
524
553
|
},
|
|
554
|
+
{
|
|
555
|
+
"type": "text",
|
|
556
|
+
"text": f" (v{version('t_bug_catcher')})",
|
|
557
|
+
"marks": [
|
|
558
|
+
{"type": "em"},
|
|
559
|
+
{"type": "subsup", "attrs": {"type": "sub"}},
|
|
560
|
+
],
|
|
561
|
+
},
|
|
525
562
|
],
|
|
526
563
|
},
|
|
527
564
|
]
|
|
@@ -540,7 +577,26 @@ class Jira:
|
|
|
540
577
|
},
|
|
541
578
|
{"type": "underline"},
|
|
542
579
|
],
|
|
543
|
-
}
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
"type": "text",
|
|
583
|
+
"text": " [Robocloud ",
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
"type": "text",
|
|
587
|
+
"text": "link",
|
|
588
|
+
"marks": [
|
|
589
|
+
{
|
|
590
|
+
"type": "link",
|
|
591
|
+
"attrs": {"href": CONFIG.RC_RUN_LINK},
|
|
592
|
+
},
|
|
593
|
+
{"type": "underline"},
|
|
594
|
+
],
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
"type": "text",
|
|
598
|
+
"text": "]",
|
|
599
|
+
},
|
|
544
600
|
],
|
|
545
601
|
"robocloud": [
|
|
546
602
|
{
|
|
@@ -601,6 +657,7 @@ class Jira:
|
|
|
601
657
|
"type": "doc",
|
|
602
658
|
"content": []
|
|
603
659
|
+ (self.__error_string_markup(error_string, exc_info) if error_string else [])
|
|
660
|
+
+ self.__bot_name_markup()
|
|
604
661
|
+ self.__date_markup()
|
|
605
662
|
+ self.__runlink_markup()
|
|
606
663
|
+ self.__environment_markup()
|
|
@@ -902,6 +959,7 @@ class Jira:
|
|
|
902
959
|
exception: Optional[Exception] = None,
|
|
903
960
|
assignee: Optional[str] = None,
|
|
904
961
|
attachments: Union[List, str, Path, None] = None,
|
|
962
|
+
stack_trace: Optional[str] = None,
|
|
905
963
|
metadata: Optional[dict] = None,
|
|
906
964
|
additional_info: Optional[str] = None,
|
|
907
965
|
) -> dict:
|
|
@@ -911,6 +969,7 @@ class Jira:
|
|
|
911
969
|
exception (Exception, optional): The exception to be added to the Jira issue.
|
|
912
970
|
assignee (str, optional): The assignee to be added to the Jira issue.
|
|
913
971
|
attachments (List, optional): List of attachments to be added to the Jira issue.
|
|
972
|
+
stack_trace (str, optional): Stack trace to be added to the Jira issue.
|
|
914
973
|
metadata (dict, optional): Metadata to be added to the Jira issue.
|
|
915
974
|
additional_info (str, optional): Additional information to be added to the Jira issue.
|
|
916
975
|
|
|
@@ -955,6 +1014,9 @@ class Jira:
|
|
|
955
1014
|
)
|
|
956
1015
|
return existing_ticket
|
|
957
1016
|
|
|
1017
|
+
if stack_trace:
|
|
1018
|
+
attachments.insert(0, stack_trace)
|
|
1019
|
+
|
|
958
1020
|
assignee_id = None
|
|
959
1021
|
assignee = assignee if assignee else self._default_assignee
|
|
960
1022
|
if assignee:
|
|
@@ -979,19 +1041,23 @@ class Jira:
|
|
|
979
1041
|
attachments=attachments,
|
|
980
1042
|
labels=["bug_catcher"],
|
|
981
1043
|
)
|
|
1044
|
+
if os.path.exists(stack_trace):
|
|
1045
|
+
os.remove(stack_trace)
|
|
982
1046
|
return response
|
|
983
1047
|
except Exception as ex:
|
|
984
1048
|
logger.warning(f"Failed to create Jira issue due to: {ex.__class__.__name__}: {ex}")
|
|
985
1049
|
return False
|
|
986
1050
|
|
|
987
|
-
def report_unhandled_error(
|
|
1051
|
+
def report_unhandled_error(
|
|
1052
|
+
self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, stack_trace: str = None
|
|
1053
|
+
):
|
|
988
1054
|
"""Report an unhandled error to Jira.
|
|
989
1055
|
|
|
990
1056
|
Args:
|
|
991
1057
|
exc_type (type): The type of the exception.
|
|
992
1058
|
exc_value (Exception, str): The value of the exception.
|
|
993
1059
|
exc_traceback (TracebackType): The traceback of the exception.
|
|
994
|
-
|
|
1060
|
+
stack_trace (str, optional): Stack trace to be added to the Jira issue.
|
|
995
1061
|
|
|
996
1062
|
Returns:
|
|
997
1063
|
The response from creating the Jira issue.
|
|
@@ -1029,8 +1095,11 @@ class Jira:
|
|
|
1029
1095
|
summary=summary,
|
|
1030
1096
|
description=description,
|
|
1031
1097
|
assignee_id=assignee_id,
|
|
1098
|
+
attachments=[stack_trace] if stack_trace else None,
|
|
1032
1099
|
labels=["bug_catcher", "fatal_error"],
|
|
1033
1100
|
)
|
|
1101
|
+
if os.path.exists(stack_trace):
|
|
1102
|
+
os.remove(stack_trace)
|
|
1034
1103
|
return response
|
|
1035
1104
|
except Exception as ex:
|
|
1036
1105
|
logger.warning(f"Failed to create Jira issue due to: {ex.__class__.__name__}: {ex}")
|
|
@@ -1204,7 +1273,7 @@ class Jira:
|
|
|
1204
1273
|
return response[0]["accountId"]
|
|
1205
1274
|
|
|
1206
1275
|
@staticmethod
|
|
1207
|
-
def
|
|
1276
|
+
def sanitize_summary(exception: Union[Exception, str]) -> str:
|
|
1208
1277
|
"""Remove locators from the exception.
|
|
1209
1278
|
|
|
1210
1279
|
Args:
|
|
@@ -1213,9 +1282,11 @@ class Jira:
|
|
|
1213
1282
|
Returns:
|
|
1214
1283
|
str: The cleaned exception string.
|
|
1215
1284
|
"""
|
|
1285
|
+
message = re.sub("<([a-z]+)(?![^>]*\/>)[^>]*>", r"<\1>", str(exception))
|
|
1286
|
+
message = re.sub(">([^<]+)<\/", ">...</", message)
|
|
1216
1287
|
if "selenium" not in exception.__class__.__name__.lower() and not isinstance(exception, AssertionError):
|
|
1217
|
-
return str(
|
|
1218
|
-
return re.sub(r"\'(.+)\'", "'...'",
|
|
1288
|
+
return str(message)
|
|
1289
|
+
return re.sub(r"\'(.+)\'", "'...'", message)
|
|
1219
1290
|
|
|
1220
1291
|
def __create_summary(self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType) -> str:
|
|
1221
1292
|
"""Create the summary of the ticket.
|
|
@@ -1230,10 +1301,16 @@ class Jira:
|
|
|
1230
1301
|
"""
|
|
1231
1302
|
frames = get_frames(exc_traceback)
|
|
1232
1303
|
file_name, line_no, _, _ = frames[-1]
|
|
1233
|
-
summary = (
|
|
1234
|
-
f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}] "
|
|
1235
|
-
f"{self.remove_locators_from_exception(exc_value)}"
|
|
1236
|
-
)
|
|
1304
|
+
summary = f"[{exc_type.__name__}:{os.path.basename(file_name)}:{line_no}]"
|
|
1237
1305
|
if self._project_key == CONFIG.SUPPORT_BOARD and CONFIG.ADMIN_CODE:
|
|
1238
1306
|
summary = CONFIG.ADMIN_CODE + " - " + summary
|
|
1239
|
-
|
|
1307
|
+
if CONFIG.LIMITS.SUMMARY_LENGTH <= len(summary):
|
|
1308
|
+
return summary
|
|
1309
|
+
else:
|
|
1310
|
+
message = self.sanitize_summary(exc_value)
|
|
1311
|
+
message = (
|
|
1312
|
+
message
|
|
1313
|
+
if len(message) <= CONFIG.LIMITS.SUMMARY_LENGTH - len(summary)
|
|
1314
|
+
else message[: CONFIG.LIMITS.SUMMARY_LENGTH - len(summary)] + "..."
|
|
1315
|
+
)
|
|
1316
|
+
return summary + " " + message
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from json import JSONEncoder
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import FunctionType, ModuleType
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import whispers
|
|
13
|
+
|
|
14
|
+
from .utils import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _Encoder(JSONEncoder):
|
|
18
|
+
"""Encoder class for encoding the Episode object to json."""
|
|
19
|
+
|
|
20
|
+
def default(self, o):
|
|
21
|
+
"""This method is used to encode the Episode object to json.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
o (object): The object to be encoded.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
str: The json string.
|
|
28
|
+
"""
|
|
29
|
+
if hasattr(o, "__dict__"):
|
|
30
|
+
return o.__dict__
|
|
31
|
+
if isinstance(o, datetime):
|
|
32
|
+
return o.isoformat()
|
|
33
|
+
if isinstance(o, Path):
|
|
34
|
+
return str(o)
|
|
35
|
+
return JSONEncoder.default(self, o)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StackSaver:
|
|
39
|
+
"""A class to save the stack trace."""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
"""Initializes the StackSaver class."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def strip_path(path: str):
|
|
47
|
+
"""A static method to strip the current working directory path from the input.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
path (str): The path from which to strip the current working directory path.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
str: The stripped path.
|
|
54
|
+
"""
|
|
55
|
+
return path.replace(os.getcwd(), "").strip(os.sep)
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def serialize_frame_info(frame_info: dict) -> dict:
|
|
59
|
+
"""A static method to serialize the frame info.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
frame_info (dict): The frame info to be serialized.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
dict: The serialized frame info.
|
|
66
|
+
"""
|
|
67
|
+
run_locals = {}
|
|
68
|
+
run_args = {}
|
|
69
|
+
if frame_info["locals"]:
|
|
70
|
+
for key, value in frame_info["locals"].items():
|
|
71
|
+
if isinstance(value, dict):
|
|
72
|
+
run_locals[str(key)] = value
|
|
73
|
+
else:
|
|
74
|
+
run_locals[str(key)] = str(value)
|
|
75
|
+
for key, value in frame_info["args"].items():
|
|
76
|
+
if isinstance(value, dict):
|
|
77
|
+
run_args[str(key)] = value
|
|
78
|
+
else:
|
|
79
|
+
run_args[str(key)] = str(value)
|
|
80
|
+
serializable_frame_info = {
|
|
81
|
+
"filename": frame_info["filename"],
|
|
82
|
+
"function_name": frame_info["function_name"],
|
|
83
|
+
"locals": run_locals,
|
|
84
|
+
"args": run_args,
|
|
85
|
+
}
|
|
86
|
+
return serializable_frame_info
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def filter_variables(variables: dict) -> dict:
|
|
90
|
+
"""A static method to filter the variables.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
variables (dict): The variables to be filtered.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
dict: The filtered variables.
|
|
97
|
+
"""
|
|
98
|
+
if not isinstance(variables, dict):
|
|
99
|
+
return variables
|
|
100
|
+
else:
|
|
101
|
+
local_variables = {}
|
|
102
|
+
for var_name, var in variables.items():
|
|
103
|
+
if re.match(r"^__\w+__$", var_name):
|
|
104
|
+
continue
|
|
105
|
+
if isinstance(var, (ModuleType, FunctionType)):
|
|
106
|
+
continue
|
|
107
|
+
local_variables[var_name] = var
|
|
108
|
+
return local_variables
|
|
109
|
+
|
|
110
|
+
def mask_credentials(self, file_path: str) -> None:
|
|
111
|
+
"""A method to mask the credentials in the file.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
file_path (str): The path of the file to be masked.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
Exception: If the masking fails.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
None
|
|
121
|
+
"""
|
|
122
|
+
with open(file_path, "r") as f:
|
|
123
|
+
filedata = f.readlines()
|
|
124
|
+
|
|
125
|
+
secrets = [secret for secret in whispers.secrets(file_path)]
|
|
126
|
+
|
|
127
|
+
for index, line in enumerate(filedata):
|
|
128
|
+
if not secrets:
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
for secret in secrets:
|
|
132
|
+
if secret.key in line and secret.value in line:
|
|
133
|
+
filedata[index] = line.replace(secret.value, secret.value[:1] + "***")
|
|
134
|
+
secrets.pop(secrets.index(secret))
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
if secrets:
|
|
138
|
+
logger.warning("Failed to mask credentials")
|
|
139
|
+
os.remove(file_path)
|
|
140
|
+
raise Exception("Failed to mask credentials")
|
|
141
|
+
|
|
142
|
+
with open(file_path, "w") as file:
|
|
143
|
+
file.writelines(filedata)
|
|
144
|
+
|
|
145
|
+
def save_stack_trace(self, exception: Optional[Exception] = None):
|
|
146
|
+
"""A method to save the stack trace.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
exception (Exception, optional): The exception to be saved. Defaults to None.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Optional[str]: The path of the saved stack trace.
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
frames = []
|
|
156
|
+
stack_details_json = []
|
|
157
|
+
tb = exception.__traceback__ if exception else sys.exc_info()[2]
|
|
158
|
+
while tb is not None:
|
|
159
|
+
frame = tb.tb_frame
|
|
160
|
+
if "site-packages" in frame.f_code.co_filename:
|
|
161
|
+
tb = tb.tb_next
|
|
162
|
+
continue
|
|
163
|
+
frames.append(frame)
|
|
164
|
+
tb = tb.tb_next
|
|
165
|
+
frames = frames[:3]
|
|
166
|
+
|
|
167
|
+
for frame in frames:
|
|
168
|
+
frame_info = {
|
|
169
|
+
"filename": self.strip_path(frame.f_code.co_filename),
|
|
170
|
+
"function_name": frame.f_code.co_name,
|
|
171
|
+
"locals": self.filter_variables(frame.f_locals),
|
|
172
|
+
"args": self.filter_variables(inspect.getargvalues(frame)[3]),
|
|
173
|
+
}
|
|
174
|
+
stack_details_json.append(self.serialize_frame_info(frame_info))
|
|
175
|
+
|
|
176
|
+
file_path = f"stack_details_{datetime.now().strftime('%Y%m%d%H%M%S')}.json"
|
|
177
|
+
|
|
178
|
+
with open(file_path, "w") as f:
|
|
179
|
+
json.dump(stack_details_json, f, indent=4, cls=_Encoder)
|
|
180
|
+
|
|
181
|
+
self.mask_credentials(file_path)
|
|
182
|
+
|
|
183
|
+
return file_path
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.warning(f"Failed to save stack trace: {e}")
|
|
186
|
+
return
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: t_bug_catcher
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Bug catcher
|
|
5
5
|
Home-page: https://www.thoughtful.ai/
|
|
6
6
|
Author: Thoughtful
|
|
@@ -14,6 +14,7 @@ Requires-Python: >=3.9
|
|
|
14
14
|
Requires-Dist: requests<3.0.0,>=2.31.0
|
|
15
15
|
Requires-Dist: bugsnag>=4.6.1
|
|
16
16
|
Requires-Dist: retry~=0.9.2
|
|
17
|
+
Requires-Dist: whispers>=2.2.1
|
|
17
18
|
|
|
18
19
|
t-bug-catcher
|
|
19
20
|
==============
|
|
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
|