Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 80674) +++ Misc/NEWS (working copy) @@ -31,6 +31,9 @@ Library ------- +- Issue #8214: Syslog module now has an enable_exception_logging() method + which sets up sys.excepthook so that unhandled exceptions are sent to syslog. + - Issue #8577: distutils.sysconfig.get_python_inc() now makes a difference between the build dir and the source dir when looking for "python.h" or "Include". Index: Doc/library/syslog.rst =================================================================== --- Doc/library/syslog.rst (revision 80674) +++ Doc/library/syslog.rst (working copy) @@ -59,6 +59,13 @@ :func:`openlog` parameters are reset to defaults. +.. function:: enable_exception_logging([chain]) + + Set ``sys.excepthook`` so that unhandled exceptions get logged to the + system syslog. If *chain* is True (the default), the current excepthook + is called in addition. + + .. function:: setlogmask(maskpri) Set the priority mask to *maskpri* and return the previous mask value. Calls @@ -105,3 +112,7 @@ syslog.openlog(logopt=syslog.LOG_PID, facility=syslog.LOG_MAIL) syslog.syslog('E-mail processing initiated...') + +To enable logging of unhandled exceptions to syslog:: + + syslog.enable_exception_logging() Index: Modules/syslogmodule.c =================================================================== --- Modules/syslogmodule.c (revision 80674) +++ Modules/syslogmodule.c (working copy) @@ -26,6 +26,9 @@ Revision history: +2010/04/26 (Sean Reifschneider) + - Adding helpers for logging exceptions to syslog. + 2010/04/20 (Sean Reifschneider) - Use basename(sys.argv[0]) for the default "ident". - Arguments to openlog() are now keyword args and are all optional. @@ -59,6 +62,7 @@ static char S_log_open = 0; + static PyObject * syslog_get_argv(void) { @@ -223,11 +227,221 @@ return PyInt_FromLong(mask); } +static PyObject * +syslog_logexception(PyObject *self, PyObject *args) +{ + PyObject *traceback, *result, *etype, *evalue, *etb, *line; + char *s, *eol; + int i; + + + if (!PyArg_ParseTuple(args, "OOO:logexception", + &etype, &evalue, &etb)) + return(NULL); + + if ((traceback = PyImport_ImportModule("traceback")) == NULL) { + return(NULL); + } + + if ((result = PyObject_CallMethod(traceback, "format_exception", + "OOO", etype, evalue, etb)) == NULL) { + Py_DECREF(traceback); + return(NULL); + } + + if (!PySequence_Check(result)) { + PyErr_SetString(PyExc_TypeError, + "Traceback format_exception() did not return a sequence"); + Py_DECREF(result); + return(NULL); + } + + for (i = 0; i < PySequence_Size(result); i++) { + if (!(line = PySequence_GetItem(result, i))) { + Py_DECREF(result); + return(NULL); + } + + for (s = PyString_AsString(line); s && *s; s = eol) { + PyObject *message, *syslogargs; + + eol = strchr(s, '\n'); + + syslogargs = PyTuple_New(1); + if (!syslogargs) { + Py_DECREF(line); + Py_DECREF(result); + return(NULL); + } + + if (eol) { + message = PyString_FromStringAndSize(s, eol - s); + eol++; + } else { + message = PyString_FromString(s); + } + if (message == NULL) { + Py_DECREF(syslogargs); + Py_DECREF(line); + Py_DECREF(result); + return(NULL); + } + + PyTuple_SET_ITEM(syslogargs, 0, message); + syslog_syslog(self, syslogargs); + Py_DECREF(syslogargs); + Py_DECREF(message); + } + Py_DECREF(line); + } + Py_DECREF(result); + + Py_RETURN_NONE; +} + +/*********************************************************************/ +/* _Hook */ +static PyTypeObject hookType; + +typedef struct { + PyObject_HEAD + PyObject *saved_hook; +} hookObject; + +static PyObject * +newhookobject(PyObject *self, PyObject *args) +{ + hookObject *hook; + PyObject *chain = NULL; + + if (!PyArg_ParseTuple(args, "|O:_Hook", &chain)) + return(NULL); + + if ((hook = PyObject_New(hookObject, &hookType)) == NULL) + return(NULL); + + if (chain == NULL || PyObject_IsTrue(chain)) { + hook->saved_hook = PySys_GetObject("excepthook"); + Py_INCREF(hook->saved_hook); + } else { + hook->saved_hook = NULL; + } + + return((PyObject*) hook); +} + +static void +delhookobject(hookObject *hook) { + if (hook->saved_hook) { + Py_DECREF(hook->saved_hook); + } + PyObject_Del(hook); +} + +static PyObject * +hookobject_hook(hookObject *self, PyObject *args) +{ + PyObject *etype, *evalue, *etb, *arglist, *results; + + if (!PyArg_ParseTuple(args, "OOO:_hook", &etype, &evalue, &etb)) + return(NULL); + + if (!(arglist = Py_BuildValue("(OOO)", etype, evalue, etb))) { + return(NULL); + } + + if (self->saved_hook) { + results = PyEval_CallObject(self->saved_hook, arglist); + /* ignore results, we're already probably handling an exception */ + Py_XDECREF(results); + } + results = syslog_logexception((PyObject *) self, arglist); + /* ignore results, we're already probably handling an exception */ + Py_XDECREF(results); + Py_DECREF(arglist); + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(hook_doc, + "_hook (etype, evalue, etb)\n\ + \n\ + Private method which calls the saved hook, then the syslog hook."); + +static PyMethodDef hook_methods[] = { + {"_hook", (PyCFunction) hookobject_hook, METH_VARARGS, hook_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyDoc_STRVAR(hooktype_doc, + "An internal class which is used for the handling of chained exception\n\ + handlers."); + +static PyTypeObject hookType = { + PyObject_HEAD_INIT(NULL) + 0, + "_Hook", /*tp_name*/ + sizeof(hookObject), /*tp_size*/ + 0, /*tp_itemsize*/ + /* methods */ + (destructor) delhookobject, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + hooktype_doc,/*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + hook_methods,/*tp_methods*/ +}; + +/* End of _Hook */ +/*********************************************************************/ + +static PyObject * +syslog_enable_exception_logging(PyObject *self, PyObject *args) +{ + PyObject *chain = NULL, *hook, *attr; + + + /* check arguments here to make a prettier exception */ + if (!PyArg_ParseTuple(args, "|O:enable_exception_logging", &chain)) + return(NULL); + + if (!(hook = newhookobject(self, args))) { + return(NULL); + } + if (!(attr = PyObject_GetAttrString(hook, "_hook"))) { + return(NULL); + } + PySys_SetObject("excepthook", attr); + + Py_RETURN_NONE; +} + /* List of functions defined in the module */ static PyMethodDef syslog_methods[] = { + {"_Hook", (PyCFunction) newhookobject, METH_VARARGS}, {"openlog", (PyCFunction) syslog_openlog, METH_VARARGS | METH_KEYWORDS}, {"closelog", syslog_closelog, METH_NOARGS}, + {"logexception",syslog_logexception, METH_VARARGS}, + {"enable_exception_logging", syslog_enable_exception_logging, METH_VARARGS}, {"syslog", syslog_syslog, METH_VARARGS}, {"setlogmask", syslog_setlogmask, METH_VARARGS}, {"LOG_MASK", syslog_log_mask, METH_VARARGS}, @@ -235,6 +449,7 @@ {NULL, NULL, 0} }; + /* Initialization function for the module */ PyMODINIT_FUNC @@ -242,6 +457,10 @@ { PyObject *m; + Py_TYPE(&hookType) = &PyType_Type; + if (PyType_Ready(&hookType) < 0) + return; + /* Create the module and add the functions */ m = Py_InitModule("syslog", syslog_methods); if (m == NULL)