breakpad.py 3.8 KB

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