管理Python扩展中的日志/警告

2 投票
2 回答
1370 浏览
提问于 2025-04-15 23:41

简而言之:在你的Python项目中,如何在C++部分进行可配置的(最好是可捕获的)日志记录?接下来会有详细说明。

假设你有几个编译好的.so模块,这些模块可能需要进行错误检查,并提醒用户数据(部分)不正确。目前,我的设置比较简单,使用的是Python代码中的logging框架和C/C++中的log4cxx库。log4cxx的日志级别是在一个文件中定义的(log4cxx.properties),而且现在是固定的,我在考虑如何让它更灵活。我看到几种选择:

  1. 一种控制方式是进行模块级的配置调用。

    # 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"
    
  2. 让每个进行日志记录的编译函数接受可选的日志参数。

    # 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"
    
  3. 还有其他方法吗?

第二种方法似乎更干净,但需要改变函数的签名(或者使用关键字参数并进行解析)。第一种方法可能有点笨拙,但可以一次性设置整个模块,并且减少了每个函数中的逻辑。

你对此有什么看法?我也很欢迎其他解决方案。

谢谢,

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日志记录器中。

撰写回答