there's been a little discussion outside of this thread, and I'd like to consolidate all the points. Marcus, I and some others have also discussed some things on chat, too, and of course there's Jared's and Michael's comments here. Let me summarize:
- We would like to drop any kind of dependency and hand-roll our logger. I'll make some points on why further down.
- Any log message must have a severity and a "category" (or "source ID"). We already have this, but want to keep it.
- The source ID for a block should be its ID. I like Jared's suggestion of the log-hierarchy following the block-hierarchy.
- We'll also drop the debug logger vs the regular logger. Devs can use the DEBUG and TRACE log levels for their debugging.
- We absolutely want to make sure that log messages are properly interleaved. This requires some amount of serialization/thread handling, I'll also comment on that later.
- By default, there should be log files and logging to stderr. However, we also want to enable (or at least, not disable) other logging paths, such as journald, or network logging. This might be as simple as providing generic hooks for other loggers.
- If possible, we would also like to pull in log messages from dependencies, so they all go through the same logging interface. For example, UHD log messages could be redirected to the GNU Radio logger when running gr-uhd applications, so UHD and GNU Radio don't clobber each other with different formats of their logging messages.
Out of these requirements, there fall some design decisions:
- There will be one central "root logger". All other logger objects (such as a logger attached to a block) will be childs of the root logger
- Every child logger can filter log messages by severity. Do we also want to filter in the root logger, or do we just inherit the default log level from it? That's up for debate.
- The root logger will implement a thread-safe multi-writer queue for log infos. It will serially handle the actual logging, which means the logging is not fully synchronous -- but it also means that the publishing of the log messages is not handled by the thread that is producing it, which is good for performance.
- We will most likely start with the dumbest form of such a queue, maybe a std::queue with a mutex and a notification variable. We can optimize later, if need be. Lockfree data structures tend to be worse for best-case and better for worst-case latency, which probably makes them less suitable but who knows.
- The root logger will be created at runtime in a singleton pattern. Andrej pointed out that might cause trouble at destruction time, so we should think about that. Maybe only child loggers share a reference to the root logger, and the singleton factory only stores a weak pointer to it. It runs a separate thread to dispatch log messages to the various logging back-ends.
On top of that, here's a couple of suggestions from myself, which are the least fixed out of all of these bullet points:
- We could further simplify the block logging interface by adding an API call to the block itself, rather than exposing the logger object. This guarantees that the source ID for block log messages is always correct.
- I want (optional) colours on the stderr output :)