Question

I want to design a logging function with the following characteristics:

  • based on std::string rather than char*
  • supports variable number of variables, like printf
  • accepts as first parameter a severity level
  • avoids formatting overhead when severity level is below logging level
  • as simple as printf, or nearly so

I'm inclined to use boost::format because of its automatic type conversion. But here are some problems I see:

Its syntax is a little awkward: format("Mgr %s on pid %d is in state %s" % mgr % pid % s) is a little hard on the eyes (the list nature of the variables isn't as evident without commas). A log invocation would look like this:

mylog(INFO, format("Mgr %s on pid %d is in state %s" % mgr % pid % s));

What's worse, is it even possible to implement mylog() to check if we're logging INFO messages before the format object is constructed?

The other approach I thought about, which looks closer to printf, would be

mylog(INFO, "Mgr %s on pid %d is in state %s", mgr, pid, s);

or even

mylog_info("Mgr %s on pid %d is in state %s", mgr, pid, s);

The implementation would be something like:

mylog(int severity, string pattern, ...) {
    if (severity >= LOGLEVEL) {
        boost::format fmt(pattern);
        for parm /* remaining parameters */ {
            fmt % parm; // feed into format one at a time
        }
    }
}

This certainly defers construction of the format object until it's needed. But from what I can tell, there's no way, when traversing a variable argument list, to tell when you've reached the end!

Can someone suggest a syntactically simple technique of accomplishing this?

Note: I have g++ 4.4, which does not support all of c++11 (although it does support variadic templates)

Was it helpful?

Solution

You can use variadic templates and recursion.

Note: Since you mention GCC 4.4, this feature is available for that compiler, but it is not enabled by default. You have to add either the -std=c++0x or -std=gnu++0x option to the compiler to enable the feature.

A solution could be implemented as something along these lines:

// Does the actual logging of the formatted message
void mylog_r (int severity, boost::format &fmt) {
  std::cout << "[" << severity << "] "
            << fmt
            << std::endl;
}

// Unpacks the variadic arguments one at a time recursively
template <typename T, typename... Params>
void mylog_r (int severity, boost::format &fmt, T arg, Params... parameters) {
  mylog_r(severity, fmt % arg, parameters...); // recursively unpack
}

// This version of mylog() checks severity and converts fmt to boost::format
template <typename... Params>
void mylog (int severity, std::string fmt, Params... parameters) {
  if (severity < LEVEL) return;
  boost::format bfmt(fmt);
  mylog_r(severity, bfmt, parameters...);
}

OTHER TIPS

You can do something like that, i.e., like an implementation of printf:

#include <stdio.h>
#include <stdarg.h>
#include <string>

using namespace std;

enum Info_t
  {
    NONE,
    WARNING,
    ERROR   // so on
  };

int myLog(Info_t severity, string format, ...)
{
  int done = 0;
  if ( severity > WARNING ) {
    va_list arg;

    va_start(arg, format.c_str());
    done = vfprintf(stdout, format.c_str(), arg);
    va_end(arg);
  }

  return done;
}

int main()
{
  myLog(NONE, "Print %s %d.\n", "NONE", 0);
  myLog(WARNING, "Print %s %d.\n", "WARNING", 1);
  myLog(ERROR, "Print %s %d.\n", "ERROR", 2);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top