t-bug-catcher 0.3.1__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.
Files changed (25) hide show
  1. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/PKG-INFO +2 -1
  2. t_bug_catcher-0.3.1/t_bug_catcher.egg-info/requires.txt → t_bug_catcher-0.4.0/requirements.txt +1 -0
  3. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/setup.cfg +1 -1
  4. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/setup.py +1 -1
  5. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/__init__.py +1 -5
  6. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/bug_catcher.py +8 -1
  7. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/config.py +1 -0
  8. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/jira.py +14 -2
  9. t_bug_catcher-0.4.0/t_bug_catcher/stack_saver.py +186 -0
  10. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/PKG-INFO +2 -1
  11. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/SOURCES.txt +1 -0
  12. t_bug_catcher-0.3.1/requirements.txt → t_bug_catcher-0.4.0/t_bug_catcher.egg-info/requires.txt +2 -1
  13. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/MANIFEST.in +0 -0
  14. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/README.rst +0 -0
  15. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/pyproject.toml +0 -0
  16. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/bug_snag.py +0 -0
  17. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/exceptions.py +0 -0
  18. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/utils/__init__.py +0 -0
  19. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/utils/common.py +0 -0
  20. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/utils/logger.py +0 -0
  21. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher/workitems.py +0 -0
  22. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
  23. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/not-zip-safe +0 -0
  24. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.0}/t_bug_catcher.egg-info/top_level.txt +0 -0
  25. {t_bug_catcher-0.3.1 → 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.1
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
  ==============
@@ -1,3 +1,4 @@
1
1
  requests<3.0.0,>=2.31.0
2
2
  bugsnag>=4.6.1
3
3
  retry~=0.9.2
4
+ whispers>=2.2.1
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.3.1
2
+ current_version = 0.4.0
3
3
  commit = True
4
4
  tag = False
5
5
 
@@ -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.3.1",
29
+ version="0.4.0",
30
30
  zip_safe=False,
31
31
  install_requires=install_requirements,
32
32
  )
@@ -3,14 +3,12 @@
3
3
  __author__ = """Thoughtful"""
4
4
  __email__ = "support@thoughtful.ai"
5
5
  # fmt: off
6
- __version__ = '0.3.1'
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
 
@@ -20,6 +20,7 @@ class Config:
20
20
  f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
21
21
  f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
22
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')}/"
23
24
  )
24
25
 
25
26
  ENVIRONMENT = (
@@ -959,6 +959,7 @@ class Jira:
959
959
  exception: Optional[Exception] = None,
960
960
  assignee: Optional[str] = None,
961
961
  attachments: Union[List, str, Path, None] = None,
962
+ stack_trace: Optional[str] = None,
962
963
  metadata: Optional[dict] = None,
963
964
  additional_info: Optional[str] = None,
964
965
  ) -> dict:
@@ -968,6 +969,7 @@ class Jira:
968
969
  exception (Exception, optional): The exception to be added to the Jira issue.
969
970
  assignee (str, optional): The assignee to be added to the Jira issue.
970
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.
971
973
  metadata (dict, optional): Metadata to be added to the Jira issue.
972
974
  additional_info (str, optional): Additional information to be added to the Jira issue.
973
975
 
@@ -1012,6 +1014,9 @@ class Jira:
1012
1014
  )
1013
1015
  return existing_ticket
1014
1016
 
1017
+ if stack_trace:
1018
+ attachments.insert(0, stack_trace)
1019
+
1015
1020
  assignee_id = None
1016
1021
  assignee = assignee if assignee else self._default_assignee
1017
1022
  if assignee:
@@ -1036,19 +1041,23 @@ class Jira:
1036
1041
  attachments=attachments,
1037
1042
  labels=["bug_catcher"],
1038
1043
  )
1044
+ if os.path.exists(stack_trace):
1045
+ os.remove(stack_trace)
1039
1046
  return response
1040
1047
  except Exception as ex:
1041
1048
  logger.warning(f"Failed to create Jira issue due to: {ex.__class__.__name__}: {ex}")
1042
1049
  return False
1043
1050
 
1044
- def report_unhandled_error(self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType):
1051
+ def report_unhandled_error(
1052
+ self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, stack_trace: str = None
1053
+ ):
1045
1054
  """Report an unhandled error to Jira.
1046
1055
 
1047
1056
  Args:
1048
1057
  exc_type (type): The type of the exception.
1049
1058
  exc_value (Exception, str): The value of the exception.
1050
1059
  exc_traceback (TracebackType): The traceback of the exception.
1051
- assignee (str, optional): The assignee to be added to the Jira issue.
1060
+ stack_trace (str, optional): Stack trace to be added to the Jira issue.
1052
1061
 
1053
1062
  Returns:
1054
1063
  The response from creating the Jira issue.
@@ -1086,8 +1095,11 @@ class Jira:
1086
1095
  summary=summary,
1087
1096
  description=description,
1088
1097
  assignee_id=assignee_id,
1098
+ attachments=[stack_trace] if stack_trace else None,
1089
1099
  labels=["bug_catcher", "fatal_error"],
1090
1100
  )
1101
+ if os.path.exists(stack_trace):
1102
+ os.remove(stack_trace)
1091
1103
  return response
1092
1104
  except Exception as ex:
1093
1105
  logger.warning(f"Failed to create Jira issue due to: {ex.__class__.__name__}: {ex}")
@@ -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.1
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
  ==============
@@ -10,6 +10,7 @@ t_bug_catcher/bug_snag.py
10
10
  t_bug_catcher/config.py
11
11
  t_bug_catcher/exceptions.py
12
12
  t_bug_catcher/jira.py
13
+ t_bug_catcher/stack_saver.py
13
14
  t_bug_catcher/workitems.py
14
15
  t_bug_catcher.egg-info/PKG-INFO
15
16
  t_bug_catcher.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
1
1
  requests<3.0.0,>=2.31.0
2
2
  bugsnag>=4.6.1
3
- retry~=0.9.2
3
+ retry~=0.9.2
4
+ whispers>=2.2.1
File without changes
File without changes