Sunday 24 June 2012

Wrapping... up?

I came upon POCO C++ while looking for a logging framework, and it looked a good fit for what I needed. Still, I felt a bit apprehensive about spreading Poco::Logger references throughout my code, so I decided to create a wrapper.

At a certain point, I created a class called LoggerController, that would control a container of loggers, allowing clients to create/store/retrieve loggers. And I wish I had created this class sooner. Why? Because after I started working on it, I quickly realized I was heading the wrong way.

I had created the wrong abstraction. I started with a worthwhile goal - minimize the dependency between my code and a particular logging implementation (in this case, POCO's). However, I was soon duplicating some of POCO's logger functionality, namely, the task of managing loggers.

In fact, what I actually needed was just something that encapsulated the Logger reference returned by Poco::Logger::get(). So, if I ever came upon another framework, say Moocho C++, with a logger better suited to my needs, I (hopefully) wouldn't have to change all my code, only the wrapper I created.

So, after some false starts, I reached something like this:

class Logger
{
public:
    enum Level
    { Fatal = 1, Critical, Error, Warning, Info = 6, Debug };
 
    // A SIMPLISTIC WAY TO CORRELATE ENUM VALUES AND TEXTUAL DESCRIPTION
    static std::pair< std::string, Level > LevelDescription[];

    Logger(Logger const&) = delete;
    Logger& operator=(Logger const&) = delete;
    Logger() = delete;

    Logger(std::string const& loggerName);

    // EACH OF THE FOLLOWING FUNCTIONS ACTIVATES A POCO CHANNEL. THEY'RE EXCLUSIVE,
    // A POCO LOGGER HAS A "THERE CAN BE ONLY 1" RELATIONSHIP WITH ITS CHANNEL

    // THE CONFIGURATION FOR THE LOGGER IS STORED IN A PROPERTIES FILE.
    // THE propertyPrefix TELLS US THE PROPERTY PREFIX WE NEED TO READ,
    // E.G., "com.this.and.that"
    void SimpleFileLogger(std::string const& propertyPrefix);
    //void FileLogger(std::string const& filepath); // ToDo

    void SetLoggerLevel(Level newLevel) { m_logger.setLevel(newLevel); }
    Level GetLoggerLevel() const 
        { return static_cast<Level>(m_logger.getLevel()); }
    bool OKToLog(Level l) const { return m_logger.is(l); }

    // BY CALLING THE poco_* MACROS, WE WILL BE, AT TIMES, 
    // TESTING THE LOG LEVEL TWICE, ONCE IN OUR OWN CODE, 
    // AND ONCE IN POCO'S CODE
    void LogDebug(std::string const& message) { poco_debug(m_logger, message); }
    void LogInfo(std::string const& message) 
        { poco_information(m_logger, message); }
private:
    Logger::Level ConvertLevel(std::string const& levelString) const;
    Poco::Logger& m_logger;
};

Note: The UPPERCASE comments is something I've picked up many years ago, on a Clipper 5 book, the rationale being that by emphasizing the comments, assuming these were correct and up-to-date, one could have an easier time understanding the intended functional goal of the code. However, with all the tools available to generate automatic docs from the comments, and with IDEs colored syntax (something I didn't have in those days), I'll be revising this practice.

It's actually a very small wrapper. I don't intend to create something that exposes the whole Poco::Logger functionality. That may happen, as time goes by, and my needs change. This is the way I'd rather work, when I have that option - start simple and add more functionality as needed.

So, once it's set up, it'll be relatively simple to use, you just declare it:

Logger l("TestLog");

// Do something (hopefully) useful

l.LogDebug("Insert useful info here");

And, when the scope ends, so does the wrapper.

Yes, there's the creation of an object everytime a logger is needed. For now, I'll assume that's fine. If I ever come upon a situation where it causes a bottleneck, I'll bypass my wrapper and use Poco::Logger directly. If this ever happens, I expect it'll be in a small section of code, anyway,

The actual management of TestLog (e.g., creating it, if it doesn't exist) resides in POCO C++, and I don't need (nor want) to duplicate it. I just want a handy way to get a reference to Poco::Logger without actually calling it Poco::Logger&.

It lends itself to becoming an interface, with several implementations (e.g., each implementation with its own logging framework); but, until I see a good reason to do it, I'll leave it as it is.

As I use it, I'll post here about its merits - and faults.

No comments:

Post a Comment