OLD | NEW |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """Breakpad for Python. | 5 """This file remains here because of multiple find_depot_tools.py scripts |
| 6 that attempt to import it as a way to find depot_tools. |
6 | 7 |
7 Sends a notification when a process stops on an exception. | 8 See also: |
8 | 9 * http://crbug.com/585837 |
9 It is only enabled when all these conditions are met: | 10 * Example of find_depot_tools in build repo: |
10 1. hostname finishes with '.google.com' or 'chromium.org' | 11 https://chromium.googlesource.com/chromium/tools/build/+/2680bd4/scripts/common/
find_depot_tools.py |
11 2. main module name doesn't contain the word 'test' | |
12 3. no NO_BREAKPAD environment variable is defined | |
13 """ | 12 """ |
14 | |
15 import atexit | |
16 import getpass | |
17 import os | |
18 import socket | |
19 import sys | |
20 import time | |
21 import traceback | |
22 import urllib | |
23 import urllib2 | |
24 | |
25 | |
26 # Configure these values. | |
27 DEFAULT_URL = 'https://chromium-status.appspot.com' | |
28 | |
29 # Global variable to prevent double registration. | |
30 _REGISTERED = False | |
31 | |
32 _TIME_STARTED = time.time() | |
33 | |
34 _HOST_NAME = socket.getfqdn() | |
35 | |
36 # Skip unit tests and we don't want anything from non-googler. | |
37 IS_ENABLED = ( | |
38 not 'test' in getattr(sys.modules['__main__'], '__file__', '') and | |
39 not 'NO_BREAKPAD' in os.environ and | |
40 _HOST_NAME.endswith(('.google.com', '.chromium.org'))) | |
41 | |
42 | |
43 def post(url, params): | |
44 """HTTP POST with timeout when it's supported.""" | |
45 if not IS_ENABLED: | |
46 # Make sure to not send anything for non googler. | |
47 return | |
48 kwargs = {} | |
49 if (sys.version_info[0] * 10 + sys.version_info[1]) >= 26: | |
50 kwargs['timeout'] = 4 | |
51 try: | |
52 request = urllib2.urlopen(url, urllib.urlencode(params), **kwargs) | |
53 out = request.read() | |
54 request.close() | |
55 return out | |
56 except IOError: | |
57 return 'There was a failure while trying to send the stack trace. Too bad.' | |
58 | |
59 | |
60 def FormatException(e): | |
61 """Returns a human readable form of an exception. | |
62 | |
63 Adds the maximum number of interesting information in the safest way.""" | |
64 try: | |
65 out = repr(e) | |
66 except Exception: | |
67 out = '' | |
68 try: | |
69 out = str(e) | |
70 if isinstance(e, Exception): | |
71 # urllib exceptions, usually the HTTP headers. | |
72 if hasattr(e, 'headers'): | |
73 out += '\nHeaders: %s' % e.headers | |
74 if hasattr(e, 'url'): | |
75 out += '\nUrl: %s' % e.url | |
76 if hasattr(e, 'msg'): | |
77 out += '\nMsg: %s' % e.msg | |
78 # The web page in some urllib exceptions. | |
79 if hasattr(e, 'read') and callable(e.read): | |
80 out += '\nread(): %s' % e.read() | |
81 if hasattr(e, 'info') and callable(e.info): | |
82 out += '\ninfo(): %s' % e.info() | |
83 except Exception: | |
84 pass | |
85 return out | |
86 | |
87 | |
88 def SendStack(last_tb, stack, url=None, maxlen=50, verbose=True): | |
89 """Sends the stack trace to the breakpad server.""" | |
90 if not IS_ENABLED: | |
91 return | |
92 def p(o): | |
93 if verbose: | |
94 print(o) | |
95 p('Sending crash report ...') | |
96 params = { | |
97 'args': sys.argv, | |
98 'cwd': os.getcwd(), | |
99 'exception': FormatException(last_tb), | |
100 'host': _HOST_NAME, | |
101 'stack': stack[0:4096], | |
102 'user': getpass.getuser(), | |
103 'version': sys.version, | |
104 } | |
105 p('\n'.join(' %s: %s' % (k, params[k][0:maxlen]) for k in sorted(params))) | |
106 p(post(url or DEFAULT_URL + '/breakpad', params)) | |
107 | |
108 | |
109 def SendProfiling(duration, url=None): | |
110 params = { | |
111 'argv': ' '.join(sys.argv), | |
112 # Strip the hostname. | |
113 'domain': _HOST_NAME.split('.', 1)[-1], | |
114 'duration': duration, | |
115 'platform': sys.platform, | |
116 } | |
117 post(url or DEFAULT_URL + '/profiling', params) | |
118 | |
119 | |
120 def CheckForException(): | |
121 """Runs at exit. Look if there was an exception active.""" | |
122 last_value = getattr(sys, 'last_value', None) | |
123 if last_value: | |
124 if not isinstance(last_value, KeyboardInterrupt): | |
125 last_tb = getattr(sys, 'last_traceback', None) | |
126 if last_tb: | |
127 SendStack(last_value, ''.join(traceback.format_tb(last_tb))) | |
128 else: | |
129 duration = time.time() - _TIME_STARTED | |
130 if duration > 90: | |
131 SendProfiling(duration) | |
132 | |
133 | |
134 def Register(): | |
135 """Registers the callback at exit. Calling it multiple times is no-op.""" | |
136 global _REGISTERED | |
137 if _REGISTERED: | |
138 return | |
139 _REGISTERED = True | |
140 atexit.register(CheckForException) | |
141 | |
142 | |
143 if IS_ENABLED: | |
144 Register() | |
145 | |
146 # Uncomment this line if you want to test it out. | |
147 #Register() | |
OLD | NEW |