Skip to content

Commit 2834643

Browse files
authored
Merge pull request #18 from backtrace-labs/bugfix/expose-from-init
Move code from init to client/report/utils
2 parents 78f26a8 + adec9e0 commit 2834643

File tree

7 files changed

+340
-324
lines changed

7 files changed

+340
-324
lines changed

backtracepython/__init__.py

Lines changed: 2 additions & 322 deletions
Original file line numberDiff line numberDiff line change
@@ -1,333 +1,13 @@
1-
import os
2-
import platform
3-
import subprocess
4-
import sys
5-
import threading
6-
import time
7-
import uuid
8-
9-
import simplejson as json
10-
11-
from backtracepython.attributes.attribute_manager import AttributeManager
12-
1+
from .client import finalize, initialize, send_last_exception, send_report
2+
from .report import BacktraceReport
133
from .version import version, version_string
144

15-
if sys.version_info.major >= 3:
16-
from urllib.parse import urlencode
17-
else:
18-
from urllib import urlencode
19-
205
__all__ = [
216
"BacktraceReport",
227
"initialize",
238
"finalize",
24-
"terminate",
259
"version",
2610
"version_string",
2711
"send_last_exception",
2812
"send_report",
2913
]
30-
31-
attribute_manager = AttributeManager()
32-
33-
34-
class globs:
35-
endpoint = None
36-
next_except_hook = None
37-
debug_backtrace = False
38-
timeout = None
39-
tab_width = None
40-
attributes = {}
41-
context_line_count = None
42-
worker = None
43-
next_source_code_id = 0
44-
45-
46-
child_py_path = os.path.join(os.path.dirname(__file__), "child.py")
47-
48-
49-
def get_python_version():
50-
return "{} {}.{}.{}-{}".format(
51-
platform.python_implementation(),
52-
sys.version_info.major,
53-
sys.version_info.minor,
54-
sys.version_info.micro,
55-
sys.version_info.releaselevel,
56-
)
57-
58-
59-
def send_worker_msg(msg):
60-
payload = json.dumps(msg, ignore_nan=True).encode("utf-8")
61-
globs.worker.stdin.write(payload)
62-
globs.worker.stdin.write("\n".encode("utf-8"))
63-
globs.worker.stdin.flush()
64-
65-
66-
def walk_tb_backwards(tb):
67-
while tb is not None:
68-
yield tb.tb_frame, tb.tb_lineno
69-
tb = tb.tb_next
70-
71-
72-
def walk_tb(tb):
73-
return reversed(list(walk_tb_backwards(tb)))
74-
75-
76-
def make_unique_source_code_id():
77-
result = str(globs.next_source_code_id)
78-
globs.next_source_code_id += 1
79-
return result
80-
81-
82-
def add_source_code(source_path, source_code_dict, source_path_dict, line):
83-
try:
84-
the_id = source_path_dict[source_path]
85-
except KeyError:
86-
the_id = make_unique_source_code_id()
87-
source_path_dict[source_path] = the_id
88-
source_code_dict[the_id] = {
89-
"minLine": line,
90-
"maxLine": line,
91-
"path": source_path,
92-
}
93-
return the_id
94-
95-
if line < source_code_dict[the_id]["minLine"]:
96-
source_code_dict[the_id]["minLine"] = line
97-
if line > source_code_dict[the_id]["maxLine"]:
98-
source_code_dict[the_id]["maxLine"] = line
99-
return the_id
100-
101-
102-
def process_frame(tb_frame, line, source_code_dict, source_path_dict):
103-
source_file = os.path.abspath(tb_frame.f_code.co_filename)
104-
frame = {
105-
"funcName": tb_frame.f_code.co_name,
106-
"line": line,
107-
"sourceCode": add_source_code(
108-
source_file, source_code_dict, source_path_dict, line
109-
),
110-
}
111-
return frame
112-
113-
114-
def get_main_thread():
115-
if sys.version_info.major >= 3:
116-
return threading.main_thread()
117-
first = None
118-
for thread in threading.enumerate():
119-
if thread.name == "MainThread":
120-
return thread
121-
if first is None:
122-
first = thread
123-
return first
124-
125-
126-
class BacktraceReport:
127-
def __init__(self):
128-
self.fault_thread = threading.current_thread()
129-
self.source_code = {}
130-
self.source_path_dict = {}
131-
entry_source_code_id = None
132-
import __main__
133-
134-
cwd_path = os.path.abspath(os.getcwd())
135-
entry_thread = get_main_thread()
136-
if hasattr(__main__, "__file__"):
137-
entry_source_code_id = (
138-
add_source_code(
139-
__main__.__file__, self.source_code, self.source_path_dict, 1
140-
)
141-
if hasattr(__main__, "__file__")
142-
else None
143-
)
144-
145-
init_attrs = {"error.type": "Exception"}
146-
init_attrs.update(attribute_manager.get())
147-
148-
self.log_lines = []
149-
150-
self.report = {
151-
"uuid": str(uuid.uuid4()),
152-
"timestamp": int(time.time()),
153-
"lang": "python",
154-
"langVersion": get_python_version(),
155-
"agent": "backtrace-python",
156-
"agentVersion": version_string,
157-
"mainThread": str(self.fault_thread.ident),
158-
"entryThread": str(entry_thread.ident),
159-
"cwd": cwd_path,
160-
"attributes": init_attrs,
161-
"annotations": {
162-
"Environment Variables": dict(os.environ),
163-
},
164-
}
165-
if entry_source_code_id is not None:
166-
self.report["entrySourceCode"] = entry_source_code_id
167-
168-
def set_exception(self, garbage, ex_value, ex_traceback):
169-
self.report["classifiers"] = [ex_value.__class__.__name__]
170-
self.report["attributes"]["error.message"] = str(ex_value)
171-
172-
threads = {}
173-
for thread in threading.enumerate():
174-
if thread.ident == self.fault_thread.ident:
175-
threads[str(self.fault_thread.ident)] = {
176-
"name": self.fault_thread.name,
177-
"stack": [
178-
process_frame(
179-
frame, line, self.source_code, self.source_path_dict
180-
)
181-
for frame, line in walk_tb(ex_traceback)
182-
],
183-
}
184-
else:
185-
threads[str(thread.ident)] = {
186-
"name": thread.name,
187-
}
188-
189-
self.report["threads"] = threads
190-
191-
def capture_last_exception(self):
192-
self.set_exception(*sys.exc_info())
193-
194-
def set_attribute(self, key, value):
195-
self.report["attributes"][key] = value
196-
197-
def set_dict_attributes(self, target_dict):
198-
self.report["attributes"].update(target_dict)
199-
200-
def set_annotation(self, key, value):
201-
self.report["annotations"][key] = value
202-
203-
def get_attributes(self):
204-
return self.report["attributes"]
205-
206-
def set_dict_annotations(self, target_dict):
207-
self.report["annotations"].update(target_dict)
208-
209-
def log(self, line):
210-
self.log_lines.append(
211-
{
212-
"ts": time.time(),
213-
"msg": line,
214-
}
215-
)
216-
217-
def send(self):
218-
if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]:
219-
self.report["annotations"]["Log"] = self.log_lines
220-
send_worker_msg(
221-
{
222-
"id": "send",
223-
"report": self.report,
224-
"context_line_count": globs.context_line_count,
225-
"timeout": globs.timeout,
226-
"endpoint": globs.endpoint,
227-
"tab_width": globs.tab_width,
228-
"debug_backtrace": globs.debug_backtrace,
229-
"source_code": self.source_code,
230-
}
231-
)
232-
233-
234-
def create_and_send_report(ex_type, ex_value, ex_traceback):
235-
report = BacktraceReport()
236-
report.set_exception(ex_type, ex_value, ex_traceback)
237-
report.set_attribute("error.type", "Unhandled exception")
238-
report.send()
239-
240-
241-
def bt_except_hook(ex_type, ex_value, ex_traceback):
242-
if globs.debug_backtrace:
243-
# Go back to normal exceptions while we do our work here.
244-
sys.excepthook = globs.next_except_hook
245-
246-
# Now if this fails we'll get a normal exception.
247-
create_and_send_report(ex_type, ex_value, ex_traceback)
248-
249-
# Put our exception handler back in place, and then also
250-
# pass the exception down the chain.
251-
sys.excepthook = bt_except_hook
252-
else:
253-
# Failure here is silent.
254-
try:
255-
create_and_send_report(ex_type, ex_value, ex_traceback)
256-
except:
257-
pass
258-
259-
# Send the exception on to the next thing in the chain.
260-
globs.next_except_hook(ex_type, ex_value, ex_traceback)
261-
262-
263-
def initialize(**kwargs):
264-
globs.endpoint = construct_submission_url(
265-
kwargs["endpoint"], kwargs.get("token", None)
266-
)
267-
globs.debug_backtrace = kwargs.get("debug_backtrace", False)
268-
globs.timeout = kwargs.get("timeout", 4)
269-
globs.tab_width = kwargs.get("tab_width", 8)
270-
globs.context_line_count = kwargs.get("context_line_count", 200)
271-
272-
attribute_manager.add(kwargs.get("attributes", {}))
273-
stdio_value = None if globs.debug_backtrace else subprocess.PIPE
274-
globs.worker = subprocess.Popen(
275-
[sys.executable, child_py_path],
276-
stdin=subprocess.PIPE,
277-
stdout=stdio_value,
278-
stderr=stdio_value,
279-
)
280-
281-
disable_global_handler = kwargs.get("disable_global_handler", False)
282-
if not disable_global_handler:
283-
globs.next_except_hook = sys.excepthook
284-
sys.excepthook = bt_except_hook
285-
286-
287-
def construct_submission_url(endpoint, token):
288-
if "submit.backtrace.io" in endpoint or token is None:
289-
return endpoint
290-
291-
return "{}/post?{}".format(
292-
endpoint,
293-
urlencode(
294-
{
295-
"token": token,
296-
"format": "json",
297-
}
298-
),
299-
)
300-
301-
302-
def finalize():
303-
send_worker_msg({"id": "terminate"})
304-
if not globs.debug_backtrace:
305-
globs.worker.stdout.close()
306-
globs.worker.stderr.close()
307-
globs.worker.wait()
308-
309-
310-
def send_last_exception(**kwargs):
311-
report = BacktraceReport()
312-
report.capture_last_exception()
313-
report.set_dict_attributes(kwargs.get("attributes", {}))
314-
report.set_dict_annotations(kwargs.get("annotations", {}))
315-
report.set_attribute("error.type", "Exception")
316-
report.send()
317-
318-
319-
def make_an_exception():
320-
try:
321-
raise Exception
322-
except:
323-
return sys.exc_info()
324-
325-
326-
def send_report(msg, **kwargs):
327-
report = BacktraceReport()
328-
report.set_exception(*make_an_exception())
329-
report.set_dict_attributes(kwargs.get("attributes", {}))
330-
report.set_dict_annotations(kwargs.get("annotations", {}))
331-
report.set_attribute("error.message", msg)
332-
report.set_attribute("error.type", "Message")
333-
report.send()

backtracepython/attributes/attribute_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ def get_predefined_dynamic_attribute_providers(self):
6363
result.append(LinuxMemoryAttributeProvider())
6464

6565
return result
66+
67+
68+
attribute_manager = AttributeManager()

0 commit comments

Comments
 (0)