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: Lib/test/test_syslog.py =================================================================== --- Lib/test/test_syslog.py (revision 0) +++ Lib/test/test_syslog.py (revision 0) @@ -0,0 +1,33 @@ +import unittest +from test import test_support +import sys +syslog = test_support.import_module("syslog") + +class SyslogTestExceptHook(unittest.TestCase): + MAX_DURATION = 20 # Entire test should last at most 20 sec. + + def setUp(self): + if not hasattr(self, 'originalexcepthook'): + self.originalexcepthook = sys.excepthook + + def tearDown(self): + sys.excepthook = self.originalexcepthook + + def test_setup_default(self): + self.failIf('_Hook' in repr(sys.excepthook)) + syslog.enable_exception_logging() + self.failUnless('_Hook' in repr(sys.excepthook)) + + def test_setup_true(self): + self.failIf('_Hook' in repr(sys.excepthook)) + syslog.enable_exception_logging(True) + self.failUnless('_Hook' in repr(sys.excepthook)) + + def test_setup_false(self): + self.failIf('_Hook' in repr(sys.excepthook)) + syslog.enable_exception_logging(False) + self.failUnless('_Hook' in repr(sys.excepthook)) + + +if __name__ == '__main__': + unittest.main() 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) { @@ -74,24 +78,24 @@ PyObject *argv = PySys_GetObject("argv"); if (argv == NULL) { - return(NULL); + return NULL; } argv_len = PyList_Size(argv); if (argv_len == -1) { PyErr_Clear(); - return(NULL); + return NULL; } if (argv_len == 0) { - return(NULL); + return NULL; } scriptobj = PyList_GetItem(argv, 0); if (!PyString_Check(scriptobj)) { - return(NULL); + return NULL; } if (PyString_GET_SIZE(scriptobj) == 0) { - return(NULL); + return NULL; } atslash = strrchr(PyString_AsString(scriptobj), SEP); @@ -102,7 +106,7 @@ return(scriptobj); } - return(NULL); + return NULL; } @@ -136,8 +140,7 @@ openlog(S_ident_o ? PyString_AsString(S_ident_o) : NULL, logopt, facility); S_log_open = 1; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } @@ -173,8 +176,7 @@ Py_BEGIN_ALLOW_THREADS; syslog(priority, "%s", message); Py_END_ALLOW_THREADS; - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject * @@ -186,8 +188,7 @@ S_ident_o = NULL; S_log_open = 0; } - Py_INCREF(Py_None); - return Py_None; + Py_RETURN_NONE; } static PyObject * @@ -223,11 +224,224 @@ 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)) { + if ((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; + } + + results = syslog_logexception((PyObject *) self, arglist); + /* ignore results, we're already probably handling an exception */ + Py_XDECREF(results); + + if (self->saved_hook) { + results = PyEval_CallObject(self->saved_hook, 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)