Viewing file: abrt_exception_handler.py (9.34 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
#:mode=python: # -*- coding: utf-8 -*- ## Copyright (C) 2001-2005 Red Hat, Inc. ## Copyright (C) 2001-2005 Harald Hoyer <harald@redhat.com> ## Copyright (C) 2009 Jiri Moskovcak <jmoskovc@redhat.com>
## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version.
## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
""" Module for the ABRT exception handling hook """
import sys import os
class RPMinfoError(Exception): """Exception class for RPMdb-querying related errors""" pass
def syslog(msg): """Log message to system logger (journal)"""
from systemd import journal
# required as a workaround for rhbz#1023041 # where journal tries to log into non-existent log # and fails (during %check in mock) # # try/except block should be removed when the bug is fixed
try: journal.send(msg) except: pass
def write_dump(tb_text, tb): if sys.argv[0][0] == "/": executable = os.path.abspath(sys.argv[0]) else: # We don't know the path. # (BTW, we *can't* assume the script is in current directory.) executable = sys.argv[0]
dso_list = None # Trace back is None in case of SyntaxError exception. if tb: try: import rpm dso_list = get_dso_list(tb) except ImportError as imperr: syslog("RPM module not available, cannot query RPM db for package "\ "names")
# Open ABRT daemon's socket and write data to it try: import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.settimeout(5) try: s.connect("/var/run" + "/abrt/abrt.socket") s.sendall("POST / HTTP/1.1\r\n\r\n") s.sendall("type=Python\0") s.sendall("pid=%s\0" % os.getpid()) s.sendall("executable=%s\0" % executable) # This handler puts a short(er) crash descr in 1st line of the backtrace. # Example: # CCMainWindow.py:1:<module>:ZeroDivisionError: integer division or modulo by zero s.sendall("reason=%s\0" % tb_text.splitlines()[0]) s.sendall("backtrace=%s\0" % tb_text)
if dso_list: s.sendall("dso_list=%s\0" % "\n".join(dso_list))
s.sendall("environ=") for k,v in os.environ.iteritems(): s.sendall("%s=%s\n" % (k,v)) s.sendall("\0")
s.shutdown(socket.SHUT_WR)
# Read the response and log if there's anything wrong response = "" while True: buf = s.recv(256) if not buf: break response += buf except socket.timeout as ex: syslog("communication with ABRT daemon failed: %s" % str(ex))
s.close() parts = response.split() if (len(parts) < 2 or (not parts[0].startswith("HTTP/")) or (not parts[1].isdigit()) or (int(parts[1]) >= 400)): syslog("error sending data to ABRT daemon: %s" % response)
except Exception as ex: syslog("can't communicate with ABRT daemon, is it running? %s" % str(ex))
def get_package_for_file(fpath): """ Returns package name for a given file.
@param fpath: filename @type fpath: str @return: package name for the file @rtype: str @throws RPMinfoError: if package for the file cannot be found
"""
import rpm ts = rpm.TransactionSet() mi = ts.dbMatch("basenames", fpath) try: header = mi.next() except StopIteration: raise RPMinfoError("Cannot get package and component for file "+ fpath) package = "{0}-{1}-{2}.{3}".format(header["name"], header["version"], header["release"], header["arch"])
return package
def get_dso_list(tb): """ Get the list of names of the packages whose files appear in the traceback.
@param tb: traceback @type tb: traceback @return: list of package names @rtype: list
"""
import inspect if inspect.istraceback(tb): tb = inspect.getinnerframes(tb)
packages = set() for (frame, fpath, lineno, func, ctx, idx) in tb: try: packages.add(get_package_for_file(fpath)) except RPMinfoError as rpmerr: continue
# remove the package name of the executable itself try: packages.discard(get_package_for_file(sys.argv[0])) except RPMinfoError as rpmerr: pass
return list(packages)
def require_abs_path(): """ Return True if absolute path requirement is enabled in configuration """
import problem
try: conf = problem.load_plugin_conf_file("python.conf") except OsError: return False
return conf.get("RequireAbsolutePath", "yes") == "yes"
def handleMyException((etype, value, tb)): """ The exception handling function.
progname - the name of the application version - the version of the application """
try: # Restore original exception handler sys.excepthook = sys.__excepthook__ # pylint: disable-msg=E1101
import errno
# Ignore Ctrl-C # SystemExit rhbz#636913 -> this exception is not an error if etype in [KeyboardInterrupt, SystemExit]: return sys.__excepthook__(etype, value, tb)
# Ignore EPIPE: it happens all the time # Testcase: script.py | true, where script.py is: ## #!/usr/bin/python ## import os ## import time ## time.sleep(1) ## os.write(1, "Hello\n") # print "Hello" wouldn't be the same # if etype == IOError or etype == OSError: if value.errno == errno.EPIPE: return sys.__excepthook__(etype, value, tb)
# Ignore interactive Python and similar # Check for first "-" is meant to catch "-c" which appears in this case: ## $ python -c 'import sys; print "argv0 is:%s" % sys.argv[0]' ## argv0 is:-c # Are there other cases when sys.argv[0][0] is "-"? if not sys.argv[0] or sys.argv[0][0] == "-": einfo = "" if not sys.argv[0] else " (python %s ...)" % sys.argv[0] syslog("detected unhandled Python exception in 'interactive mode%s'" % einfo) raise Exception
# Ignore scripts with relative path unless "RequireAbsolutePath = no". # (In this case we can't reliably determine package) syslog("detected unhandled Python exception in '%s'" % sys.argv[0]) if sys.argv[0][0] != "/": if require_abs_path(): raise Exception
import traceback
elist = traceback.format_exception(etype, value, tb)
if tb != None and etype != IndentationError: tblast = traceback.extract_tb(tb, limit=None) if len(tblast): tblast = tblast[len(tblast)-1] extxt = traceback.format_exception_only(etype, value) if tblast and len(tblast) > 3: ll = [] ll.extend(tblast[:3]) ll[0] = os.path.basename(tblast[0]) tblast = ll
ntext = "" for t in tblast: ntext += str(t) + ":"
text = ntext text += extxt[0] text += "\n" text += "".join(elist)
trace = tb while trace.tb_next: trace = trace.tb_next frame = trace.tb_frame text += ("\nLocal variables in innermost frame:\n") try: for (key, val) in frame.f_locals.items(): text += "%s: %s\n" % (key, repr(val)) except: pass else: text = str(value) + "\n" text += "\n" text += "".join(elist)
# Send data to the daemon write_dump(text, tb)
except: # Silently ignore any error in this hook, # to not interfere with other scripts pass
return sys.__excepthook__(etype, value, tb)
def installExceptionHandler(): """ Install the exception handling function. """ sys.excepthook = lambda etype, value, tb: handleMyException((etype, value, tb))
# install the exception handler when the abrt_exception_handler # module is imported try: installExceptionHandler() except Exception, e: # TODO: log errors? # OTOH, if abrt is deinstalled uncleanly # and this file (sitecustomize.py) exists but # abrt_exception_handler module does not exist, we probably # don't want to irritate admins... pass
if __name__ == '__main__': # test exception raised to show the effect div0 = 1 / 0 # pylint: disable-msg=W0612 sys.exit(0)
__author__ = "Harald Hoyer <harald@redhat.com>"
|