OLD | NEW |
(Empty) | |
| 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 |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """Breakpad for Python. |
| 6 |
| 7 Sends a notification when a process stops on an exception. |
| 8 |
| 9 It is only enabled when all these conditions are met: |
| 10 1. hostname finishes with '.google.com' or 'chromium.org' |
| 11 2. main module name doesn't contain the word 'test' |
| 12 3. no NO_BREAKPAD environment variable is defined |
| 13 """ |
| 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 |