From 70b692cf157779955d7a2eafd59885f96b96b6e4 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 17 Sep 2024 19:05:22 +0200 Subject: [PATCH 1/6] Version 0.4.0 --- CHANGELOG.md | 19 +++++++++ README.md | 84 +++++++++++++++++++++++--------------- backtracepython/version.py | 4 +- setup.py | 2 +- 4 files changed, 74 insertions(+), 35 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dc01eff --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Version 0.4.0 + +- Changed a submission flow - from spawning and sending data from a process to background thread(#19). +- Added support for submit.backtrace.io(#14). +- Added new default attributes: + - backtrace.agent, backtrace.version (#13) + - application.session (#13), + - uname.sysname, uname.version, uname.release (#13), + - error.type (#13), + - linux process and system memory information (#15), + - guid (#15), + - cpu attributes (#15), + - process.age (#15), +- Added a support for attachments (#19), +- Added a new client initialization attributes: + - attributes - client specific attributes that will be included every time the report is being generated (#19), + - attachments - list of attachments paths (#19), + - collect_source_code - disable collecting source code information for each stack trace (#20), + - ignore_ssl_certificate ($19) diff --git a/README.md b/README.md index 4ae2ecd..33702b1 100644 --- a/README.md +++ b/README.md @@ -16,98 +16,118 @@ This module supports Python 2, Python 3, and PyPy. python -m pip install backtracepython ``` - ## Basic Usage -```import backtracepython as bt +``` +import backtracepython as bt bt.initialize( -    endpoint="https://yourcompany.sp.backtrace.io:6098", token="51cc8e69c5b62fa8c72dc963e730f1e8eacbd243aeafc35d08d05ded9a024121" +    endpoint="https://submit.backtrace.io/{universe}/{token}/json" ) ``` + ### Sending Reports from Unhandled Exceptions -By default, the backtracepython module will automatically capture unhandled exceptions and create and send error reports from them.  This behavior can be adjusted with the ```disable_global_handler``` option to ```bt.initialize``` (see below). +By default, the backtracepython module will automatically capture unhandled exceptions and create and send error reports from them.  This behavior can be adjusted with the `disable_global_handler` option to `bt.initialize` (see below). ### Sending Reports Manually -You can also send error reports manually within code.  However, in order to get a correct callstack and source code context, you must send an error with a python exception context.  To do this, you can raise a python exception then immediately send a report via the ```send_last_exception call```.  A simple example: +You can also send error reports manually within code.  However, in order to get a correct callstack and source code context, you must send an error with a python exception context.  To do this, you can raise a python exception then immediately send a report via the `send_last_exception call`.  A simple example: -```try: +``` +try:     raise Exception("This report was sent manually.") except:     bt.send_last_exception() ``` ## Reference -### bt.initialize(**kwargs) + +### bt.initialize + #### Arguments -* endpoint  - Required. Example: https://yourcompany.sp.backtrace.io:6098 .  Sets the HTTP/HTTPS endpoint that error reports will be sent to.  -token  -  Required. Example: 51cc8e69c5b62fa8c72dc963e730f1e8eacbd243aeafc35d08d05ded9a024121  Sets the token that will be used for authentication when sending an error report.  -* attributes  - Dictionary that contains additional attributes to be sent along with every error report. These can be overridden on an individual report with report.set_attribute  Example: { 'application': "ApplicationName", 'serverId': "foo" }   -* timeout  - Defaults to 4. Maximum amount of seconds to wait for error report processing and sending before concluding it failed.  -* debug_backtrace - Defaults to False . Set to True to have an error during collecting the report raise an exception, and to print some debugging information to stderr.  -* disable_global_handler - Defaults to False. If this is False  this module will insert itself in the sys.excepthook chain and report those errors automatically before re-raising the exception. Set to True  to disable this. Note that in this case the only way error reports will be reported is if you manually create and send them.  -* context_line_count  - Defaults to 200 . When an error is reported, this many lines above and below each stack function are included in the report.  -* tab_width  - Defaults to 8.  If there are any hard tabs in the source code, it is unclear how many spaces they should be indented to correctly display the source code. Therefore the error report can override this number to specify how many spaces a hard tab should be represented by when viewing source code.  + +- endpoint  - Required. Example: https://yourcompany.sp.backtrace.io:6098 or https://submit.backtrace.io/{universe}/{token}/json.  Sets the HTTP/HTTPS endpoint that error reports will be sent to. If submit.backtrace.io url is provided, the token argument is not required. + token  -  Required only if endpoint is not set to submit.backtrace.io. Example: 51cc8e69c5b62fa8c72dc963e730f1e8eacbd243aeafc35d08d05ded9a024121  Sets the token that will be used for authentication when sending an error report. +- attributes  - Dictionary that contains additional attributes to be sent along with every error report. These can be overridden on an individual report with report.set_attribute  Example: { 'application': "ApplicationName", 'serverId': "foo" }. Attributes values should be set to a primitive value such as boolean, integer or string. +- attachments - A list of file paths that will be sent with each report. +- ignore_ssl_certificate - Defaults to False. If True, ssl verification will be ignored during HTTP submission. +- timeout  - Defaults to 4. Maximum amount of seconds to wait for error report processing and sending before concluding it failed. +- debug_backtrace - Defaults to False . Set to True to have an error during collecting the report raise an exception, and to print some debugging information to stderr. +- disable_global_handler - Defaults to False. If this is False  this module will insert itself in the sys.excepthook chain and report those errors automatically before re-raising the exception. Set to True  to disable this. Note that in this case the only way error reports will be reported is if you manually create and send them. +- context_line_count  - Defaults to 200 . When an error is reported, this many lines above and below each stack function are included in the report. +- tab_width  - Defaults to 8.  If there are any hard tabs in the source code, it is unclear how many spaces they should be indented to correctly display the source code. Therefore the error report can override this number to specify how many spaces a hard tab should be represented by when viewing source code. +- collect_source_code - Default to True. By default Backtrace client collects corresponded source code and send it with the report. If set to False, the source code will not be collected. ### bt.BacktraceReport Create a report object that you can later choose whether or not to send. This may be useful to track something like a request. -```report.set_attribute(key, value) ``` +`report.set_attribute(key, value) ` Adds an attribute to a specific report. Valid types for value are str, float, int, and bool. -Attributes are indexed and searchable. See also ```addAnnotation``` +Attributes are indexed and searchable. See also `addAnnotation` -```report.set_dict_attributes(dict)```  +`report.set_dict_attributes(dict)` Adds all key-value pairs of dict into the report recursively. -```report.set_annotation(key, value) ``` +`report.get_attributes() ` + +Returns all report attributes. + +`report.set_annotation(key, value) ` Adds an annotation to a specific report. Annotations, unlike attributes, are not indexed and searchable. However, they are available for inspection when you view a specific report. key - String which is the name of the annotation. value - Any type which is JSON-serializable. -```report.set_dict_annotations(dict) ``` +`report.set_dict_annotations(dict) ` Adds all key-value pairs of dict into the report. -```report.set_exception(ExceptionType, exception, traceback)``` +`report.add_attachment(attachment_path) ` + +Adds an attachment to the report. + +`report.get_attachments() ` + +Returns a list of attachment paths. + +`report.set_exception(ExceptionType, exception, traceback)` error  is an Error object. Backtrace will extract information from this object such as the error message and stack trace and send this information along with the report. -```report.capture_last_exception() ``` +`report.capture_last_exception() ` -This is the same as report.set_exception(*sys.exc_info()) +This is the same as report.set_exception(\*sys.exc_info()) -```report.log(line) ``` +`report.log(line) ` Adds a timestamped log message to the report. Log output is available when you view a report. -```report.send() ``` +`report.send() ` Sends the error report to the endpoint specified in initialize. -### bt.send_last_exception(**kwargs) -* attributes  - dictionary of attributes to add to the report. See report.set_dict_attributes -* annotations  - dictionary of annotations to add to the report. See report.set_dict_annotations +### bt.send_last_exception(\*\*kwargs) +- attributes  - dictionary of attributes to add to the report. See report.set_dict_attributes +- annotations  - dictionary of annotations to add to the report. See report.set_dict_annotations ## Contributing To run the test suite: ``` -python setup.py test +pytest ``` Since all of these implementations of Python are supported, be sure to run the test suite with all of them: - * Python 2 - * Python 3 - * PyPy +- Python 2 +- Python 3 +- PyPy ### Publishing to PyPI diff --git a/backtracepython/version.py b/backtracepython/version.py index 35028a7..7c01207 100644 --- a/backtracepython/version.py +++ b/backtracepython/version.py @@ -1,7 +1,7 @@ class version: major = 0 - minor = 3 - patch = 3 + minor = 4 + patch = 0 version_string = "{}.{}.{}".format(version.major, version.minor, version.patch) diff --git a/setup.py b/setup.py index c560f8c..055ba56 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ setup( name="backtracepython", - version="0.3.3", + version="0.4.0", description="Backtrace.io error reporting tool for Python", author="Backtrace.io", author_email="team@backtrace.io", From 5bd9cb098cf9f39f7480fbab5e97078553e6b3a6 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 17 Sep 2024 19:27:01 +0200 Subject: [PATCH 2/6] Add more tests before the release --- backtracepython/client.py | 8 +++++ tests/test_report_attributes.py | 31 ++++++++++++++++ tests/test_stack_trace_parser.py | 61 ++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 tests/test_stack_trace_parser.py diff --git a/backtracepython/client.py b/backtracepython/client.py index 1b74240..06aed09 100644 --- a/backtracepython/client.py +++ b/backtracepython/client.py @@ -23,6 +23,14 @@ def get_attributes(): return attribute_manager.get() +def set_attribute(key, value): + attribute_manager.add({key: value}) + + +def set_attributes(attributes): + attribute_manager.add(attributes) + + def send(report, attachments=[]): if globs.handler is None: return False diff --git a/tests/test_report_attributes.py b/tests/test_report_attributes.py index 339e73f..e15d297 100644 --- a/tests/test_report_attributes.py +++ b/tests/test_report_attributes.py @@ -1,3 +1,4 @@ +from backtracepython.client import set_attribute from backtracepython.report import BacktraceReport report = BacktraceReport() @@ -30,3 +31,33 @@ def test_report_attribute_override(): override_report.set_attribute(override_attribute_name, expected_value) assert override_report.get_attributes()[override_attribute_name] == expected_value + + +def test_unique_user_id(): + attributes = report.get_attributes() + assert attributes["guid"] is not None + + +def test_unique_user_should_always_be_the_same(): + override_report = BacktraceReport() + attributes = report.get_attributes() + override_report_attributes = override_report.get_attributes() + assert attributes["guid"] == override_report_attributes["guid"] + + +def test_override_default_client_attribute(): + test_attribute = "guid" + test_attribute_value = "foo" + set_attribute(test_attribute, test_attribute_value) + new_report = BacktraceReport() + attributes = new_report.get_attributes() + assert attributes["guid"] == test_attribute_value + + +def test_override_default_client_attribute_by_report(): + test_attribute = "guid" + test_attribute_value = "bar" + new_report = BacktraceReport() + new_report.set_attribute(test_attribute, test_attribute_value) + attributes = new_report.get_attributes() + assert attributes["guid"] == test_attribute_value diff --git a/tests/test_stack_trace_parser.py b/tests/test_stack_trace_parser.py new file mode 100644 index 0000000..7fe645e --- /dev/null +++ b/tests/test_stack_trace_parser.py @@ -0,0 +1,61 @@ +import sys +import threading +import time + +from backtracepython.report import BacktraceReport + + +def open_file(name): + open(name).read() + + +def failing_function(): + open_file("test") + + +def test_main_thread_generation_without_exception(): + report = BacktraceReport() + data = report.get_data() + stack_trace = data["threads"][data["mainThread"]] + assert len(stack_trace["stack"]) != 0 + + +def test_skipping_backtrace_module_in_process_stack(): + test_name = sys._getframe().f_code.co_name + + report = BacktraceReport() + data = report.get_data() + stack_trace = data["threads"][data["mainThread"]] + + current_function_name = stack_trace["stack"][0]["funcName"] + assert current_function_name == test_name + + +def test_main_thread_generation_with_exception(): + # this funciton + 2 fauulting functions + expected_number_of_frames = 3 + try: + failing_function() + except: + report = BacktraceReport() + report.capture_last_exception() + data = report.get_data() + stack_trace = data["threads"][data["mainThread"]] + assert len(stack_trace["stack"]) == expected_number_of_frames + + +def test_background_thread_stack_trace_generation(): + def wait_in_thread(): + while True: + time.sleep(0.1) + + def runner(): + wait_in_thread() + + thread = threading.Thread(target=runner, name="runner", daemon=True) + thread.start() + + report = BacktraceReport() + data = report.get_data() + stack_trace = data["threads"][str(thread.ident)] + assert len(stack_trace) != 0 From 009e1399b23c218b5ab2470259b3318c7e6d48d6 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 17 Sep 2024 19:32:25 +0200 Subject: [PATCH 3/6] Fix thread on python2-n --- tests/test_stack_trace_parser.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_stack_trace_parser.py b/tests/test_stack_trace_parser.py index 7fe645e..b758b01 100644 --- a/tests/test_stack_trace_parser.py +++ b/tests/test_stack_trace_parser.py @@ -45,17 +45,21 @@ def test_main_thread_generation_with_exception(): def test_background_thread_stack_trace_generation(): + if_stop = False + def wait_in_thread(): - while True: + while not if_stop: time.sleep(0.1) def runner(): wait_in_thread() - thread = threading.Thread(target=runner, name="runner", daemon=True) + thread = threading.Thread(target=runner, name="runner") thread.start() report = BacktraceReport() data = report.get_data() + if_stop = True + thread.join() stack_trace = data["threads"][str(thread.ident)] assert len(stack_trace) != 0 From 29a82d3ca0894b06f30d5a03a4d743ddf8449ae5 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 17 Sep 2024 19:35:26 +0200 Subject: [PATCH 4/6] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc01eff..cb5bddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,4 +16,4 @@ - attributes - client specific attributes that will be included every time the report is being generated (#19), - attachments - list of attachments paths (#19), - collect_source_code - disable collecting source code information for each stack trace (#20), - - ignore_ssl_certificate ($19) + - ignore_ssl_certificate - if True, the option disables ssl validation (#19). From 9d5eb8683aa6e660a1e28c8bb5ca8bfb0e23e3b1 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 17 Sep 2024 19:38:18 +0200 Subject: [PATCH 5/6] Use python3 if possible --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 33702b1..7daf7f0 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,6 @@ test suite with all of them: 3. Tag the version in git. ``` -python2 setup.py bdist_wheel --universal +python3 setup.py bdist_wheel --universal twine upload dist/* ``` From 9a06638b7fa8b7a6a9316f087cb373a4a78200fe Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 17 Sep 2024 19:43:21 +0200 Subject: [PATCH 6/6] Adjust readme --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7daf7f0..c2742c8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ python -m pip install backtracepython ## Basic Usage -``` +```python import backtracepython as bt bt.initialize(     endpoint="https://submit.backtrace.io/{universe}/{token}/json" @@ -27,20 +27,20 @@ bt.initialize( ### Sending Reports from Unhandled Exceptions -By default, the backtracepython module will automatically capture unhandled exceptions and create and send error reports from them.  This behavior can be adjusted with the `disable_global_handler` option to `bt.initialize` (see below). +By default, the `backtracepython` module automatically captures unhandled exceptions and creates and sends error reports from them. This behavior can be adjusted with the `disable_global_handler` option in `bt.initialize` (see below). ### Sending Reports Manually -You can also send error reports manually within code.  However, in order to get a correct callstack and source code context, you must send an error with a python exception context.  To do this, you can raise a python exception then immediately send a report via the `send_last_exception call`.  A simple example: +You can also send error reports manually in your code. However, to get a correct callstack and source code context, you must send an error with a Python exception context. To do this, you can raise a Python exception and then immediately send a report using the `send_last_exception` call. Here's an example: -``` +```python try:     raise Exception("This report was sent manually.") except:     bt.send_last_exception() ``` -## Reference +## Documentation ### bt.initialize @@ -101,11 +101,11 @@ error  is an Error object. Backtrace will extract information from this object This is the same as report.set_exception(\*sys.exc_info()) -`report.log(line) ` +`report.log(line)` Adds a timestamped log message to the report. Log output is available when you view a report. -`report.send() ` +`report.send()` Sends the error report to the endpoint specified in initialize.