[PATCH 12/14] pytdb: Add support for logging

Kirill Smelkov kirr at landau.phys.spbu.ru
Sat Oct 2 07:43:51 MDT 2010


Logging is important when doing crash recovery and checks, and also
since it is used in tdbtorture, let's add support for it.

NOTE: when setting log_fn in Tdb ctor - it is not hooked immediately
like in C tdb_open_ex(). The point here is that there is no python
object yet, at the stage when tdb_open* is called.

Signed-off-by: Kirill Smelkov <kirr at landau.phys.spbu.ru>
---
 lib/tdb/pytdb.c                |   91 ++++++++++++++++++++++++++++++++++++++-
 lib/tdb/python/tests/simple.py |   17 +++++++
 2 files changed, 105 insertions(+), 3 deletions(-)

diff --git a/lib/tdb/pytdb.c b/lib/tdb/pytdb.c
index 713b026..6b66fd3 100644
--- a/lib/tdb/pytdb.c
+++ b/lib/tdb/pytdb.c
@@ -38,6 +38,7 @@
 typedef struct {
 	PyObject_HEAD
 	TDB_CONTEXT *ctx;
+	PyObject *py_logfn;
 	bool closed;
 } PyTdbObject;
 
@@ -100,21 +101,27 @@ static PyObject *PyString_FromTDB_DATA(TDB_DATA data)
 	Py_RETURN_TRUE; \
 } while (0)
 
