summaryrefslogtreecommitdiffstats
path: root/src/hooks/abrt_exception_handler.py.in
blob: ace51c5cb497494318a47b4df4ac2252ce2ec404 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#: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., 675 Mass Ave, Cambridge, MA 02139, USA.

"""
Module for the ABRT exception handling hook
"""

import sys
import os
import socket

def write_dump(pid, tb):
    executable = "Exception raised from python shell"
    if sys.argv[0]:
        executable = os.path.abspath(sys.argv[0])

    # Open ABRT daemon's socket and write data to it.
    try:
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        s.connect(@VAR_RUN@ + "/abrt/abrt.socket")
        s.sendall("PUT / HTTP/1.1\r\n\r\n")
        s.sendall("PID=%s\0" % pid)
        s.sendall("EXECUTABLE=%s\0" % executable)
        s.sendall("ANALYZER=Python\0")
        s.sendall("BASENAME=pyhook\0")
        # 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.splitlines()[0])
        s.sendall("BACKTRACE=%s\0" % tb)
        # TODO: read back response and complain if it is not "xxx Created"
        s.close()
    except Exception, ex:
        import syslog
        syslog.syslog("can't communicate with ABRT daemon, is it running? %s", str(ex))

def handleMyException((etype, value, tb)):
    """
    The exception handling function.

    progname - the name of the application
    version  - the version of the application
    """

    # restore original exception handler
    sys.excepthook = sys.__excepthook__  # pylint: disable-msg=E1101
    # ignore
    #  - uncaught ctrl-c
    #  - SystemExit rhbz#636913 -> this exception is not an error
    if etype in [KeyboardInterrupt, SystemExit]:
        return sys.__excepthook__(etype, value, tb)

    try:
        import os.path
        import traceback
        import errno
        import syslog

        # EPIPE is not a crash, 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)

        # "-c" appears in this case:
        # $ python -c 'import sys; print "argv0 is:%s" % sys.argv[0]'
        # argv0 is:-c
        if not sys.argv[0] or sys.argv[0] == "-c":
            # Looks like interactive Python - abort dumping
            syslog.syslog("abrt: detected unhandled Python exception")
            raise Exception
        syslog.syslog("abrt: detected unhandled Python exception in %s" % sys.argv[0])
        if sys.argv[0][0] != "/":
            # Relative path - can't reliably determine package
            # this script belongs to - abort dumping
            # TODO: check abrt.conf and abort only if
            # ProcessUnpackaged = no?
            raise Exception

        elist = traceback.format_exception(etype, value, tb)
        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

        # add coredump saving
        write_dump(os.getpid(), text)

    except:
        # silently ignore any error in this hook,
        # to not interfere with the python 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>"