2

TL;DR 版本:您在 Python 项目中的 C++ 位中使用什么来进行可配置(最好是捕获)日志记录?详情如下。

假设您有一些编译.so模块可能需要进行一些错误检查并警告用户(部分)不正确的数据。目前我有一个非常简单的设置,我使用logging来自 Python 代码的框架和log4cxx来自 C/C++ 的库。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. 还有什么办法?

第二个似乎更干净一些,但它需要更改函数签名(或使用 kwargs 并解析它们)。第一个是.. 可能有点尴尬,但是一次性设置了整个模块并从每个单独的函数中删除了逻辑。

您对此有何看法?我也很喜欢替代解决方案。

谢谢,

4

2 回答 2

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

于 2010-06-08T11:49:29.613 回答
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 )
于 2011-11-11T00:31:38.930 回答