-
Notifications
You must be signed in to change notification settings - Fork 3
/
enable_faulthandler.py
151 lines (118 loc) · 5.69 KB
/
enable_faulthandler.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
"""Enable Python's faulthandler.
When you crash the app, a traceback is written into the faultlog folder and a warning message is displayed the next time you launch Pythonista.
"""
from __future__ import absolute_import, division, print_function
def run():
import ctypes
import datetime
import errno
import io
import objc_util
import os
import shutil
import sys
try:
unicode
except NameError:
unicode = str
print(u"Enabling fault handler and Objective-C exception handler...")
LOGDIR = os.path.expanduser(u"~/Documents/faultlog")
LOGNAME_TEMPLATE = u"faultlog-{:%Y-%m-%d-%H-%M-%S}.txt"
LOGNAME_DEFAULT = u"faultlog-temp.txt"
EXCEPTIONLOGNAME_DEFAULT = u"exceptionlog-temp.txt"
# Create the faultlog directory if necessary
try:
os.mkdir(LOGDIR)
except (IOError, OSError) as err:
if err.errno != errno.EEXIST:
raise
# Check whether an Objective-C exception log exists and append it to the fault log
try:
fin = io.open(os.path.join(LOGDIR, EXCEPTIONLOGNAME_DEFAULT), "rb")
except (IOError, OSError) as err:
if err.errno != errno.ENOENT:
raise
else:
with fin:
data = fin.read()
if data:
with io.open(os.path.join(LOGDIR, LOGNAME_DEFAULT), "ab") as fout:
# If the faultlog is not empty, add a separator
if fout.tell() != 0:
fout.write(b"\n" + b"-"*72 + b"\n\n")
fout.write(data)
os.remove(os.path.join(LOGDIR, EXCEPTIONLOGNAME_DEFAULT))
# Check whether a faultlog was written
did_fault = False
try:
f = io.open(os.path.join(LOGDIR, LOGNAME_DEFAULT), "rb")
except (IOError, OSError) as err:
if err.errno != errno.ENOENT:
raise
else:
with f:
if f.read(1):
did_fault = True
# Notify the user that a crash has happened
if did_fault:
import console
try:
from urllib.parse import quote
except ImportError:
from urllib import quote
print(u"Pythonista quit abnormally last time.", file=sys.stderr)
default_path = os.path.join(LOGDIR, LOGNAME_DEFAULT)
stamped_name = LOGNAME_TEMPLATE.format(datetime.datetime.fromtimestamp(os.stat(default_path).st_mtime))
stamped_path = os.path.join(LOGDIR, stamped_name)
shutil.move(default_path, stamped_path)
print(u"For details, see the following log file: ", file=sys.stderr, end=u"")
console.write_link(stamped_name, "file://" + quote(stamped_path))
print(file=sys.stderr)
if sys.version_info < (3,):
print(u"Setting exception handler.")
# Set the Objective-C exception handler only under Python 2.
# Otherwise under Pythonista 3 it would be set twice - once by Python 2 and once by Python 3.
# This way the exception handler is set exactly once and works under Pythonista 2 and 3.
# typedef void (*objc_uncaught_exception_handler)(id exception);
objc_uncaught_exception_handler = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
# objc_uncaught_exception_handler objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn);
objc_util.c.objc_setUncaughtExceptionHandler.argtypes = [objc_uncaught_exception_handler]
objc_util.c.objc_setUncaughtExceptionHandler.restype = objc_uncaught_exception_handler
# Set Objective-C uncaught exception handler
@objc_uncaught_exception_handler
def handler(exc_pointer):
exc = objc_util.ObjCInstance(exc_pointer)
name = exc.name()
reason = exc.reason()
user_info = exc.userInfo()
call_stack_symbols = exc.callStackSymbols()
with io.open(os.path.join(LOGDIR, EXCEPTIONLOGNAME_DEFAULT), "wb") as f:
try:
f.write(b"Objective-C exception details:\n\n")
if reason is None:
f.write(str(name).encode("utf-8") + b"\n")
else:
f.write(str(name).encode("utf-8") + b": " + str(reason).encode("utf-8") + b"\n")
if user_info is not None:
f.write(str(user_info).encode("utf-8") + b"\n")
f.write(b"\nStack trace:\n\n")
for sym in call_stack_symbols:
f.write(str(sym).encode("utf-8") + b"\n")
f.write(b"\nEnd of exception details.\n")
except Exception as err:
import traceback
f.write(b"I messed up! Python exception:\n")
f.write(traceback.format_exc().encode("utf-8"))
raise
# The exception handler must be kept in some kind of permanent location, otherwise it will be collected by the garbage collector, because there are no more references to it from Python.
objc_util._dgelessus_pythonista_startup_exception_handler = handler
objc_util.c.objc_setUncaughtExceptionHandler(handler)
else:
# The faulthandler module is only available under Python 3.
print("Setting fault handler.")
import faulthandler
logfile = io.open(os.path.join(LOGDIR, LOGNAME_DEFAULT), "wb")
faulthandler.enable(logfile)
print(u"Done enabling fault handler and Objective-C exception handler.")
if __name__ == "__main__":
run()