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

发布于 2024-09-05 05:50:49 字数 1264 浏览 6 评论 0原文

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

假设您有一些已编译的 .so 模块,它们可能需要进行一些错误检查并警告用户(部分)不正确的数据。目前,我的设置非常简单,使用来自 Python 代码的 logging 框架和来自 C/C++ 的 log4cxx 库。 log4cxx 日志级别在文件(log4cxx.properties)中定义,目前已修复,我正在考虑如何使其更加灵活。我看到的几个选择:

  1. 控制它的一种方法是进行模块范围的配置调用。

    <前><代码># foo/__init__.py 导入系统 从 _foo 导入导入 bar、baz、configure_log 配置日志(sys.stdout,警告) # 测试/test_foo.py def test_foo(): # 也许是一个自定义上下文来更改日志文件 # 模块并在最后恢复它。 以 CaptureLog(foo) 作为日志: 断言 foo.bar() == 5 断言 log.read() == "124.24 - foo - INFO - Bar 返回 5" 控制它的
  2. 让每个进行日志记录的编译函数都接受可选的日志参数。

    <前><代码># foo.c int bar(PyObject* x, PyObject* 日志文件, PyObject* 日志级别) { LoggerPtr 记录器 = default_logger("foo"); if (日志文件!= Py_None) logger = file_logger(日志文件,日志级别); ... } # 测试/test_foo.py def test_foo(): 使用 TemporaryFile() 作为日志文件: 断言 foo.bar(logfile=logfile, loglevel=DEBUG) == 5 断言 logfile.read() == "124.24 - foo - INFO - Bar 返回 5"
  3. 还有其他方式吗?

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

您对此有何看法?我也热衷于替代解决方案。

谢谢,

TL;DR version: What do you use for configurable (and preferably captured) logging inside your C++ bits in a Python project? Details follow.

Say you have a a few compiled .so modules that may need to do some error checking and warn user of (partially) incorrect data. Currently I'm having a pretty simplistic setup where I'm using logging framework from Python code and log4cxx library from C/C++. log4cxx log level is defined in a file (log4cxx.properties) and is currently fixed and I'm thinking how to make it more flexible. Couple of choices that I see:

  1. One way to control it would be to have a module-wide configuration call.

    # 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. Have every compiled function that does logging accept optional log parameters.

    # 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. Some other way?

Second one seems to be somewhat cleaner, but it requires function signature alteration (or using kwargs and parsing them). First one is.. probably somewhat awkward but sets up entire module in one go and removes logic from each individual function.

What are your thoughts on this? I'm all ears to alternative solutions as well.

Thanks,

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

听闻余生 2024-09-12 05:50:49

我坚信在 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 记录器中。

I'm a big believer in having as much work happen in Python as possible, leaving only the work that has to happen in C in C. So I like #2 better than #1, but you are right, it clutters up all your function signatures.

I'd create a module-level object to handle the logging, sort of like a callback. The Python code could create the object any way it likes, then assign it to the module object. The C code can simply use the global object to do its logging:

# 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);
}

Now you can simply call the C log_it function throughout your code, and not worry about how the Python code gets it done. Of course, your Python log_it function would be richer than this one, and it would let you get all of your logging integrated into one Python logger.

诗化ㄋ丶相逢 2024-09-12 05:50:49

谢谢你们提供的信息。我发现 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 )

Thanks for the information guys. I found the PyObject_CallFunction easier to use.

// 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 )
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文