I want to implement C++ logging with the following characteristics:
- It must be available to all source code without needing for every function to have an extra parameter (which I presume requires a global)
- A logging call can specify a severity level (INFO, DEBUG, WARN, etc.) and the logging facility can be set at runtime to ignore calls below a certain severity level
- The log sink can be set at runtime to the console, or to a file.
Things I don't need are:
- Support for multiple log sinks at runtime (i.e., it all goes either to the console, or to a file)
- Support for multithreaded logging
- Ability to pass
cout
-style expressions (e.g."foo=" << foo
) in a logger call. I will only pass astd::string
.
I found this answer, which appears to address my needs, but it's quite a bit over my head. I think my confusion centers around functors. (I read the Wikipedia article but it clearly hasn't sunk in.)
Here are the parts I do understand:
- Using a macro (e.g. LOG_DEBUG) to conveniently specify severity level and call the logger.
- Using
#ifdef NDEBUG
to keep logging calls from being compiled (although I need to be able to set logging at runtime). - The rationale for using a macro to invoke the logger, so that it can automatically and invisibly add info like
__FILE__
and__LINE__
at point where the logger is being called. - The LOG macro contains an expression beginning with
static_cast<std::ostringstream&>
. I think this is purely to do with evaluating thecout
-style formatting string, which I don't intend to support.
Here's where I'm struggling:
Logger& Debug() {
static Logger logger(Level::Debug, Console);
return logger;
}
Reading about operator()
, it looks like class Logger
is used for creating "functors". Each Logger functor is instantiated (?) with both a level, and a LogSink. (Do you "instantiate" a functor?) LogSink is described as "a backend consuming preformatted messages", but I don't know what that would look like or how it's "written to". At what point is the static Logger object instantiated? What causes it to be instantiated?
These macro definitions...
#define LOG(Logger_, Message_) \
Logger_( \
static_cast<std::ostringstream&>( \
std::ostringstream().flush() << Message_ \
).str(), \
__FUNCTION__, \
__FILE__, \
__LINE__ \
);
#define LOG_DEBUG(Message_) LOG(Debug(), Message_)
... and this line of code...
LOG_DEBUG(my_message);
... get preprocessed to:
Debug()(my_message, "my_function", "my_file", 42);
What happens when this executes?
How and where is the formatted string actually written to the "log sink"?
(Note: it was suggested that I look at log4cpp - I found it much bigger and harder to understand than what I need, not to mention the political problems I'd have bringing a third-party library into our environment)
UPDATE:
To understand how the above solution works, I tried writing a minimally complete, working program. I intentionally removed the following:
- the "magic" involving std::ostringstream
- the #ifdef NDEBUG
- the Logger Level class enum
- the LogSink ctor argument (for now I will just write to std::cout)
Here is the complete source file:
#include <iostream>
#include <string>
class Logger {
public:
Logger(int l);
void operator()(std::string const& message,
char const* function,
char const* file,
int line);
private:
int _level;
};
Logger::Logger(int l) :
_level(l)
{ }
#define LOG(Logger_, Message_) \
Logger_( \
Message_, \
__FUNCTION__, \
__FILE__, \
__LINE__ \
)
#define LOG_DEBUG(Message_) \
LOG( \
Debug(), \
Message_ \
)
Logger& Debug() {
static Logger logger(1);
return logger;
}
// Use of Logger class begins here
int main(int argc, char** argv) {
LOG_DEBUG("Hello, world!");
return 0;
}
When compiled:
$ c++ main.cpp
Undefined symbols for architecture x86_64:
"Logger::operator()(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, char const*, char const*, int)", referenced from:
_main in main-c81cf6.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
I see that there's no definition of a function that takes those four arguments and writes them to std::cout
, but what is the name of the function I need to define?
(By now I agree that I should be using Boost::Log, but functors are obviously a topic that I don't fully understand.)