views:

647

answers:

7

I'm writing a c++ shared library that is intended to be used by other library or executable. What is the best way to add a generic logging in my library? Ideally I'd like to adapt my library to logging functionality chosen by the library's user. Suppose i have a class in my library

class A {
  public: 
  void method(string param1, int param2);
}   

void A::method(string param1, int param2){
  /* i want to log values of param1 and param2, 
     but actual logging method must be defined outside of my library. 
     Maybe some kind of macro should help here. */ 
   /*e.g.*/  GENERICLOG_DEBUG("param1=" + param1+ " param2="+param1);
   /*if so, what that macro body should look like ? */
}

I don't want to make my library tied to any log4XXX specific API.

+2  A: 

Use log4cxx or log4cplus.

Vinay Sajip
A: 

syslog - then you can have whatever syslog facility use it.

or allow user to specify a callback.

A: 

If you don't want to use an existing logging lib, you can try to use macros. I would recommend to provide your own lib and to make it available with macros with printf like formatting mechanism.

I've done something like that in the past. The macro called a log object which encapsulate the real logging which was possible to extend via plugin.

But I think that something similar is already done by Log4xxx so maybe it is good to look at it.

Here is an proposal (sorry no time to test, I hope it works)

header:

#ifdef _MYAPI_IMPL
#define MY_API __declspec(dllexport)
#else
#define MY_API __declspec(dllimport)
#endif

class MY_API Log
{
public:
    enum Level {Error, Warning, Info, Debug};
    Log(Level level, const char* file, int line );
    void operator()(const char* Format, ... );
private:
       const char* m_file;
       Level m_level;
       int m_line;
};

#define __LOG(lvl) (Log(lvl, __FILE__, __LINE__ ))

#define LOG_ERR  __LOG(Log::Error)
#define LOG_WRN  __LOG(Log::Warning)
#define LOG_INF  __LOG(Log::Info)
#define LOG_DBG  __LOG(Log::Debug)

class My_API Logger
{
public:
    virtual void log(const char* message)=0;
};

class MY_API LoggerManager
{
private:
    static LoggerManager* s_inst;
    LoggerManager() {}
        virtual ~LoggerManager() {}
public:
    static LoggerManager* Instance();
    static void Clean();
    addLogger(Logger* newLogger, Log::Level minlevel = Log::Info);
        log(const char* file, int line, Log::Level level, const char* message);
};

Cpp:


Log::Log(Level level, const char* file, int line)
 : m_file(file), m_level(level), m_line(line)
{
}

void Log::operator()(const char* format, ... )
{
    va_list va;
    va_start(va, format);
    char message[LENGTH+1]={0};

    _vsnprintf(message, LENGTH, format, va);

    va_end(va);

    LoggerManager::Instance()->log(m_file, m_line, m_level, message);


};

Other libs and exe should be able to call like this. They just have to include the .h and link with the lib.

LOG_INF("Hello %s!", "world");

Update: I've added the explanation needed for the logging mechanism. One way is to use a singleton and to provide a interface to be subclassed for the actual logging.

The benefit of using macros is that it gives you the ability to get the location of the log which may be very interesting infomation in some cases. It is also possible to turn the macros into regular printf when you don't want to implement all the logging mechanism.

luc
+1  A: 

You can use google-glog. I find it nice to use.

http://code.google.com/p/google-glog/

It supports per file debug values, can log to syslog, email you, and loads of other nice features.

Newton
A: 

Declare prototype of logging function in your library:

extern void __cdecl UserLog(char* stText);

Do not implement it in your library. The user of your library should be implement this function and your library will use user's implementation.

User's implementation could look like the following (that's just a sample):

void __cdecl UserLog(char* stText)
{
  std::cout << stText << std::endl;
}

That's valid if your library is static library.

In your code you'll be able to use in the following way:

class A {
  public: 
  void method(string param1, int param2);
}   

void A::method(string param1, int param2){
   string formatted = str( boost::format( "param1=%s param2=%d" ) % param1 % param2 );
   UserLog( formatted.c_str() );
}
Kirill V. Lyadvinsky
+4  A: 

You can provide a callback mechanism to allow the library's user to provide your library with an adapter into their logging.

I.e., in your library provide an abstract logging interface class, e.g.:

class Logger
{
public:
    virtual ~Logger () {}
    virtual void log (const std::string& message) = 0;
};

and a class to regster a Logger with:

class Log
{
private:
    static Logger* logger_;
public:
    static void registerLogger (Logger& logger)
    { logger_ = &logger; }

    static void log (const std::string& message)
    { if (logger_ != 0) logger_->log (message); }
};

Your library then logs with something like:

Log::log ("My log message");

The application using your library should provide an implementation of Logger (i.e. a concrete subclass) and register it with your Log class. Their Logger impl will implement logging as they see fit.

This allows your library to be used by applications which use different logging libraries.

Note the above code is basic and untested. In practice you may want to change the log methods to include a logging level parameter, etc.

jon hanson
Thanks, nice solution! I can also provide default logging ( e.g. syslog) if no user-logger was registered in my lib.
pcapcanari
yep .
jon hanson
A: 

i have same query but the logging has to be implemented completely by the library itself as the caller / user of the library won't implement the logging.

Any inputs?

Adil