breakpad.py 3.78 KB
Newer Older
1
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 3 4 5 6
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Breakpad for Python.

7 8 9
Sends a notification when a process stops on an exception.

It is only enabled when all these conditions are met:
10
  1. hostname finishes with '.google.com' or 'chromium.org'
11 12 13
  2. main module name doesn't contain the word 'test'
  3. no NO_BREAKPAD environment variable is defined
"""
14 15 16

import atexit
import getpass
17
import os
18
import socket
19
import sys
20 21 22 23
import time
import traceback
import urllib
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 31
_REGISTERED = False

32 33
_TIME_STARTED = time.time()

34 35
_HOST_NAME = socket.getfqdn()

36 37 38 39 40 41
# Skip unit tests and we don't want anything from non-googler.
IS_ENABLED = (
    not 'test' in getattr(sys.modules['__main__'], '__file__', '') and
    not 'NO_BREAKPAD' in os.environ and
    _HOST_NAME.endswith(('.google.com', '.chromium.org')))

42 43 44

def post(url, params):
  """HTTP POST with timeout when it's supported."""
45 46 47
  if not IS_ENABLED:
    # Make sure to not send anything for non googler.
    return
48 49 50
  kwargs = {}
  if (sys.version_info[0] * 10 + sys.version_info[1]) >= 26:
    kwargs['timeout'] = 4
51 52 53 54 55 56 57
  try:
    request = urllib2.urlopen(url, urllib.urlencode(params), **kwargs)
    out = request.read()
    request.close()
    return out
  except IOError:
    return 'There was a failure while trying to send the stack trace. Too bad.'
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
def FormatException(e):
  """Returns a human readable form of an exception.

  Adds the maximum number of interesting information in the safest way."""
  try:
    out = repr(e)
  except Exception:
    out = ''
  try:
    out = str(e)
    if isinstance(e, Exception):
      # urllib exceptions, usually the HTTP headers.
      if hasattr(e, 'headers'):
        out += '\nHeaders: %s' % e.headers
      if hasattr(e, 'url'):
        out += '\nUrl: %s' % e.url
      if hasattr(e, 'msg'):
        out += '\nMsg: %s' % e.msg
      # The web page in some urllib exceptions.
      if hasattr(e, 'read') and callable(e.read):
        out += '\nread(): %s' % e.read()
      if hasattr(e, 'info') and callable(e.info):
        out += '\ninfo(): %s' % e.info()
  except Exception:
    pass
  return out


88
def SendStack(last_tb, stack, url=None, maxlen=50, verbose=True):
89
  """Sends the stack trace to the breakpad server."""
90 91
  if not IS_ENABLED:
    return
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  def p(o):
    if verbose:
      print(o)
  p('Sending crash report ...')
  params = {
    'args': sys.argv,
    'cwd': os.getcwd(),
    'exception': FormatException(last_tb),
    'host': _HOST_NAME,
    'stack': stack[0:4096],
    'user': getpass.getuser(),
    'version': sys.version,
  }
  p('\n'.join('  %s: %s' % (k, params[k][0:maxlen]) for k in sorted(params)))
  p(post(url or DEFAULT_URL + '/breakpad', params))
107 108


109
def SendProfiling(url=None):
110 111 112 113 114 115 116 117
  params = {
    'argv': ' '.join(sys.argv),
    # Strip the hostname.
    'domain': _HOST_NAME.split('.', 1)[-1],
    'duration': time.time() - _TIME_STARTED,
    'platform': sys.platform,
  }
  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 124 125 126 127 128 129
  if last_value:
    if not isinstance(last_value, KeyboardInterrupt):
      last_tb = getattr(sys, 'last_traceback', None)
      if last_tb:
        SendStack(last_value, ''.join(traceback.format_tb(last_tb)))
  else:
    SendProfiling()
130 131


132 133 134 135 136 137 138 139 140
def Register():
  """Registers the callback at exit. Calling it multiple times is no-op."""
  global _REGISTERED
  if _REGISTERED:
    return
  _REGISTERED = True
  atexit.register(CheckForException)


141
if IS_ENABLED:
142 143 144 145
  Register()

# Uncomment this line if you want to test it out.
#Register()