5

I have different parts of my application calling a logger function to log details.

Logger class

std::string filename = "blahblah"; // variable to store the location of the properties file 
log4cpp::PropertyConfigurator::configure(filename);

void Logger::logging(const std::string& msg)
{
   Log4cpp::Category& myLogger = log4cpp::Category::getRoot();

   myLogger.log(log4cpp::Priority::INFO, msg);//only takes in string as input
}

Calling class

Logger logMe;

int a = 5;
double b = 6;

logMe.logging("log this msg" + a + "," + b);

I realised that the above will give me error, as a and b are of different types. One way to solve it is to use std::to_string

logMe.logging("log this msg" + std::to_string(a) + "," + std::to_string(b));

However, I have hundreds of calls to the logging function, and it will be time consuming to edit each and every call to std::to_string. Is/are there any easier way(s) to do it?

Oh and to clarify, the code works before as the previous way was by defining a #define function.

#Define logging(FLAG, X)\
do {\
    ...
    clog << x; \
}while(0)

logging(LogFlag::Warning, "log this msg" << a << "," << b << endl);

But i am now rewriting parts of the code to comply to static testing.

Thanks in advance.

Tony Tony
  • 455
  • 1
  • 4
  • 9

3 Answers3

5

You can add an overload of logging that takes a parameter pack and joins it up into a string, using std::stringstream

In c++17, we can use a fold expression, e.g.

template <typename Args ...>
void Logger::logging(Args ... args)
{
   std::stringstream ss;
   (ss << ... << args); 

   Log4cpp::Category& myLogger = log4cpp::Category::getRoot();

   myLogger.log(log4cpp::Priority::INFO, ss.str());
}

In c++11 or 14, we have to be slightly more tricky

template <typename ... Args >
void Logger::logging(Args ... args)
{
   std::stringstream ss;
   std::initializer_list<int> unused{ (ss << args, 0)... };

   Log4cpp::Category& myLogger = log4cpp::Category::getRoot();

   myLogger.log(log4cpp::Priority::INFO, ss.str());
}

Then you call either as e.g.

logMe.logging("log this msg", a, ",", b);
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • i tried using this method, but i get "invalid operands of types 'const unsigned int' to binary 'operator<<'. does the method only works for int since std::initializer_list? – Tony Tony Aug 11 '17 at 08:43
  • @TonyTony how are you calling it? It should be `logMe.logging("log this msg", a, ",", b);` – Caleth Aug 11 '17 at 08:54
4

I suggest adding an operator<<() to the class

class Logger
{
     public:

          Logger &operator<<(const std::string &s)
          {
              logging(s)
              return *this;
          };

          Logger &operator<<(const char *s)
          {
              return operator<<(std::string(s));
          }


          template <class T>
               Logger &operator<<(const T &v)
          {
               std::ostringstream s;
               s << v;
               return operator<<(logging(ss.str()));
          };

       // other stuff you have in your class, including the logging() function
};

//  to use

logMe << "log this msg" << a << b;

Not quite the same syntax to use this as you have described, but it works more generally.

Peter
  • 35,646
  • 4
  • 32
  • 74
3

It's fairly easy to use a stringstream. You can then convert it to std::string using str().

#include <sstream>
...
int a = 5;
double b = 6;

std::stringstream ss;
ss << "log this msg" << a << b;
std::cout << ss.str() << std::endl;
logMe.logging(ss.str());
Roy
  • 3,027
  • 4
  • 29
  • 43
  • I know that I can use stringstream, but then I will still have to add std::stringstream code to every line that calls logMe.logging. So if i have 500 calls, I have to add std::stringstream code 500 times. Hence I'm asking if there's an easier way. But thanks for answering! – Tony Tony Aug 11 '17 at 07:37
  • @TonyTony I'm not sure if there is a cleaner way other than overriding an operator. However, I assume that requires changing a lot of code too? namely wherever you are making a log call. – Roy Aug 11 '17 at 07:51
  • yup over-ridding an operator seems like the best solution so far. However, problem will come in if say I want to add another parameter to perhaps specify the log flag. (E.g. logMe.logging(Flag::Warning, "log this msg"); – Tony Tony Aug 11 '17 at 08:00
  • @TonyTony Well you could then override the ```<<``` operator for the ```Flag::Warning``` type to do something different. – Roy Aug 11 '17 at 08:06