Managing logs/warnings in Python extensions

拥有回忆 提交于 2020-01-04 08:25:10

问题


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,


回答1:


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.




回答2:


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 )


来源:https://stackoverflow.com/questions/2995027/managing-logs-warnings-in-python-extensions

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!