+static void __obj_set_logging_function(PyTdbObject *self, PyObject *py_logfn);
+
+
 static PyObject *py_tdb_open(PyTypeObject *type, PyObject *args, PyObject *kwargs)
 {
 	char *name = NULL;
 	int hash_size = 0, tdb_flags = TDB_DEFAULT, flags = O_RDWR, mode = 0600;
+	PyObject *py_logfn = Py_None;
 	TDB_CONTEXT *ctx;
 	PyTdbObject *ret;
-	const char *kwnames[] = { "name", "hash_size", "tdb_flags", "flags", "mode", NULL };
+	const char *kwnames[] = { "name", "hash_size", "tdb_flags", "flags", "mode", "log_fn", NULL };
 
-	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|siiii", (char **)kwnames, &name, &hash_size, &tdb_flags, &flags, &mode))
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|siiiiO", (char **)kwnames, &name, &hash_size, &tdb_flags, &flags, &mode, &py_logfn))
 		return NULL;
 
 	if (name == NULL) {
 		tdb_flags |= TDB_INTERNAL;
 	}
 
+	/* we can't init logging at this stage because python tdb object does
+	 * not exist yet */
 	ctx = tdb_open(name, hash_size, tdb_flags, flags, mode);
 	if (ctx == NULL) {
 		PyErr_SetFromErrno(PyExc_IOError);
@@ -129,9 +136,76 @@ static PyObject *py_tdb_open(PyTypeObject *type, PyObject *args, PyObject *kwarg
 
 	ret->ctx = ctx;
 	ret->closed = false;
+	ret->py_logfn = Py_None; Py_INCREF(Py_None);
+	__obj_set_logging_function(ret, py_logfn);
 	return (PyObject *)ret;
 }
 
+/* logging function for log_fn=None */
+static void py_logfn_none(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
+{
+}
+
+static void py_logfn_helper(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
+{
+	va_list ap;
+	char *msg;
+	int len;
+	PyObject *pyret;
+	PyTdbObject *self = tdb_get_logging_private(tdb);
+
+	va_start(ap, format);
+	len = vasprintf(&msg, format, ap);
+	va_end(ap);
+	if (len < 0) {
+		msg = NULL;
+		PyErr_SetString(PyExc_RuntimeError,
+				"tdb.py_logfn_helper: vasprintf failed");
+		goto out;
+	}
+
+	/* logfn(db, level, msg) */
+	pyret = PyObject_CallFunction(self->py_logfn, "Ois", self, (int)level, msg);
+	Py_XDECREF(pyret);
+
+out:
+	/* we are in void-return callback from tdb code - we can't raise */
+	if (PyErr_Occurred())
+		PyErr_Print();
+
+	free(msg);
+}
+
+static void __obj_set_logging_function(PyTdbObject *self, PyObject *py_logfn)
+{
+	struct tdb_logging_context log_ctx;
+
+	Py_INCREF(py_logfn);
+	Py_DECREF(self->py_logfn);
+	self->py_logfn = py_logfn;
+
+	log_ctx.log_fn = (py_logfn != Py_None ? py_logfn_helper : py_logfn_none);
+	log_ctx.log_private = self;
+
+	tdb_set_logging_function(self->ctx, &log_ctx);
+}
+
+static PyObject *obj_set_logging_function(PyTdbObject *self, PyObject *args)
+{
+	PyObject *py_logfn;
+	if (!PyArg_ParseTuple(args, "O", &py_logfn))
+		return NULL;
+
+	__obj_set_logging_function(self, py_logfn);
+	Py_RETURN_NONE;
+}
+
+static PyObject *obj_log_fn(PyTdbObject *self)
+{
+	Py_INCREF(self->py_logfn);
+	return self->py_logfn;
+}
+
 static PyObject *obj_transaction_cancel(PyTdbObject *self)
 {
 	int ret = tdb_transaction_cancel(self->ctx);
@@ -669,6 +743,11 @@ static PyMethodDef tdb_object_methods[] = {
 	{ "traverse_read", (PyCFunction)obj_traverse_read, METH_VARARGS,
 		"S.traverse_read(fn|None) -> ntraversed\n"
 		"fn(key, data) -> continue?" },
+	{ "set_logging_function", (PyCFunction)obj_set_logging_function, METH_VARARGS,
+		"S.set_logging_function(logfn|None) -> None\n"
+		"logfn(db, level, msg)" },
+	{ "log_fn", (PyCFunction)obj_log_fn, METH_NOARGS,
+		"S.log_fn() -> logfn | None" },
 	{ NULL }
 };
 
@@ -735,6 +814,7 @@ static void tdb_object_dealloc(PyTdbObject *self)
 {
 	if (!self->closed)
 		tdb_close(self->ctx);
+	Py_DECREF(self->py_logfn);
 	PyObject_Del(self);
 }
 
@@ -809,7 +889,7 @@ PyTypeObject PyTdb = {
 };
 
 static PyMethodDef tdb_methods[] = {
-	{ "open", (PyCFunction)py_tdb_open, METH_VARARGS|METH_KEYWORDS, "open(name, hash_size=0, tdb_flags=TDB_DEFAULT, flags=O_RDWR, mode=0600)\n"
+	{ "open", (PyCFunction)py_tdb_open, METH_VARARGS|METH_KEYWORDS, "open(name, hash_size=0, tdb_flags=TDB_DEFAULT, flags=O_RDWR, mode=0600, log_fn=None)\n"
 		"Open a TDB file." },
 	{ NULL }
 };
@@ -846,6 +926,11 @@ void inittdb(void)
 	PyModule_AddObject(m, "DISALLOW_NESTING", PyInt_FromLong(TDB_DISALLOW_NESTING));
 	PyModule_AddObject(m, "INCOMPATIBLE_HASH", PyInt_FromLong(TDB_INCOMPATIBLE_HASH));
 
+	PyModule_AddObject(m, "DEBUG_FATAL", PyInt_FromLong(TDB_DEBUG_FATAL));
+	PyModule_AddObject(m, "DEBUG_ERROR", PyInt_FromLong(TDB_DEBUG_ERROR));
+	PyModule_AddObject(m, "DEBUG_WARNING", PyInt_FromLong(TDB_DEBUG_WARNING));
+	PyModule_AddObject(m, "DEBUG_TRACE", PyInt_FromLong(TDB_DEBUG_TRACE));
+
 	PyModule_AddObject(m, "__docformat__", PyString_FromString("restructuredText"));
 
 	Py_INCREF(&PyTdb);
diff --git a/lib/tdb/python/tests/simple.py b/lib/tdb/python/tests/simple.py
index d0abaad..b9dbb48 100644
--- a/lib/tdb/python/tests/simple.py
+++ b/lib/tdb/python/tests/simple.py
@@ -258,6 +258,23 @@ class SimpleTdbTests(TestCase):
         self.tdb.add_flags(tdb.NOMMAP)
         self.tdb.remove_flags(tdb.NOMMAP)
 
+    def test_log(self):
+        self.assertEquals(None, self.tdb.log_fn())
+
+        v = []
+        def logit(db, level, msg):
+            v.append( (level, msg) )
+
+        self.tdb.set_logging_function(logit)
+
+        self.tdb.transaction_start()
+        self.assertEquals(v, [])
+
+        # so far the only place in tdb which logs at TRACE level on non-failure
+        self.tdb.transaction_start()
+        self.assertEquals(len(v), 1)
+        self.assertEquals(v[0], (tdb.DEBUG_TRACE, "tdb_transaction_start: nesting 1\n"))
+
 
 if __name__ == '__main__':
     import unittest
-- 
1.7.3.1.50.g1e633


More information about the samba-technical mailing list