-
Notifications
You must be signed in to change notification settings - Fork 2
Move code from init to client/report/utils #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,333 +1,14 @@ | ||
import os | ||
import platform | ||
import subprocess | ||
import sys | ||
import threading | ||
import time | ||
import uuid | ||
|
||
import simplejson as json | ||
|
||
from backtracepython.attributes.attribute_manager import AttributeManager | ||
|
||
from .version import version, version_string | ||
|
||
if sys.version_info.major >= 3: | ||
from urllib.parse import urlencode | ||
else: | ||
from urllib import urlencode | ||
from .report import BacktraceReport | ||
from .client import initialize, finalize, finalize, send_last_exception, send_report | ||
|
||
__all__ = [ | ||
"BacktraceReport", | ||
"initialize", | ||
"finalize", | ||
"terminate", | ||
"finalize", | ||
"version", | ||
perf2711 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"version_string", | ||
"send_last_exception", | ||
"send_report", | ||
] | ||
|
||
attribute_manager = AttributeManager() | ||
|
||
|
||
class globs: | ||
endpoint = None | ||
next_except_hook = None | ||
debug_backtrace = False | ||
timeout = None | ||
tab_width = None | ||
attributes = {} | ||
context_line_count = None | ||
worker = None | ||
next_source_code_id = 0 | ||
|
||
|
||
child_py_path = os.path.join(os.path.dirname(__file__), "child.py") | ||
|
||
|
||
def get_python_version(): | ||
return "{} {}.{}.{}-{}".format( | ||
platform.python_implementation(), | ||
sys.version_info.major, | ||
sys.version_info.minor, | ||
sys.version_info.micro, | ||
sys.version_info.releaselevel, | ||
) | ||
|
||
|
||
def send_worker_msg(msg): | ||
payload = json.dumps(msg, ignore_nan=True).encode("utf-8") | ||
globs.worker.stdin.write(payload) | ||
globs.worker.stdin.write("\n".encode("utf-8")) | ||
globs.worker.stdin.flush() | ||
|
||
|
||
def walk_tb_backwards(tb): | ||
while tb is not None: | ||
yield tb.tb_frame, tb.tb_lineno | ||
tb = tb.tb_next | ||
|
||
|
||
def walk_tb(tb): | ||
return reversed(list(walk_tb_backwards(tb))) | ||
|
||
|
||
def make_unique_source_code_id(): | ||
result = str(globs.next_source_code_id) | ||
globs.next_source_code_id += 1 | ||
return result | ||
|
||
|
||
def add_source_code(source_path, source_code_dict, source_path_dict, line): | ||
try: | ||
the_id = source_path_dict[source_path] | ||
except KeyError: | ||
the_id = make_unique_source_code_id() | ||
source_path_dict[source_path] = the_id | ||
source_code_dict[the_id] = { | ||
"minLine": line, | ||
"maxLine": line, | ||
"path": source_path, | ||
} | ||
return the_id | ||
|
||
if line < source_code_dict[the_id]["minLine"]: | ||
source_code_dict[the_id]["minLine"] = line | ||
if line > source_code_dict[the_id]["maxLine"]: | ||
source_code_dict[the_id]["maxLine"] = line | ||
return the_id | ||
|
||
|
||
def process_frame(tb_frame, line, source_code_dict, source_path_dict): | ||
source_file = os.path.abspath(tb_frame.f_code.co_filename) | ||
frame = { | ||
"funcName": tb_frame.f_code.co_name, | ||
"line": line, | ||
"sourceCode": add_source_code( | ||
source_file, source_code_dict, source_path_dict, line | ||
), | ||
} | ||
return frame | ||
|
||
|
||
def get_main_thread(): | ||
if sys.version_info.major >= 3: | ||
return threading.main_thread() | ||
first = None | ||
for thread in threading.enumerate(): | ||
if thread.name == "MainThread": | ||
return thread | ||
if first is None: | ||
first = thread | ||
return first | ||
|
||
|
||
class BacktraceReport: | ||
def __init__(self): | ||
self.fault_thread = threading.current_thread() | ||
self.source_code = {} | ||
self.source_path_dict = {} | ||
entry_source_code_id = None | ||
import __main__ | ||
|
||
cwd_path = os.path.abspath(os.getcwd()) | ||
entry_thread = get_main_thread() | ||
if hasattr(__main__, "__file__"): | ||
entry_source_code_id = ( | ||
add_source_code( | ||
__main__.__file__, self.source_code, self.source_path_dict, 1 | ||
) | ||
if hasattr(__main__, "__file__") | ||
else None | ||
) | ||
|
||
init_attrs = {"error.type": "Exception"} | ||
init_attrs.update(attribute_manager.get()) | ||
|
||
self.log_lines = [] | ||
|
||
self.report = { | ||
"uuid": str(uuid.uuid4()), | ||
"timestamp": int(time.time()), | ||
"lang": "python", | ||
"langVersion": get_python_version(), | ||
"agent": "backtrace-python", | ||
"agentVersion": version_string, | ||
"mainThread": str(self.fault_thread.ident), | ||
"entryThread": str(entry_thread.ident), | ||
"cwd": cwd_path, | ||
"attributes": init_attrs, | ||
"annotations": { | ||
"Environment Variables": dict(os.environ), | ||
}, | ||
} | ||
if entry_source_code_id is not None: | ||
self.report["entrySourceCode"] = entry_source_code_id | ||
|
||
def set_exception(self, garbage, ex_value, ex_traceback): | ||
self.report["classifiers"] = [ex_value.__class__.__name__] | ||
self.report["attributes"]["error.message"] = str(ex_value) | ||
|
||
threads = {} | ||
for thread in threading.enumerate(): | ||
if thread.ident == self.fault_thread.ident: | ||
threads[str(self.fault_thread.ident)] = { | ||
"name": self.fault_thread.name, | ||
"stack": [ | ||
process_frame( | ||
frame, line, self.source_code, self.source_path_dict | ||
) | ||
for frame, line in walk_tb(ex_traceback) | ||
], | ||
} | ||
else: | ||
threads[str(thread.ident)] = { | ||
"name": thread.name, | ||
} | ||
|
||
self.report["threads"] = threads | ||
|
||
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_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 send(self): | ||
if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]: | ||
self.report["annotations"]["Log"] = self.log_lines | ||
send_worker_msg( | ||
{ | ||
"id": "send", | ||
"report": self.report, | ||
"context_line_count": globs.context_line_count, | ||
"timeout": globs.timeout, | ||
"endpoint": globs.endpoint, | ||
"tab_width": globs.tab_width, | ||
"debug_backtrace": globs.debug_backtrace, | ||
"source_code": self.source_code, | ||
} | ||
) | ||
|
||
|
||
def create_and_send_report(ex_type, ex_value, ex_traceback): | ||
report = BacktraceReport() | ||
report.set_exception(ex_type, ex_value, ex_traceback) | ||
report.set_attribute("error.type", "Unhandled exception") | ||
report.send() | ||
|
||
|
||
def bt_except_hook(ex_type, ex_value, ex_traceback): | ||
if globs.debug_backtrace: | ||
# Go back to normal exceptions while we do our work here. | ||
sys.excepthook = globs.next_except_hook | ||
|
||
# Now if this fails we'll get a normal exception. | ||
create_and_send_report(ex_type, ex_value, ex_traceback) | ||
|
||
# Put our exception handler back in place, and then also | ||
# pass the exception down the chain. | ||
sys.excepthook = bt_except_hook | ||
else: | ||
# Failure here is silent. | ||
try: | ||
create_and_send_report(ex_type, ex_value, ex_traceback) | ||
except: | ||
pass | ||
|
||
# Send the exception on to the next thing in the chain. | ||
globs.next_except_hook(ex_type, ex_value, ex_traceback) | ||
|
||
|
||
def initialize(**kwargs): | ||
globs.endpoint = construct_submission_url( | ||
kwargs["endpoint"], kwargs.get("token", None) | ||
) | ||
globs.debug_backtrace = kwargs.get("debug_backtrace", False) | ||
globs.timeout = kwargs.get("timeout", 4) | ||
globs.tab_width = kwargs.get("tab_width", 8) | ||
globs.context_line_count = kwargs.get("context_line_count", 200) | ||
|
||
attribute_manager.add(kwargs.get("attributes", {})) | ||
stdio_value = None if globs.debug_backtrace else subprocess.PIPE | ||
globs.worker = subprocess.Popen( | ||
[sys.executable, child_py_path], | ||
stdin=subprocess.PIPE, | ||
stdout=stdio_value, | ||
stderr=stdio_value, | ||
) | ||
|
||
disable_global_handler = kwargs.get("disable_global_handler", False) | ||
if not disable_global_handler: | ||
globs.next_except_hook = sys.excepthook | ||
sys.excepthook = bt_except_hook | ||
|
||
|
||
def construct_submission_url(endpoint, token): | ||
if "submit.backtrace.io" in endpoint or token is None: | ||
return endpoint | ||
|
||
return "{}/post?{}".format( | ||
endpoint, | ||
urlencode( | ||
{ | ||
"token": token, | ||
"format": "json", | ||
} | ||
), | ||
) | ||
|
||
|
||
def finalize(): | ||
send_worker_msg({"id": "terminate"}) | ||
if not globs.debug_backtrace: | ||
globs.worker.stdout.close() | ||
globs.worker.stderr.close() | ||
globs.worker.wait() | ||
|
||
|
||
def send_last_exception(**kwargs): | ||
report = BacktraceReport() | ||
report.capture_last_exception() | ||
report.set_dict_attributes(kwargs.get("attributes", {})) | ||
report.set_dict_annotations(kwargs.get("annotations", {})) | ||
report.set_attribute("error.type", "Exception") | ||
report.send() | ||
|
||
|
||
def make_an_exception(): | ||
try: | ||
raise Exception | ||
except: | ||
return sys.exc_info() | ||
|
||
|
||
def send_report(msg, **kwargs): | ||
report = BacktraceReport() | ||
report.set_exception(*make_an_exception()) | ||
report.set_dict_attributes(kwargs.get("attributes", {})) | ||
report.set_dict_annotations(kwargs.get("annotations", {})) | ||
report.set_attribute("error.message", msg) | ||
report.set_attribute("error.type", "Message") | ||
report.send() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.