c++c++17log4cxx

LOG4CXX_INFO clean wrapper that is not a #define


I am trying to wrap a #define macro in C++ into an inline or template function. In LOG4CXX it has a define such as:

#define LOG4CXX_INFO(logger, message) { \
        if (logger->isInfoEnabled()) {\
           ::log4cxx::helpers::MessageBuffer oss_; \
           logger->forcedLog(::log4cxx::Level::getInfo(), oss_.str(oss_ << message), LOG4CXX_LOCATION); }}

I am needing to add other context to the logs and have wrapped LOG4CXX_INFO in another define and have this implementation working. I am wanting to move away from using a #define to wrap this and do it in a more C++ way. This issue I am running into is that the message param can be of various types of objects concated together using + or << operators and this is currently handled in the MessageBuffer class with operator overrides.

If I try to template it such as:

   template<typename T> 
   void info(LoggerPtr logger, const T& message) {
        ...do stuff ...
        LOG4CXX_INFO(logger, message);
   }

The compiler will complain about not finding << operators for various types of objects.

What needs to be done to make this work?

Compiled with the following on ubuntu

gcc main.cpp -lstdc++ -llog4cxx
#include <log4cxx/logger.h>

using namespace std;

#define info(logger, message) LOG4CXX_INFO(logger, message)

template<typename T> void tinfo(log4cxx::LoggerPtr logger, const T& message) {
     LOG4CXX_INFO(logger, message);
}

int main()
{
        log4cxx::LoggerPtr logger;
        LOG4CXX_INFO(logger, "test" << "test2");

        info(logger,"another test");
        info(logger,"another test" << "another test2");

        tinfo(logger,"test template");

        //tinfo(logger,"test template" << "test template 2"); -> this line does not compile
        return 0;
}

Compiler error:

main.cpp: In function ‘int main()’:
main.cpp:21:31: error: invalid operands of types ‘const char [14]’ and ‘const char [16]’ to binary ‘operator<<’
  tinfo(logger,"test template" << "test template 2");

MessageBuffer code can be found here: https://github.com/apache/logging-log4cxx/blob/master/src/main/include/log4cxx/helpers/messagebuffer.h and here https://github.com/apache/logging-log4cxx/blob/master/src/main/cpp/messagebuffer.cpp


Solution

  • tinfo(logger,"test template" << "test template 2");
    

    is wrong. It first evaluates "test template" << "test template 2" and then passes the result to the function. But that makes no sense, since the left-hand side of << should be a stream object.

    This works with the macro, because macros are pure text substitution, so the substitution

    LOG4CXX_INFO(logger, "test" << "test2");
    

    yields

    oss_ << "test" << "test2"
    

    which is evaluated left-to-right. If you add parentheses around the macro argument

    LOG4CXX_INFO(logger, ("test" << "test2"));
    

    you reproduce the function behavior faithfully and you will get the same error because you would be evaluating

    oss_ << ("test" << "test2")
    

    If you want to output multiple things in one function call I suggest using a fold expression (available since C++17):

    template<typename... Ts>
    void tinfo(log4cxx::LoggerPtr logger, const Ts&... messages) {
        if (logger->isInfoEnabled()) {
            ::log4cxx::helpers::MessageBuffer oss_; 
            logger->forcedLog(::log4cxx::Level::getInfo(), oss_.str((oss_ << ... << messages)), LOG4CXX_LOCATION);
        }
    }
    
    tinfo(logger, "test template", "test template 2");
    
    

    If you want to keep the original syntax and/or refer to LOG4CXX_INFO instead of reimplementing the body of that macro, then I don't think it is possible without macros.