[email protected] | 1887842 | 2012-01-10 16:45:37 | [diff] [blame] | 1 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | ada4c65 | 2009-12-03 15:32:01 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | """Breakpad for Python. |
| 6 | |
[email protected] | fa87702 | 2010-09-09 20:01:09 | [diff] [blame] | 7 | Sends a notification when a process stops on an exception. |
| 8 | |
| 9 | It is only enabled when all these conditions are met: |
[email protected] | 2892425 | 2011-04-30 14:23:48 | [diff] [blame] | 10 | 1. hostname finishes with '.google.com' or 'chromium.org' |
[email protected] | fa87702 | 2010-09-09 20:01:09 | [diff] [blame] | 11 | 2. main module name doesn't contain the word 'test' |
| 12 | 3. no NO_BREAKPAD environment variable is defined |
| 13 | """ |
[email protected] | ada4c65 | 2009-12-03 15:32:01 | [diff] [blame] | 14 | |
| 15 | import atexit |
| 16 | import getpass |
[email protected] | fa87702 | 2010-09-09 20:01:09 | [diff] [blame] | 17 | import os |
[email protected] | 7aca9e8 | 2009-12-03 19:06:09 | [diff] [blame] | 18 | import socket |
[email protected] | ada4c65 | 2009-12-03 15:32:01 | [diff] [blame] | 19 | import sys |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 20 | import time |
| 21 | import traceback |
| 22 | import urllib |
| 23 | import urllib2 |
[email protected] | ada4c65 | 2009-12-03 15:32:01 | [diff] [blame] | 24 | |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 25 | |
[email protected] | 7aeac3e | 2010-05-13 19:53:25 | [diff] [blame] | 26 | # Configure these values. |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 27 | DEFAULT_URL = 'https://chromium-status.appspot.com' |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 28 | |
[email protected] | 2892425 | 2011-04-30 14:23:48 | [diff] [blame] | 29 | # Global variable to prevent double registration. |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 30 | _REGISTERED = False |
| 31 | |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 32 | _TIME_STARTED = time.time() |
| 33 | |
[email protected] | 2892425 | 2011-04-30 14:23:48 | [diff] [blame] | 34 | _HOST_NAME = socket.getfqdn() |
| 35 | |
[email protected] | 1887842 | 2012-01-10 16:45:37 | [diff] [blame] | 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 | |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 42 | |
| 43 | def post(url, params): |
| 44 | """HTTP POST with timeout when it's supported.""" |
[email protected] | 2e72bb1 | 2012-01-17 15:18:35 | [diff] [blame] | 45 | if not IS_ENABLED: |
| 46 | # Make sure to not send anything for non googler. |
| 47 | return |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 48 | kwargs = {} |
| 49 | if (sys.version_info[0] * 10 + sys.version_info[1]) >= 26: |
| 50 | kwargs['timeout'] = 4 |
[email protected] | 2e72bb1 | 2012-01-17 15:18:35 | [diff] [blame] | 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.' |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 58 | |
[email protected] | ada4c65 | 2009-12-03 15:32:01 | [diff] [blame] | 59 | |
[email protected] | 1bd1ed3 | 2010-11-24 20:50:56 | [diff] [blame] | 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 | |
[email protected] | 2e72bb1 | 2012-01-17 15:18:35 | [diff] [blame] | 88 | def SendStack(last_tb, stack, url=None, maxlen=50, verbose=True): |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 89 | """Sends the stack trace to the breakpad server.""" |
[email protected] | 1887842 | 2012-01-10 16:45:37 | [diff] [blame] | 90 | if not IS_ENABLED: |
[email protected] | 1887842 | 2012-01-10 16:45:37 | [diff] [blame] | 91 | return |
[email protected] | 2e72bb1 | 2012-01-17 15:18:35 | [diff] [blame] | 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)) |
[email protected] | 7aca9e8 | 2009-12-03 19:06:09 | [diff] [blame] | 107 | |
| 108 | |
[email protected] | 43b9910 | 2012-11-28 19:08:14 | [diff] [blame] | 109 | def SendProfiling(duration, url=None): |
[email protected] | 2e72bb1 | 2012-01-17 15:18:35 | [diff] [blame] | 110 | params = { |
| 111 | 'argv': ' '.join(sys.argv), |
| 112 | # Strip the hostname. |
| 113 | 'domain': _HOST_NAME.split('.', 1)[-1], |
[email protected] | 43b9910 | 2012-11-28 19:08:14 | [diff] [blame] | 114 | 'duration': duration, |
[email protected] | 2e72bb1 | 2012-01-17 15:18:35 | [diff] [blame] | 115 | 'platform': sys.platform, |
| 116 | } |
| 117 | post(url or DEFAULT_URL + '/profiling', params) |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 118 | |
| 119 | |
[email protected] | 7aca9e8 | 2009-12-03 19:06:09 | [diff] [blame] | 120 | def CheckForException(): |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 121 | """Runs at exit. Look if there was an exception active.""" |
[email protected] | 7aeac3e | 2010-05-13 19:53:25 | [diff] [blame] | 122 | last_value = getattr(sys, 'last_value', None) |
[email protected] | 7ca049b | 2011-04-29 12:58:23 | [diff] [blame] | 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: |
[email protected] | 43b9910 | 2012-11-28 19:08:14 | [diff] [blame] | 129 | duration = time.time() - _TIME_STARTED |
| 130 | if duration > 90: |
| 131 | SendProfiling(duration) |
[email protected] | 7aca9e8 | 2009-12-03 19:06:09 | [diff] [blame] | 132 | |
| 133 | |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 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 | |
[email protected] | 1887842 | 2012-01-10 16:45:37 | [diff] [blame] | 143 | if IS_ENABLED: |
[email protected] | ce921b2 | 2010-05-28 01:44:58 | [diff] [blame] | 144 | Register() |
| 145 | |
| 146 | # Uncomment this line if you want to test it out. |
| 147 | #Register() |