1

I'm using spdlog in various C++ libraries for which Python bindings are created with pybind11.

Now I'm wondering if there is a nice way how I can configure these loggers (mostly the level and format) from the Python side.

Currently I am creating a separate logger for each class in the constructor (logger_ is a class member):

MyClass::MyClass()
{
    logger_ = spdlog::get(LOGGER_NAME);
    if (!logger_)
    {
        logger_ = spdlog::stderr_color_mt(LOGGER_NAME);
    }
    logger_->debug("Logger is set up");
}

Now I can just use MyClass and it will create and use a logger with default settings. If I want to change this, I can do so by creating a logger with the proper name before creating the class:

auto logger = spdlog::stdout_color_st(MyClass::LOGGER_NAME);
logger->set_level(spdlog::level::debug);
logger->set_pattern("[%l] %n: %v");

MyClass c;

However, I can't find a good way to do this when using the Python bindings of MyClass. I saw that there is already a Python package binding spdlog, so I tried the following:

import spdlog
logger = spdlog.ConsoleLogger(MyClass.LOGGER_NAME)
logger.set_level(spdlog.LogLevel.DEBUG)
logger.set_pattern("[%l] %n: %v")

c = MyClass()

However, this does not affect the logger used inside MyClass (I assume there is simply no connection between the spdlog instance from the Python package and the one in my C++ code).

So long story short: Is there a way how I can configure the logger used in the C++ code from the Python side? If there is a way without having to manually pass a logger to the constructor of each class, that would be great. But I'm not sure if this is possible?

luator
  • 4,769
  • 3
  • 30
  • 51

2 Answers2

0

I haven't used the python binding extensively, but see the documentation, particularly Note 2:

Note 2: Manually created loggers (i.e. those you construct directly) do not get automatically registered and will not be found by the get("...") call.

If you want to register such logger use the register_logger(...) function:

spdlog::register_logger(my_logger);
...
auto the_same_logger = spdlog::get("mylogger");

Assuming register_logger(...) is exposed in the python binding, you need to register the logger you created so that your class can see it.

jwm
  • 1,504
  • 1
  • 14
  • 29
  • `register_logger` is not bound by the Python package but since I found the idea promising, I took the code and added the binding. However, when calling it, it fails with an error, saying that a logger with that name already exists. I double-checked in pure C++ and get the same error there, so it seems that note from the documentation is outdated. – luator Jul 04 '23 at 15:45
  • I’m just starting with `spdlog` myself. I can tell you that `spdlog::get()` returns a `nullptr` if the name is unregistered; if `register_logger` says the name is already bound, then it is. Also, registration may not “flow” between DLLs. I think the correct pattern is to call `get` and if null, create then register – jwm Jul 05 '23 at 21:27
  • I checked with the maintainer, the note in the docs is still valid, there was some misunderstanding on my side what is meant by "manually created". Anyway, the Python bindings already take care of the registration internally. – luator Jul 06 '23 at 07:48
  • "Also, registration may not “flow” between DLLs." -- I think this is the root of the problem. It seems that the registries used in my C++ code and in the Python package do not interact, so there may not be an easy solution to my problem. – luator Jul 06 '23 at 07:51
  • Oh, I just got it wrong where I need to register. Before I added bindings for `register_logger` to the spdlog Python package but there it is useless. Instead, I need to add it to the module that binds my C++ class. Maybe this is what you meant in the first place? I wrote an answer describing what exactly I did. However, I'm happy to accept your answer, if you clarify it a bit (feel free to use the code snippets from my answer, if you want). – luator Jul 06 '23 at 08:31
0

jwm's answer pointed me to a working solution. The logger can be created on the Python side and be registered with the module containing the C++ class by also binding spdlog::register_logger() in that module.

  1. Add a binding for spdlog::register_logger() in the module that binds MyClass. So the code for binding looks like this:
    PYBIND11_MODULE(mylib, m)
    {
        pybind11::class_<MyClass>(m, "MyClass")
            /* ... */;
    
        m.def("register_logger", &spdlog::register_logger);
    }
    
  2. In the Python code, use that binding to register the logger with the C++ module:
    import spdlog
    import mylib
    
    logger = spdlog.ConsoleLogger(mylib.MyClass.LOGGER_NAME)
    logger.set_level(spdlog.LogLevel.DEBUG)
    logger.set_pattern("[%l] %n: %v")
    # register logger with the function from mylib, so it is accessible inside MyClass
    mylib.register_logger(logger.get_underlying_logger())
    
    c = mylib.MyClass()
    
luator
  • 4,769
  • 3
  • 30
  • 51