管理Python扩展中的日志/警告
简而言之:在你的Python项目中,如何在C++部分进行可配置的(最好是可捕获的)日志记录?接下来会有详细说明。
假设你有几个编译好的.so
模块,这些模块可能需要进行错误检查,并提醒用户数据(部分)不正确。目前,我的设置比较简单,使用的是Python代码中的logging
框架和C/C++中的log4cxx
库。log4cxx
的日志级别是在一个文件中定义的(log4cxx.properties
),而且现在是固定的,我在考虑如何让它更灵活。我看到几种选择:
一种控制方式是进行模块级的配置调用。
# foo/__init__.py import sys from _foo import import bar, baz, configure_log configure_log(sys.stdout, WARNING) # tests/test_foo.py def test_foo(): # Maybe a custom context to change the logfile for # the module and restore it at the end. with CaptureLog(foo) as log: assert foo.bar() == 5 assert log.read() == "124.24 - foo - INFO - Bar returning 5"
让每个进行日志记录的编译函数接受可选的日志参数。
# foo.c int bar(PyObject* x, PyObject* logfile, PyObject* loglevel) { LoggerPtr logger = default_logger("foo"); if (logfile != Py_None) logger = file_logger(logfile, loglevel); ... } # tests/test_foo.py def test_foo(): with TemporaryFile() as logfile: assert foo.bar(logfile=logfile, loglevel=DEBUG) == 5 assert logfile.read() == "124.24 - foo - INFO - Bar returning 5"
还有其他方法吗?
第二种方法似乎更干净,但需要改变函数的签名(或者使用关键字参数并进行解析)。第一种方法可能有点笨拙,但可以一次性设置整个模块,并且减少了每个函数中的逻辑。
你对此有什么看法?我也很欢迎其他解决方案。
谢谢,
2 个回答
0
谢谢大家提供的信息。我发现使用 PyObject_CallFunction 更简单。
// C++ code with logger passed as 2nd argument
static PyObject *lap_auction_assign(PyObject *self, PyObject *args)
{
PyObject *cost_dict; PyObject *pyLogger;
/* the O! parses for a Python object (listObj) checked to be of type PyList_Type */
if( !PyArg_ParseTuple( args, "O!O", &PyDict_Type, &cost_dict, &pyLogger))
return NULL;
/*
....... then call the logger
*/
char astring[2048];
sprintf( astring, "%d out of %d rows un-assigned", no_new_list, num_rows );
PyObject_CallFunction( pyLogger, "s", astring );
/* python */
# where logging is the python logging module and lap_auction is your extension
cost_dict = {}
tmp_assign_dict = lap_auction.assign( cost_dict, logging.info )
2
我非常相信尽可能多地在Python中完成工作,只有必须在C中完成的工作才留给C。所以我更喜欢方案#2,而不是方案#1,但你说得对,这样会让所有的函数签名看起来很乱。
我会创建一个模块级别的对象来处理日志记录,有点像回调函数。Python代码可以用任何方式创建这个对象,然后把它分配给模块对象。C代码只需使用这个全局对象来进行日志记录:
# Python:
import my_compiled_module
def log_it(level, msg):
print "%s: Oh, look: %s" % (level, msg)
my_compiled_module.logger = log_it
# C
static void log_it(unsigned int level, char * msg)
{
PyObject * args = Py_BuildValue("(Is)", level, msg);
PyObject_Call(log_it, args, NULL);
Py_DECREF(args);
}
现在你可以在代码中随处调用C的log_it函数,而不必担心Python代码是怎么实现的。当然,你的Python log_it函数会比这个更复杂,它会让你把所有的日志记录整合到一个Python日志记录器中。