From 7d87f6333a7384ff94fe34d1e005a5d01fe6aafc Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 29 Oct 2024 11:19:31 +0100 Subject: [PATCH 1/6] Test invalid background thread behavior --- tests/test_stack_trace_parser.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_stack_trace_parser.py b/tests/test_stack_trace_parser.py index b758b01..772b84e 100644 --- a/tests/test_stack_trace_parser.py +++ b/tests/test_stack_trace_parser.py @@ -44,6 +44,26 @@ def test_main_thread_generation_with_exception(): assert len(stack_trace["stack"]) == expected_number_of_frames +def test_stack_trace_generation_from_background_thread(): + background_thread_name = "test_background" + def throw_in_background(): + try: + failing_function() + except: + report = BacktraceReport() + report.capture_last_exception() + data = report.get_data() + faulting_thread = data["threads"][data["mainThread"]] + assert faulting_thread["name"] != "MainThread" + assert faulting_thread["name"] == background_thread_name + assert faulting_thread["fault"] == True + + + + thread = threading.Thread(target=throw_in_background, name=background_thread_name, daemon=False) + thread.start() + thread.join() + def test_background_thread_stack_trace_generation(): if_stop = False From 2c11d09932bac3fbb620bb2dd118d0c32636f2cb Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 29 Oct 2024 12:08:14 +0100 Subject: [PATCH 2/6] Make sure all threads are veryfied --- tests/test_stack_trace_parser.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/test_stack_trace_parser.py b/tests/test_stack_trace_parser.py index 772b84e..4e37333 100644 --- a/tests/test_stack_trace_parser.py +++ b/tests/test_stack_trace_parser.py @@ -46,6 +46,7 @@ def test_main_thread_generation_with_exception(): def test_stack_trace_generation_from_background_thread(): background_thread_name = "test_background" + data_container = [] def throw_in_background(): try: failing_function() @@ -53,16 +54,28 @@ def throw_in_background(): report = BacktraceReport() report.capture_last_exception() data = report.get_data() - faulting_thread = data["threads"][data["mainThread"]] - assert faulting_thread["name"] != "MainThread" - assert faulting_thread["name"] == background_thread_name - assert faulting_thread["fault"] == True + data_container.append(data) - thread = threading.Thread(target=throw_in_background, name=background_thread_name, daemon=False) + thread = threading.Thread(target=throw_in_background, name=background_thread_name) thread.start() thread.join() + if data_container: + data = data_container[0] + faulting_thread = data["threads"][data["mainThread"]] + assert faulting_thread["name"] != "MainThread" + assert faulting_thread["name"] == background_thread_name + assert faulting_thread["fault"] == True + # make sure other threads are not marked as faulting threads + for thread_id in data["threads"]: + thread = data["threads"][thread_id] + if thread["name"] == background_thread_name: + continue + assert thread["fault"] == False + + else: + assert False def test_background_thread_stack_trace_generation(): if_stop = False From 64026bc7239ac7790cc1861f449b9f45b03fb2de Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 29 Oct 2024 12:18:19 +0100 Subject: [PATCH 3/6] Override thread id when the exception is set --- backtracepython/report.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/backtracepython/report.py b/backtracepython/report.py index ea61d49..a9727cb 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -13,10 +13,13 @@ class BacktraceReport: def __init__(self): self.fault_thread = threading.current_thread() + self.faulting_thread_id = str(self.fault_thread.ident) self.source_code = {} self.source_path_dict = {} self.attachments = [] + stack_trace = self.generate_stack_trace() + attributes, annotations = attribute_manager.get() attributes.update({"error.type": "Exception"}) @@ -29,16 +32,20 @@ def __init__(self): "langVersion": python_version, "agent": "backtrace-python", "agentVersion": version_string, - "mainThread": str(self.fault_thread.ident), + "mainThread": self.faulting_thread_id, "attributes": attributes, "annotations": annotations, - "threads": self.generate_stack_trace(), + "threads": stack_trace, } def set_exception(self, garbage, ex_value, ex_traceback): self.report["classifiers"] = [ex_value.__class__.__name__] self.report["attributes"]["error.message"] = str(ex_value) + # reset faulting thread id and make sure the faulting thread is not listed twice + self.report["threads"][self.faulting_thread_id]["fault"] = False + + # update faulting thread with information from the error fault_thread_id = str(self.fault_thread.ident) if not fault_thread_id in self.report["threads"]: @@ -54,6 +61,9 @@ def set_exception(self, garbage, ex_value, ex_traceback): self.traverse_exception_stack(ex_traceback), False ) faulting_thread["fault"] = True + self.faulting_thread_id = fault_thread_id + self.report["mainThread"] = self.faulting_thread_id + def generate_stack_trace(self): current_frames = sys._current_frames() @@ -61,13 +71,16 @@ def generate_stack_trace(self): for thread in threading.enumerate(): thread_frame = current_frames.get(thread.ident) is_main_thread = thread.name == "MainThread" - threads[str(thread.ident)] = { + thread_id = str(thread.ident) + threads[thread_id] = { "name": thread.name, "stack": self.convert_stack_trace( self.traverse_process_thread_stack(thread_frame), is_main_thread ), "fault": is_main_thread, } + if is_main_thread: + self.faulting_thread_id = thread_id return threads From b9f21a3d3a536fa70903d5bf49399bc23e28f486 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 29 Oct 2024 12:23:40 +0100 Subject: [PATCH 4/6] Linter --- backtracepython/report.py | 2 -- tests/test_stack_trace_parser.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backtracepython/report.py b/backtracepython/report.py index a9727cb..4bfe1d5 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -45,7 +45,6 @@ def set_exception(self, garbage, ex_value, ex_traceback): # reset faulting thread id and make sure the faulting thread is not listed twice self.report["threads"][self.faulting_thread_id]["fault"] = False - # update faulting thread with information from the error fault_thread_id = str(self.fault_thread.ident) if not fault_thread_id in self.report["threads"]: @@ -64,7 +63,6 @@ def set_exception(self, garbage, ex_value, ex_traceback): self.faulting_thread_id = fault_thread_id self.report["mainThread"] = self.faulting_thread_id - def generate_stack_trace(self): current_frames = sys._current_frames() threads = {} diff --git a/tests/test_stack_trace_parser.py b/tests/test_stack_trace_parser.py index 4e37333..ba022f7 100644 --- a/tests/test_stack_trace_parser.py +++ b/tests/test_stack_trace_parser.py @@ -47,6 +47,7 @@ def test_main_thread_generation_with_exception(): def test_stack_trace_generation_from_background_thread(): background_thread_name = "test_background" data_container = [] + def throw_in_background(): try: failing_function() @@ -56,8 +57,6 @@ def throw_in_background(): data = report.get_data() data_container.append(data) - - thread = threading.Thread(target=throw_in_background, name=background_thread_name) thread.start() thread.join() @@ -77,6 +76,7 @@ def throw_in_background(): else: assert False + def test_background_thread_stack_trace_generation(): if_stop = False From 2d5354ad871351bdba8d32de7327eae0b05c6157 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 29 Oct 2024 13:31:16 +0100 Subject: [PATCH 5/6] Convert methods to private methods --- backtracepython/report.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backtracepython/report.py b/backtracepython/report.py index 4bfe1d5..dcae6a5 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -18,7 +18,7 @@ def __init__(self): self.source_path_dict = {} self.attachments = [] - stack_trace = self.generate_stack_trace() + stack_trace = self.__generate_stack_trace() attributes, annotations = attribute_manager.get() attributes.update({"error.type": "Exception"}) @@ -56,14 +56,14 @@ def set_exception(self, garbage, ex_value, ex_traceback): faulting_thread = self.report["threads"][fault_thread_id] - faulting_thread["stack"] = self.convert_stack_trace( - self.traverse_exception_stack(ex_traceback), False + faulting_thread["stack"] = self.__convert_stack_trace( + self.__traverse_exception_stack(ex_traceback), False ) faulting_thread["fault"] = True self.faulting_thread_id = fault_thread_id self.report["mainThread"] = self.faulting_thread_id - def generate_stack_trace(self): + def __generate_stack_trace(self): current_frames = sys._current_frames() threads = {} for thread in threading.enumerate(): @@ -72,8 +72,8 @@ def generate_stack_trace(self): thread_id = str(thread.ident) threads[thread_id] = { "name": thread.name, - "stack": self.convert_stack_trace( - self.traverse_process_thread_stack(thread_frame), is_main_thread + "stack": self.__convert_stack_trace( + self.__traverse_process_thread_stack(thread_frame), is_main_thread ), "fault": is_main_thread, } @@ -82,21 +82,21 @@ def generate_stack_trace(self): return threads - def traverse_exception_stack(self, traceback): + def __traverse_exception_stack(self, traceback): stack = [] while traceback: stack.append({"frame": traceback.tb_frame, "line": traceback.tb_lineno}) traceback = traceback.tb_next return reversed(stack) - def traverse_process_thread_stack(self, thread_frame): + def __traverse_process_thread_stack(self, thread_frame): stack = [] while thread_frame: stack.append({"frame": thread_frame, "line": thread_frame.f_lineno}) thread_frame = thread_frame.f_back return stack - def convert_stack_trace(self, thread_stack_trace, skip_backtrace_module): + def __convert_stack_trace(self, thread_stack_trace, skip_backtrace_module): stack_trace = [] for thread_stack_frame in thread_stack_trace: From b7964b5878f2dcd2984201e5cbf31f7288af2238 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 29 Oct 2024 13:32:29 +0100 Subject: [PATCH 6/6] Move methods to the bottom --- backtracepython/report.py | 90 +++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/backtracepython/report.py b/backtracepython/report.py index dcae6a5..28fc747 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -63,6 +63,51 @@ def set_exception(self, garbage, ex_value, ex_traceback): self.faulting_thread_id = fault_thread_id self.report["mainThread"] = self.faulting_thread_id + def capture_last_exception(self): + self.set_exception(*sys.exc_info()) + + def set_attribute(self, key, value): + self.report["attributes"][key] = value + + def set_dict_attributes(self, target_dict): + self.report["attributes"].update(target_dict) + + def set_annotation(self, key, value): + self.report["annotations"][key] = value + + def get_annotations(self): + return self.report["annotations"] + + def get_attributes(self): + return self.report["attributes"] + + def set_dict_annotations(self, target_dict): + self.report["annotations"].update(target_dict) + + def log(self, line): + self.log_lines.append( + { + "ts": time.time(), + "msg": line, + } + ) + + def add_attachment(self, attachment_path): + self.attachments.append(attachment_path) + + def get_attachments(self): + return self.attachments + + def get_data(self): + return self.report + + def send(self): + if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]: + self.report["annotations"]["Log"] = self.log_lines + from backtracepython.client import send + + send(self) + def __generate_stack_trace(self): current_frames = sys._current_frames() threads = {} @@ -120,48 +165,3 @@ def __convert_stack_trace(self, thread_stack_trace, skip_backtrace_module): ) return stack_trace - - def capture_last_exception(self): - self.set_exception(*sys.exc_info()) - - def set_attribute(self, key, value): - self.report["attributes"][key] = value - - def set_dict_attributes(self, target_dict): - self.report["attributes"].update(target_dict) - - def set_annotation(self, key, value): - self.report["annotations"][key] = value - - def get_annotations(self): - return self.report["annotations"] - - def get_attributes(self): - return self.report["attributes"] - - def set_dict_annotations(self, target_dict): - self.report["annotations"].update(target_dict) - - def log(self, line): - self.log_lines.append( - { - "ts": time.time(), - "msg": line, - } - ) - - def add_attachment(self, attachment_path): - self.attachments.append(attachment_path) - - def get_attachments(self): - return self.attachments - - def get_data(self): - return self.report - - def send(self): - if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]: - self.report["annotations"]["Log"] = self.log_lines - from backtracepython.client import send - - send(self)